似乎通过在PS0和PS1变量中执行代码(根据我的理解,在运行提示命令之前和之后进行评估),应该可以记录每个运行命令的时间并在提示中显示它。这样的事情:
user@machine ~/tmp
$ sleep 1
user@machine ~/tmp 1.01s
$
然而,我很快就陷入PS0的录音时间,因为这样的事情不起作用:
PS0='$(START=$(date +%s.%N))'
据我所知,START
赋值发生在子shell中,因此在外壳中不可见。你会怎么做到这一点?
答案 0 :(得分:1)
我把它当作谜题,想要展示我困惑的结果:
首先,我摆弄了时间测量。 date +%s.%N
(我之前没有意识到)是我开始的地方。不幸的是,似乎bash
的算术评估似乎不支持浮点数。因此,我选择了别的东西:
$ START=$(date +%s.%N)
$ awk 'BEGIN { printf("%fs", '$(date +%s.%N)' - '$START') }' /dev/null
8.059526s
$
这足以计算时差。
接下来,我确认了您已经描述过的内容:子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
$
该死!我再次被困在子壳问题中。为此,我定义了另一个变量:
$ 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
$
下一步:将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
$
稀释。它开始起作用了。因此,只有一个问题 - 为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"
已添加第3行(date
命令)以解决另一个问题。评论它并开始一个新的互动bash以找出原因。
我的cygwin xterm与bash的快照,其中我将上面的行添加到./~bashrc
:
注意:
我认为这是一个谜题的解决方案,而不是一个严肃的生产方式#34;解。我确信这种时间测量会消耗很多时间。 time
命令可能提供更好的解决方案:SE: How to get execution time of a script effectively?。然而,这是练习bash的一个很好的讲座......
不要忘记此代码会使用越来越多的小文件污染您的/tmp
目录。可以不时清理/tmp
或添加适当的清理命令(例如~/.bash_logout
)。
答案 1 :(得分:1)
作为@tc said,使用算术展开允许您在PS0
和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> '
(%N
日期指令似乎没有作为 bash 4.4 的 \D{...}
扩展的一部分实现。这很遗憾,因为我们只有以秒为单位的分辨率。)
由于 PS0
仅在有命令执行时才计算和打印,PS1calc
标志设置为 1
以在 {{ 1}} 扩展与否(PS1
是 PS1calc
意味着 0
之前没有扩展,因此没有重新评估 PS0
)。 PS1time
然后将 PS1
重置为 PS1calc
。通过这种方式,空行(只是按回车键)不会在两次回车键之间累积秒数。
这种方法的一个好处是当您激活 0
时没有输出。看不到子shell或临时文件:一切都在bash进程本身内完成。
答案 2 :(得分:0)
我一直在寻找其他问题的解决方案,并提出了这个问题,并决定听起来像是一个很棒的功能。除了为其他问题开发的解决方案之外,还以@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)
}
bash_函数的输入是bash PID
这些功能及以下功能已添加到〜/ .bashrc文件中
ROOTPID=$BASHPID
bash_getstarttime $ROOTPID
这将创建初始时间值,并将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'
现在,它将在处理终端输入之前在PS0中生成时间,并在处理终端输入之后在PS1中再次生成时间,然后计算差并将其加到PS1。最后,这段代码会在终端退出时清除存储的时间:
function runonexit (){
rm /dev/shm/${USER}.bashtime.${ROOTPID}
}
trap runonexit EXIT
将它们放在一起,加上一些正在测试的其他代码,看起来像这样:
重要的部分是执行时间(以毫秒为单位)以及存储在共享内存中的所有活动终端PID的user.bashtime文件。在终端输入后也立即显示PID,因为我将其显示添加到PS0,您可以看到已添加和删除的bashtime文件。
PS0='$(bash_getstarttime $ROOTPID) $ROOTPID experiments \[\033[00m\]\n'
答案 3 :(得分:0)
算术扩展在当前进程中运行,并且可以分配给变量。它还会产生输出,您可以使用\e[$((...,0))m
(以输出\e[0m
)或${t:0:$((...,0))}
(不输出任何东西,可能更好)之类的东西来消费。 Bash支持中的64位整数支持将以POSIX纳秒计,直到2262年。
$ PS0='${t:0:$((t=$(date +%s%N),0))}'
$ PS1='$((( t )) && printf %d.%09ds $((t=$(date +%s%N)-t,t/1000000000)) $((t%1000000000)))${t:0:$((t=0))}\n$ '
0.053282161s
$ sleep 1
1.064178281s
$
$
没有评估PS0的空命令,这会留空行(我不确定您是否可以有条件地打印\ n而不会破坏内容)。您可以改用PROMPT_COMMAND(也可以节省派生)来解决此问题:
$ PS0='${t:0:$((t=$(date +%s%N),0))}'
$ PROMPT_COMMAND='(( t )) && printf %d.%09ds\\n $((t=$(date +%s%N)-t,t/1000000000)) $((t%1000000000)); t=0'
0.041584565s
$ sleep 1
1.077152833s
$
$
也就是说,如果不需要亚秒精度,我建议改用$SECONDS
(如果有时间设置,这也更有可能返回明智的答案)。
答案 4 :(得分:0)
正如问题中所述,PS0
在子 shell 中运行,这使得它无法用于设置开始时间。
相反,可以使用带有纪元秒 history
的 %s
命令和内置变量 $EPOCHSECONDS
来计算命令何时完成,仅利用 $PROMPT_COMMAND
。< /p>
# Save start time before executing command (does not work due to PS0 sub-shell)
# preexec() {
# STARTTIME=$EPOCHSECONDS
# }
# PS0=preexec
# Save end time, without duplicating commands when pressing Enter on an empty line
precmd() {
local st=$(HISTTIMEFORMAT='%s ' history 1 | awk '{print $2}');
if [[ -z "$STARTTIME" || (-n "$STARTTIME" && "$STARTTIME" -ne "$st") ]]; then
ENDTIME=$EPOCHSECONDS
STARTTIME=$st
else
ENDTIME=0
fi
}
__timeit() {
precmd;
if ((ENDTIME - STARTTIME >= 0)); then
printf 'Command took %d seconds.\n' "$((ENDTIME - STARTTIME))";
fi
# Do not forget your:
# - OSC 0 (set title)
# - OSC 777 (notification in gnome-terminal, urxvt; note, this one has preexec and precmd as OSC 777 features)
# - OSC 99 (notification in kitty)
# - OSC 7 (set url) - out of scope for this question
}
export PROMPT_COMMAND=__timeit
注意:如果您的 ignoredups
中有 $HISTCONTROL
,则不会报告重新运行的命令。