65

有没有办法在Bash提示符中嵌入最后一个命令的运行时间?我希望看起来像这样:

[last: 0s][/my/dir]$ sleep 10
[last: 10s][/my/dir]$

背景

我经常运行长时间的数据处理工作,知道他们花了多长时间很有用,这样我就可以估计未来的工作需要多长时间。对于非常常规的任务,我会继续使用适当的日志记录技术严格记录这些信息。对于不太正式的任务,我只会在命令前加上time.

time自动执行每个交互命令并以几个字符而不是 3 行打印时间信息 会很好。

4

14 回答 14

108

这是实现您想要的最小的独立代码:

function timer_start {
  timer=${timer:-$SECONDS}
}

function timer_stop {
  timer_show=$(($SECONDS - $timer))
  unset timer
}

trap 'timer_start' DEBUG
PROMPT_COMMAND=timer_stop

PS1='[last: ${timer_show}s][\w]$ '
于 2009-12-07T20:43:26.497 回答
27

使用您的回复和其他一些线程,我写了这个提示,我想与您分享。我截取了一个截图,你可以看到:

  • 白色 : 最后返回码
  • 绿色和刻度线表示成功(返回码为 0)
  • 红色和十字标记表示错误(返回码>0)
  • (绿色或红色):括号中的最后一个命令执行时间
  • (绿色或红色):当前日期时间 (\t)
  • (如果不是 root,则为绿色,如果为 root,则为红色):记录的用户名
  • (绿色):服务器名称
  • (蓝色):pwd 目录和通常的 $

自定义提示

这是放入 ~/.bashrc 文件的代码:

function timer_now {
    date +%s%N
}

function timer_start {
    timer_start=${timer_start:-$(timer_now)}
}

function timer_stop {
    local delta_us=$((($(timer_now) - $timer_start) / 1000))
    local us=$((delta_us % 1000))
    local ms=$(((delta_us / 1000) % 1000))
    local s=$(((delta_us / 1000000) % 60))
    local m=$(((delta_us / 60000000) % 60))
    local h=$((delta_us / 3600000000))
    # Goal: always show around 3 digits of accuracy
    if ((h > 0)); then timer_show=${h}h${m}m
    elif ((m > 0)); then timer_show=${m}m${s}s
    elif ((s >= 10)); then timer_show=${s}.$((ms / 100))s
    elif ((s > 0)); then timer_show=${s}.$(printf %03d $ms)s
    elif ((ms >= 100)); then timer_show=${ms}ms
    elif ((ms > 0)); then timer_show=${ms}.$((us / 100))ms
    else timer_show=${us}us
    fi
    unset timer_start
}


set_prompt () {
    Last_Command=$? # Must come first!
    Blue='\[\e[01;34m\]'
    White='\[\e[01;37m\]'
    Red='\[\e[01;31m\]'
    Green='\[\e[01;32m\]'
    Reset='\[\e[00m\]'
    FancyX='\342\234\227'
    Checkmark='\342\234\223'


    # Add a bright white exit status for the last command
    PS1="$White\$? "
    # If it was successful, print a green check mark. Otherwise, print
    # a red X.
    if [[ $Last_Command == 0 ]]; then
        PS1+="$Green$Checkmark "
    else
        PS1+="$Red$FancyX "
    fi

    # Add the ellapsed time and current date
    timer_stop
    PS1+="($timer_show) \t "

    # If root, just print the host in red. Otherwise, print the current user
    # and host in green.
    if [[ $EUID == 0 ]]; then
        PS1+="$Red\\u$Green@\\h "
    else
        PS1+="$Green\\u@\\h "
    fi
    # Print the working directory and prompt marker in blue, and reset
    # the text color to the default.
    PS1+="$Blue\\w \\\$$Reset "
}

trap 'timer_start' DEBUG
PROMPT_COMMAND='set_prompt'
于 2016-01-15T13:52:45.747 回答
11

另一个非常简单的方法是:

