使用PS0和PS1显示每个bash命令的执行时间

Vla*_*hev 5 bash shell ps1

似乎通过执行PS0和PS1变量中的代码(据我所知,在运行提示命令之前和之后评估),应该可以记录每个正在运行的命令的时间并将其显示在提示中。像这样:

user@machine ~/tmp
$ sleep 1

user@machine ~/tmp 1.01s
$
Run Code Online (Sandbox Code Playgroud)

但是,由于这样的事情不起作用,我很快就陷入了PS0中的录制时间:

PS0='$(START=$(date +%s.%N))'
Run Code Online (Sandbox Code Playgroud)

据我了解,START分配发生在子外壳中,因此在外壳中不可见。您将如何处理?

Ton*_*nyB 10

正如@tc所说PS0,使用算术扩展允许您在and的扩展期间分配变量PS1。较新的 bash 版本还允许PS*样式扩展,因此您甚至不需要子 shell 即可获取当前时间。对于 bash 4.4:

# PS0 extracts a substring of length 0 from PS1; as a side-effect it causes
# the current time as epoch seconds to PS0time (no visible output in this case)
PS0='\[${PS1:$((PS0time=\D{%s}, PS1calc=1, 0)):0}\]'
# PS1 uses the same trick to calculate the time elapsed since PS0 was output.
# It also expands the previous command's exit status ($?), the current time
# and directory ($PWD rather than \w, which shortens your home directory path
# prefix to "~") on the next line, and finally the actual prompt: 'user@host> '
PS1='\nSeconds: $((PS1calc ? \D{%s}-$PS0time : 0)) Status: $?\n\D{%T} ${PWD:PS1calc=0}\n\u@\h> '
Run Code Online (Sandbox Code Playgroud)

%N日期指令似乎没有作为\D{...}bash 4.4 扩展的一部分来实现。这很遗憾,因为我们只有单秒单位的分辨率。)

由于PS0仅在有要执行的命令时才评估和打印,因此该PS1calc标志设置为是否在扩展1中执行时间差(在命令之后) (意味着之前未扩展,因此没有重新评估)。然后重置为. 这样,空行(只需按回车键)就不会在按下回车键之间累积秒数。PS1PS1calc0PS0PS1timePS1PS1calc0

此方法的一个好处是,当您处于活动状态时没有输出set -x。看不到任何子 shell 或临时文件:一切都在 bash 进程本身内完成。


小智 5

我正在寻找不同问题的解决方案,并遇到了这个问题,并认为这听起来很酷。除了我为其他问题开发的解决方案之外,还使用@Scheff的出色答案作为基础,我想出了一个更优雅、功能更全面的解决方案。

首先,我创建了一些函数来读取/写入内存中的时间。写入共享内存文件夹可防止磁盘访问,并且如果由于某种原因未清除文件,则不会在重新启动时持续存在

function roundseconds (){
  # rounds a number to 3 decimal places
  echo m=$1";h=0.5;scale=4;t=1000;if(m<0) h=-0.5;a=m*t+h;scale=3;a/t;" | bc
}

function bash_getstarttime (){
  # places the epoch time in ns into shared memory
  date +%s.%N >"/dev/shm/${USER}.bashtime.${1}"
}

function bash_getstoptime (){
  # reads stored epoch time and subtracts from current
  local endtime=$(date +%s.%N)
  local starttime=$(cat /dev/shm/${USER}.bashtime.${1})
  roundseconds $(echo $(eval echo "$endtime - $starttime") | bc)
}
Run Code Online (Sandbox Code Playgroud)

bash_ 函数的输入是 bash PID

这些函数和以下内容被添加到 ~/.bashrc 文件中

ROOTPID=$BASHPID
bash_getstarttime $ROOTPID
Run Code Online (Sandbox Code Playgroud)

这些创建初始时间值并将 bash PID 存储为可以传递给函数的不同变量。然后将函数添加到 PS0 和 PS1

PS0='$(bash_getstarttime $ROOTPID) etc..'
PS1='\[\033[36m\] Execution time $(bash_getstoptime $ROOTPID)s\n'
PS1="$PS1"'and your normal PS1 here'
Run Code Online (Sandbox Code Playgroud)

现在它会在处理终端输入之前在 PS0 中生成时间,处理完终端输入后在 PS1 中再次生成时间,然后计算差值并添加到 PS1。最后,这段代码会在终端退出时清理存储的时间:

function runonexit (){
  rm /dev/shm/${USER}.bashtime.${ROOTPID}
}

trap runonexit EXIT
Run Code Online (Sandbox Code Playgroud)

把它们放在一起,加上一些正在测试的额外代码,它看起来像这样:

