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.
精彩评论