trap 'SECONDS=0' DEBUG
export PS1='your_normal_prompt_here ($SECONDS) # '

这显示了自上一个简单命令启动以来的秒数。如果您在不输入命令的情况下直接按 Enter 键,则计数器不会重置 - 当您只想查看自上次在其中执行任何操作后终端已经启动了多长时间时,这可能会很方便。它在 Red Hat 和 Ubuntu 中对我来说很好用。在 Cygwin 下它对我不起作用,但我不确定这是一个错误还是只是尝试在 Windows 下运行 Bash 的限制。

这种方法的一个可能缺点是您不断重置 SECONDS,但如果您确实需要将 SECONDS 保留为自初始 shell 调用以来的秒数,您可以为 PS1 计数器创建自己的变量,而不是直接使用 SECONDS。另一个可能的缺点是像“999999”这样的大秒值可能会更好地显示为天+小时+分钟+秒,但是添加一个简单的过滤器很容易,例如:

seconds2days() { # convert integer seconds to Ddays,HH:MM:SS
  printf "%ddays,%02d:%02d:%02d" $(((($1/60)/60)/24)) \
  $(((($1/60)/60)%24)) $((($1/60)%60)) $(($1%60)) |
  sed 's/^1days/1day/;s/^0days,\(00:\)*//;s/^0//' ; }
trap 'SECONDS=0' DEBUG
PS1='other_prompt_stuff_here ($(seconds2days $SECONDS)) # '

这会将“999999”翻译成“11days,13:46:39”。最后的 sed 将“1days”更改为“1day”,并修剪掉诸如“0days,00:”之类的空前导值。调整口味。

于 2010-04-28T18:51:09.553 回答
10

您可以将这个 zsh 借用的钩子用于 bash: http: //www.twistedmatrix.com/users/glyph/preexec.bash.txt

使用此挂钩完成的时间 (Mac OS X):使用 Growl 监控长时间运行的 shell 命令

于 2009-12-07T20:50:30.103 回答
8

如果您在开始长期工作之前没有设置任何其他答案并且您只想知道该工作花费了多长时间,您可以做简单的

$ HISTTIMEFORMAT="%s " history 2

它会回复类似

  654  1278611022 gvn up
  655  1278611714 HISTTIMEFORMAT="%s " history 2

然后您可以直观地减去两个时间戳(有人知道如何捕获 shell 内置历史命令的输出吗?)

于 2010-07-08T18:00:52.313 回答
6

我从 Ville Laurikari 那里得到了答案,并使用time命令对其进行了改进,以显示亚秒级的准确性:

function timer_now {
  date +%s%N
}

function timer_start {
  timer_start=${timer_start:-$(timer_now)}
}

function timer_stop {
  local delta_us=$((($(timer_now) - $timer_start) / 1000))
  local us=$((delta_us % 1000))
  local ms=$(((delta_us / 1000) % 1000))
  local s=$(((delta_us / 1000000) % 60))
  local m=$(((delta_us / 60000000) % 60))
  local h=$((delta_us / 3600000000))
  # Goal: always show around 3 digits of accuracy
  if ((h > 0)); then timer_show=${h}h${m}m
  elif ((m > 0)); then timer_show=${m}m${s}s
  elif ((s >= 10)); then timer_show=${s}.$((ms / 100))s
  elif ((s > 0)); then timer_show=${s}.$(printf %03d $ms)s
  elif ((ms >= 100)); then timer_show=${ms}ms
  elif ((ms > 0)); then timer_show=${ms}.$((us / 100))ms
  else timer_show=${us}us
  fi
  unset timer_start
}

trap 'timer_start' DEBUG
PROMPT_COMMAND=timer_stop

PS1='[last: ${timer_show}][\w]$ '

当然,这需要启动一个进程,因此效率较低,但仍然足够快,以至于您不会注意到。

于 2015-07-29T08:19:35.093 回答
4

我发现trap ... DEBUG每次$PROMPT_COMMAND调用它都在运行,重置计时器,因此总是返回 0。

