How can the last command's wall time be put in the Bash prompt?

后端 未结 14 2660
無奈伤痛
無奈伤痛 2020-12-04 05:16

Is there a way to embed the last command\'s elapsed wall time in a Bash prompt? I\'m hoping for something that would look like this:

[last: 0s][/my/dir]$ sl         


        
相关标签:
14条回答
  • 2020-12-04 05:57

    A version with split hours, minutes and seconds inspired by the zsh spaceship prompt, based on Ville's answer and this time conversion function by perreal.

    I also added a threshold variable so that the timer only displays for long running commands.

    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\\$ '
    

    For the coloured output in my screenshot:

    bold=$(tput bold)
    reset=$(tput sgr0)
    yellow=$(tput setaf 3)
    cyan=$(tput setaf 6)
    
    PS1='\n${bold}${cyan}\w ${yellow}${timer_show}${reset}\n\\$ '
    
    0 讨论(0)
  • 2020-12-04 05:59

    Another very minimal approach is:

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

    This shows the number of seconds since the last simple command was started. The counter is not reset if you simply hit Enter without entering a command -- which can be handy when you just want to see how long the terminal has been up since you last did anything in it. It works fine for me in Red Hat and Ubuntu. It did NOT work for me under Cygwin, but I'm not sure if that's a bug or just a limitation of trying to run Bash under Windows.

    One possible drawback to this approach is that you keep resetting SECONDS, but if you truly need to preserve SECONDS as the number of seconds since initial shell invocation, you can create your own variable for the PS1 counter instead of using SECONDS directly. Another possible drawback is that a large seconds value such as "999999" might be be better displayed as days+hours+minutes+seconds, but it's easy to add a simple filter such as:

    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)) # '
    

    This translates "999999" into "11days,13:46:39". The sed at the end changes "1days" to "1day", and trims off empty leading values such as "0days,00:". Adjust to taste.

    0 讨论(0)
  • 2020-12-04 06:02

    Using your replies and some other threads, I wrote this prompt which I want to share with you. I took a screenshot in wich you can see :

    • White : Last return code
    • Green and tick mark means success (return code was 0)
    • Red and cross mark means error (return code was >0)
    • (Green or Red) : Last command execution time in parenthesis
    • (Green or Red) : Current date time (\t)
    • (Green if not root, Red if root) : the logged username
    • (Green) : the server name
    • (Blue) : the pwd directory and the usual $

    Here is the code to put in your ~/.bashrc file :

    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'
    
    0 讨论(0)
  • 2020-12-04 06:02

    I took the answer from Ville Laurikari and improved it using the time command to show sub-second accuracy:

    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]$ '
    

    Of course this requires a process to be started, so it's less efficient, but still fast enough that you wouldn't notice.

    0 讨论(0)
  • 2020-12-04 06:09

    I found that trap ... DEBUG was running every time $PROMPT_COMMAND was called, resetting the timer, and therefore always returning 0.

    However, I found that history records times, and I tapped into these to get my answer:

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

    It's not perfect though:

    • If history doesn't register the command (e.g. repeated or ignored commands), the start time will be wrong.
    • Multi-line commands don't get the date extracted properly from history.
    0 讨论(0)
  • 2020-12-04 06:12

    Here's my take on Thomas'

    uses date +%s%3N to get milliseconds as base unit, simplified following code (less zeros)

    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' 
    

    Then add $t_show to your PS1

    0 讨论(0)
提交回复
热议问题