终端输出

重要的部分是以毫秒为单位的执行时间,以及存储在共享内存中的所有活动终端 PID 的 user.bashtime 文件。PID 也在终端输入后立即显示,因为我将它的显示添加到 PS0,您可以看到添加和删除的 bashtime 文件。

PS0='$(bash_getstarttime $ROOTPID) $ROOTPID experiments \[\033[00m\]\n'
Run Code Online (Sandbox Code Playgroud)


Sch*_*eff 3

我把这个当作谜题,并想展示我的谜题结果:

首先我摆弄了时间测量。(date +%s.%N我之前没有意识到)是我的起点。不幸的是,似乎bashs 算术评估似乎不支持浮点数。因此,我选择了其他东西:

$ START=$(date +%s.%N)

$ awk 'BEGIN { printf("%fs", '$(date +%s.%N)' - '$START') }' /dev/null
8.059526s

$
Run Code Online (Sandbox Code Playgroud)

这足以计算时间差。

接下来,我确认了您已经描述的内容:子 shell 调用会阻止使用 shell 变量。因此,我考虑在哪里可以存储开始时间,该时间对于子 shell 来说是全局的,但又足够本地化,可以同时在多个交互式 shell 中使用。我的解决方案是临时的。文件(在/tmp)。为了提供一个独特的名称,我想出了这个模式:/tmp/$USER.START.$BASHPID

$ date +%s.%N >/tmp/$USER.START.$BASHPID ; \
> awk 'BEGIN { printf("%fs", '$(date +%s.%N)' - '$(cat /tmp/$USER.START.$BASHPID)') }' /dev/null
cat: /tmp/ds32737.START.11756: No such file or directory
awk: cmd. line:1: BEGIN { printf("%fs", 1491297723.111219300 - ) }
awk: cmd. line:1:                                              ^ syntax error

$
Run Code Online (Sandbox Code Playgroud)

该死!我再次陷入了子外壳问题。为了解决这个问题,我定义了另一个变量:

$ INTERACTIVE_BASHPID=$BASHPID

$ date +%s.%N >/tmp/$USER.START.$INTERACTIVE_BASHPID ; \
> awk 'BEGIN { printf("%fs", '$(date +%s.%N)' - '$(cat /tmp/$USER.START.$INTERACTIVE_BASHPID)') }' /dev/null
0.075319s

$
Run Code Online (Sandbox Code Playgroud)

下一步:将其与PS0和一起摆弄PS1。在类似的难题中(SO: How to Change bash Prompt Color based on exit code of last command?),我已经掌握了“引用地狱”。因此,我应该能够再次这样做:

$ PS0='$(date +%s.%N >"/tmp/${USER}.START.${INTERACTIVE_BASHPID}")'

$ PS1='$(awk "BEGIN { printf(\"%fs\", "$(date +%s.%N)" - "$(cat /tmp/$USER.START.$INTERACTIVE_BASHPID)") }" /dev/null)'"$PS1"
0.118550s
$
Run Code Online (Sandbox Code Playgroud)

啊。它开始起作用了。因此,只有一个问题——找到正确的启动脚本来初始化INTERACTIVE_BASHPID. 我发现~/.bashrc这似乎是正确的选择,并且我过去已经将其用于其他一些个人定制。

所以,把它们放在一起 - 这些是我添加到我的~/.bashrc

# command duration puzzle
INTERACTIVE_BASHPID=$BASHPID
date +%s.%N >"/tmp/${USER}.START.${INTERACTIVE_BASHPID}"
PS0='$(date +%s.%N >"/tmp/${USER}.START.${INTERACTIVE_BASHPID}")'
PS1='$(awk "BEGIN { printf(\"%fs\", "$(date +%s.%N)" - "$(cat /tmp/$USER.START.$INTERACTIVE_BASHPID)") }" /dev/null)'"$PS1"
Run Code Online (Sandbox Code Playgroud)

date添加第三行(命令)是为了解决另一个问题。将其注释掉并开始一个新的交互式 bash 以找出原因。

我的 cygwin xterm 与 bash 的快照,其中我将以上行添加到./~bashrc我的 cygwin xterm 的快照,带有“tuned”bash 提示符

笔记:

  1. 我认为这更像是一个难题的解决方案,而不是一个“严肃有效”的解决方案。我确信这种时间测量本身会消耗大量时间。该time命令可能提供更好的解决方案:SE:如何有效获取脚本的执行时间?。然而,这是一个很好的练习 bash 的讲座......

  2. 不要忘记,此代码会/tmp用越来越多的小文件污染您的目录。不时清理/tmp或添加适当的清理命令(例如 to ~/.bash_logout)。