然而,我发现history记录了时间,我利用这些来得到我的答案:

HISTTIMEFORMAT='%s '
PROMPT_COMMAND="
  START=\$(history 1 | cut -f5 -d' ');
  NOW=\$(date +%s);
  ELAPSED=\$[NOW-START];
  $PROMPT_COMMAND"
PS1="\$ELAPSED $PS1"

虽然它并不完美:

  • 如果history没有注册命令(例如重复或忽略的命令),开始时间将是错误的。
  • 多行命令无法从history.
于 2016-04-12T23:56:03.527 回答
3

这是我对托马斯的看法

用于date +%s%3N以毫秒为基本单位,简化以下代码(少零)

function t_now {
    date +%s%3N
}

function t_start {
    t_start=${t_start:-$(t_now)}
}

function t_stop {
    local d_ms=$(($(t_now) - $t_start))
    local d_s=$((d_ms / 1000))
    local ms=$((d_ms % 1000))
    local s=$((d_s % 60))
    local m=$(((d_s / 60) % 60))
    local h=$((d_s / 3600))
    if ((h > 0)); then t_show=${h}h${m}m
    elif ((m > 0)); then t_show=${m}m${s}s
    elif ((s >= 10)); then t_show=${s}.$((ms / 100))s
    elif ((s > 0)); then t_show=${s}.$((ms / 10))s
    else t_show=${ms}ms
    fi
    unset t_start
}
set_prompt () {
t_stop
}

trap 't_start' DEBUG
PROMPT_COMMAND='set_prompt' 

然后添加$t_show到你的 PS1

于 2018-12-21T17:56:34.543 回答
1

bash 4.x 及更高版本的另一种方法是使用coprocwithPS0PS1如下所示:

cmd_timer()
{
    echo $(( SECONDS - $(head -n1 <&"${CMD_TIMER[0]}") ))
}

coproc CMD_TIMER ( while read; do echo $SECONDS; done )
echo '' >&"${CMD_TIMER[1]}" # For value to be ready on first PS1 expansion
export PS0="\$(echo '' >&${CMD_TIMER[1]})"
export PS1="[ \$(cmd_timer) ] \$"

这是一个.bashrc现成的片段。它对于使用undistract-metrap DEBUG的每个人都特别有用,它会为自己的目的而覆盖。

于 2019-07-03T17:09:54.077 回答
1

的翻译版本zsh

附加到您的~/.zshrc文件

function preexec() {
  timer=$(date +%s%3N)
}

function precmd() {
  if [ $timer ]; then
    local now=$(date +%s%3N)
    local d_ms=$(($now-$timer))
    local d_s=$((d_ms / 1000))
    local ms=$((d_ms % 1000))
    local s=$((d_s % 60))
    local m=$(((d_s / 60) % 60))
    local h=$((d_s / 3600))
    if ((h > 0)); then elapsed=${h}h${m}m
    elif ((m > 0)); then elapsed=${m}m${s}s
    elif ((s >= 10)); then elapsed=${s}.$((ms / 100))s
    elif ((s > 0)); then elapsed=${s}.$((ms / 10))s
    else elapsed=${ms}ms
    fi

    export RPROMPT="%F{cyan}${elapsed} %{$reset_color%}"
    unset timer
  fi
}
于 2020-08-20T10:16:29.280 回答
0

将 \t 在 PS1 中为您工作吗?

它没有给出经过的时间,但它应该很容易在必要时减去时间。

$ export PS1='[\t] [\w]\$ '
[14:22:30] [/bin]$ sleep 10
[14:22:42] [/bin]$

根据 OP 的评论,他已经在使用 \t。如果可以使用 tcsh 代替 bash,则可以设置时间变量。

/bin 1 > set time = 0
/bin 2 > sleep 10
0.015u 0.046s 0:10.09 0.4%      0+0k 0+0io 2570pf+0w
/bin 3 >

您可以将打印格式更改为不那么难看(参见 tcsh 手册页)。

