开发者

What is an elegant way that a shell-script times **itself** (really)?

开发者 https://www.devze.com 2023-03-28 04:36 出处:网络
I have a ba开发者_开发技巧sh script, call it Exp, which performs a computational experiment, and I want to

I have a ba开发者_开发技巧sh 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.


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


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.


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.

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号