前言:
由于需要获得一个程序的运行时间,我们可以在Shell里简单地调用time command的形式来获取一个程序运行的real, user, system等时间信息,但是默认输出的形式类似:
real 0m0.077s
user 0m0.077s
sys 0m0.077s
假定我们只关心用户态时间,那么可以直接使用time command | grep user来获取。但是再使用这个命令后,我们却无法像往常一样grep到我们想要的信息。于是man time查看帮助后,我们得知,原来time会默认将输出写到stderr中,那么很简单,我们可以time command 2>&1 | grep user,这样将stderr重定向到stdout,再进行grep就可以了。可以结果还是出乎我们的预料,依然无法grep。google一番之后才知道,这里的time其实是一个shell的keyword,它的处理方式不同于普通的shell command。
NOTE: 可以通过 type 命令来查看一个command的类型,比如:
$ type [
[ is a shell builtin
$ type time
[ is a shell keyword
正文:
重定向Shell关键字time
在一个Shell脚本中,我想要获得一个命令的执行时间,并把结果重定向到一个文件中。首先我尝试如下命令:
$ time command > time.txt
不起作用。于是我发现time是输出到stderr上的。我将命令改为:
$ time command 2> time.txt
还是没用。time还是把结果打印到了console上。
显然time是一个bash的保留字。它不像大多数内建的bash命令一样,但却是命令行语法(command line syntax)的一部分,就像 if 和 while。
Bash 3.1.0的手册中提到了关于pipeline和保留字time的信息:
Pipelines
A pipeline is a sequence of one or more commands separated by the char-
acter |. The format for a pipeline is:
[time [-p]] [ ! ] command [ | command2 ... ]
The standard output of command is connected via a pipe to the standard
input of command2. This connection is performed before any redirec-
tions specified by the command (see REDIRECTION below).
The return status of a pipeline is the exit status of the last command,
unless the pipefail option is enabled. If pipefail is enabled, the
pipeline's return status is the value of the last (rightmost) command
to exit with a non-zero status, or zero if all commands exit success-
fully. If the reserved word ! precedes a pipeline, the exit status of
that pipeline is the logical negation of the exit status as described
above. The shell waits for all commands in the pipeline to terminate
before returning a value.
If the time reserved word precedes a pipeline, the elapsed as well as
user and system time consumed by its execution are reported when the
pipeline terminates. The -p option changes the output format to that
specified by POSIX. The TIMEFORMAT variable may be set to a format
string that specifies how the timing information should be displayed;
see the description of TIMEFORMAT under Shell Variables below.
Each command in a pipeline is executed as a separate process (i.e., in
a subshell).
NOTE: 这里我们看到,一个包含管道的命令,默认的返回值将是最后一个子命令的返回值。
内建的help命令提到:
$ help time
time: time [-p] PIPELINE
Execute PIPELINE and print a summary of the real time, user CPU time,
and system CPU time spent executing PIPELINE when it terminates.
The return status is the return status of PIPELINE. The `-p' option
prints the timing summary in a slightly different format. This uses
the value of the TIMEFORMAT variable as the output format.
times: times
Print the accumulated user and system times for processes run from
the shell.
这就暗示说,使用bash的关键字time,会使得命令运行在一个子shell中,即使没有使用管道也是一样。
我还没有确认过这点,但是这确是为我们获得命令的执行时间提供的一些线索。
从语法描述中我们可以清楚的看到,time的输出不会被追加到命令的stderr中,而是由shell自身输出的。
关键字time设置了一个标记,知道pipeline命令执行完,timing信息才被打印到stderr中。time关键字要整个command和管道,还有相关的重定向都要来得高级。这就是为什么简单的重定向对于time而言不起作用。这是Bash语法定义的。command之后的重定向对于time而言,是command的一部分。
重定向time的输出可以通过将整个time命令放入一个子shell中来实现:
$ (time command) 2> time.txt
启动一个子shell不是必须的。我们同样可以使用下面的代码:
$ { time ls; } 2> time.txt
这可能比执行外部命令/usr/bin/time效率更高。当然time命令(外部命令)可能有你所需的更多的功能。
或许你想要依赖于bash time(关键字),因为你不知道系统安装的time(外部命令)工具是否有你所需的功能。
在重定向和管道行为这两方面来看,使用上述命令同执行外部的time命令是等价的。
可以通过将stdout和stderr重定向到/dev/null来抑制命令本身的输出。
NOTE: 作者这里也提到,可以使用外部命令来完成关键字time提供的功能。