3

I have a bash script, call it Exp, which performs a computational experiment, and I want to have the result of

time Exp

within the script itself: First it needs to be always done (and relying on typing "time Exp" is not enough -- for the critical case where you (or the user!!) needs it, it will be forgotten), and second the script Exp itself needs to store it in a file.

Writing a wrapper script (which calls "time Exp") seems to make ordinary work with Exp impossible due to the destruction of parameters and input/output by the time-command.

But actually all what is needed is to access the data in Exp itself of that universal record (which can also be accessed by ps) which is just printed by time! That is why I ask for an "elegant" solution, not just first storing somehow the date in Exp, and finally, before exit, computing the difference. But just simulating what the time-command is doing in Exp. I think that would be useful in many other situations.

Oliver
  • 31
  • 1
  • 2
    So run "time /bin/sh << EOF" and have your whole script be a here document. There are a couple other ways you might do it as well. – Ernest Friedman-Hill Aug 14 '11 at 12:07
  • If I would have to type this on the command line then it wouldn't be useful, since I just want to have **one command** for the whole thing (timing must be automatic). If it is part of the wrapper script, then I don't see how I can guarantee that all of the original functionality is maintained. And embedding into the script itself wouldn't work either, I guess. – Oliver Aug 14 '11 at 12:14
  • Look at @Jonathan Leffler's wonderful answer below; that'll do ya. – Ernest Friedman-Hill Aug 14 '11 at 12:56
  • @Oliver: Read again what Ernest said. :) – Lightness Races in Orbit Aug 14 '11 at 13:54
  • @Ernest Friedman-Hill: your comment is a perfectly good answer. I think you should make it an actual answer rather than a comment. – Bryan Oakley Aug 14 '11 at 15:29

3 Answers3

7

Since POSIX defines the time utility to write its result on standard error, the only trick you have to be aware of is that there is a bash built-in time that behaves slightly differently.

$ time sleep 1

real    0m1.004s
user    0m0.001s
sys     0m0.002s
$ time sleep 1 2>xyz

real    0m1.005s
user    0m0.001s
sys     0m0.003s
$ (time sleep 1) 2>xyz
$ cat xyz

real    0m1.005s
user    0m0.001s
sys     0m0.002s
$ /usr/bin/time sleep 1 2>xyz
$ cat xyz
        1.00 real         0.00 user         0.00 sys
$ 

Testing shown on MacOS X 10.7. Note the difference in output format between built-in and external versions of the time command. Note also that in the sub-shell format, the built-in time is redirected normally, but in the simple case, redirecting the output after the built-in time does not send the output to the same place that the rest of the standard error goes to.

So, these observations allow you to write your elegant script. Probably the simplest method is to wrap your existing script as a function, and then invoke that function via the built-in time.

Original script

# This is what was in the Exp script before.
# It echoes its name and its arguments to both stdout and stderr
echo "Exp $*"
echo "Exp $*" 1>&2

Revised script

log=./Exp.log

Exp()
{
    # This is what was in the Exp script before.
    # It echoes its name and its arguments to both stdout and stderr
    echo "Exp $*"
    echo "Exp $*" 1>&2
}

(time Exp "$@") 2>> $log

Note the careful use of "$@" in the invocation of the Exp function to preserve the separate arguments passed on the command line, and the equally deliberate use of $* inside the function (which loses the separate arguments, but is only for illustrative purposes).

Possible Issue

The only issue with this is that the error output from the original command also goes to the same log file as timing information. That can be resolved but involves tricky multiple redirections that are more likely to confuse than help. However, for the record, this works nicely:

log=./Exp.log

Exp()
{
    # This is what was in the Exp script before.
    # It echoes its name and its arguments to both stdout and stderr
    echo "Exp $*"
    echo "Exp $*" 1>&2
}

exec 3>&2

(time Exp "$@" 2>&3 ) 2>> $log
Community
  • 1
  • 1
Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
  • 1
    Aha, looks interesting. However, admittedly, I (nearly!) found a simpler solution: Just adding the line "ps -o time -p $$ > logfile" nearly does the job! It computes the processing time of the script -- now all what is missing is to sum up all the times of the children as well. ps has the S-modifier, but, alas, ps is badly documented, all the documentation on the Internet just repeats the same incomplete information, and it never says where to place the S-modifier in the command sequence! I tried many possibilities, but couldn't figure out the syntax. Perhaps I should open another question? – Oliver Aug 14 '11 at 13:17
1

You can just add the following as the first line of the script:

test -z "$TIMED" && TIMED=yes exec time $0 $@

This will not run time if TIMED is set in the environment, so it gives you a way to suppress the timing if you want.

William Pursell
  • 204,365
  • 48
  • 270
  • 300
1

The above two solutions invoke the time-command. I have my doubts that even the very elaborated one by @Jonathan Leffler is really functionally equivalent the original script: it seems to take care about output to standard output and standard error, but how it behaves w.r.t. symbolic links one needed to test (and that won't be so simple --- there are many subtleties regarding paths, especially when containing links). And I think w.r.t. the nasty quoting-business it definitely changes the semantics of the original script, making it necessary to add one quotation layer more to the parameters (if for example the script runs remotely, and one needs to have quotation marks).

With these two issues, handling of paths and symbolic links and quotation, we had a lot of trouble in the past, and these errors are very hard to catch: often the scripts we write use complicated other scripts from many mathematical/computer science packages, where literally hundreds of especially installed packages have to be handled, each with its own strange specialities, and so we want to avoid as much as possible adding further complications.

The solution, which I found with the help of @Arne, is simpler; see How to use the S-output-modifier with Unix/Linux command ps?

One just needs to add the line

ps p $$ k time S | tail -n 1 | tr -s '[:space:]' | cut -d ' ' -f 4 > log-file

to the script when one wants to store the (total) process-time in log-file. Don't know where the time-command gets the wall-clock and the system-time, but for the wall-clock perhaps one needs to do subtraction of time; and don't know about the system-time, but it must be somewhere.

Community
  • 1
  • 1
Oliver Kullmann
  • 253
  • 1
  • 7
  • Can you show a concrete example where the mechanism I suggested changes the meaning of the script - how a symlink confuses it, or how the quoting has to be changed? – Jonathan Leffler Aug 15 '11 at 13:44
  • Before going into the details, I need to ask how to give these examples --- it will take a bit space, one needs first to discuss about what "functional equivalence" here could mean, and then one must be really precise about the examples. Now these 500 something characters for a comment aren't much. I'm new to this forum, so I don't know what the standard is: do I just spread the comment over several comments, if needed, or is there another possibility? (Likely one shouldn't open a new answer.) – Oliver Kullmann Aug 15 '11 at 16:31
  • What about an email (see my profile)? Yes, I agree that neither comments nor a new question nor amending an answer really help here. – Jonathan Leffler Aug 15 '11 at 16:51