/bin 4 > set time = ( 0 "last: %E" )
/bin 5 > sleep 10
last: 0:10.09
/bin 6 >

我不知道 bash 中有类似的设施

于 2009-12-07T20:23:16.240 回答
0

这是我的版本

  • 使用日期格式化时间,只计算天数
  • 设置终端标题
  • 在 PS1 中为用户 $ + root 使用 \$ #
  • 显示返回码/退出码
  • 使用 date -u 禁用 DST
  • 使用像 _foo 这样的隐藏名称
_x_dt_min=1 # minimum running time to show delta T
function _x_before {
    _x_t1=${_x_t1:-$(date -u '+%s.%N')} # float seconds
}
function _x_after {
    _x_rc=$? # return code
    _x_dt=$(echo $(date -u '+%s.%N') $_x_t1 | awk '{printf "%f", $1 - $2}')
    unset _x_t1
    #_x_dt=$(echo $_x_dt | awk '{printf "%f", $1 + 86400 * 1001}') # test
    # only show dT for long-running commands
    # ${f%.*} = int(floor(f))
    (( ${_x_dt%.*} >= $_x_dt_min )) && {
        _x_dt_d=$((${_x_dt%.*} / 86400))
        _x_dt_s='' # init delta T string
        (( $_x_dt_d > 0 )) && \
            _x_dt_s="${_x_dt_s}${_x_dt_d} days + "
        # format time
        # %4N = four digits of nsec
        _x_dt_s="${_x_dt_s}$(date -u -d0+${_x_dt}sec '+%T.%4N')"
        PS1='rc = ${_x_rc}\ndT = ${_x_dt_s}\n\$ '
    } || {
        PS1='rc = ${_x_rc}\n\$ '
    }
    # set terminal title to terminal number
    printf "\033]0;%s\007" $(tty | sed 's|^/dev/\(pts/\)\?||')
}
trap '_x_before' DEBUG
PROMPT_COMMAND='_x_after'
PS1='\$ '

样本输出:

$ sleep 0.5
rc = 0
$ sleep 1
rc = 0
dT = 00:00:01.0040
$ sleep 1001d
rc = 0
dT = 1001 days + 00:00:00.0713
$ false
rc = 1
$ 
于 2018-12-09T16:23:59.600 回答
0

如果 somone 只想查看执行时间,请将此行添加到 bash_profile

trap 'printf "t=%s\n" $(date +%T.%3N)' DEBUG
于 2020-08-01T12:59:40.660 回答
0

基于Ville 的回答和perreal的时间转换功能,灵感来自zsh spaceship prompt的分时、分、秒版本。

我还添加了一个阈值变量,以便计时器仅显示长时间运行的命令。

提示输出

time_threshold=5;

function convert_secs {
    ((h=${1}/3600))
    ((m=(${1}%3600)/60))
    ((s=${1}%60))
    if [ $h -gt 0 ]; then printf "${h}h "; fi
    if [ $h -gt 0 ] || [ $m -gt 0 ]; then printf "${m}m "; fi
    if [ $s -gt 0 ]; then printf "${s}s "; fi
}

function timer_start {
    timer=${timer:-$SECONDS}
}

function timer_stop {
    timer_time=$(($SECONDS - $timer))
    
    if [ ! -z $timer_time ] && [ $timer_time -ge ${time_threshold} ]; then
        timer_show="took $(convert_secs $timer_time)"
    else
        timer_show=""
    fi

    unset timer
}

trap 'timer_start' DEBUG
PROMPT_COMMAND=timer_stop

PS1='\n\w ${timer_show}\n\\$ '

对于我的屏幕截图中的彩色输出:

bold=$(tput bold)
reset=$(tput sgr0)
yellow=$(tput setaf 3)
cyan=$(tput setaf 6)

PS1='\n${bold}${cyan}\w ${yellow}${timer_show}${reset}\n\\$ '
于 2020-10-25T14:27:26.857 回答