PS1 prompt to show elapsed time

One way to do it would be to use the PROMPT_COMMAND feature of bash to execute code that modifies PS1. The function below is an updated version of my original submission; this one uses two fewer environment variables and prefixes them with "_PS1_" to try to avoid clobbering existing variables.

prompt_command() {
  _PS1_now=$(date +%s)
  PS1=$( printf "\[\e[0;32m\]%02d:%02d:%02d \W>\[\e[1;37m\] " \
           $((  ( _PS1_now - _PS1_lastcmd ) / 3600))         \
           $(( (( _PS1_now - _PS1_lastcmd ) % 3600) / 60 )) \
           $((  ( _PS1_now - _PS1_lastcmd ) % 60))           \
       )
  _PS1_lastcmd=$_PS1_now
}
PROMPT_COMMAND='prompt_command'
_PS1_lastcmd=$(date +%s)

Put that into your .bash_profile to get things started up.

Note that you have to type pretty quickly to get the sleep parameter to match the prompt parameter -- the time really is the difference between prompts, including the time it takes you to type the command.

00:00:02 ~> sleep 5   ## here I typed really quickly
00:00:05 ~> sleep 3   ## here I took about 2 seconds to enter the command
00:00:10 ~> sleep 30 ## more slow typing
00:01:35 ~>

Late addition:

Based on @Cyrus' now-deleted answer, here is a version that does not clutter the environment with extra variables:

PROMPT_COMMAND='
    _prompt(){
        PROMPT_COMMAND="${PROMPT_COMMAND%-*}-$SECONDS))\""
        printf -v PS1 "\[\e[0;32m\]%02d:%02d:%02d \W>\[\e[1;37m\] " \
                      "$(($1/3600))" "$((($1%3600)/60))" "$(($1%60))"
    }; _prompt "$((SECONDS'"-$SECONDS))\""

Extra late addition:

Starting in bash version 4.2 (echo $BASH_VERSION), you can avoid the external date calls with a new printf format string; replace the $(date +%s) pieces with $(printf '%(%s)T' -1). Starting in version 4.3, you can omit the -1 parameter to rely on the "no argument means now" behavior.


PS1[3]=$SECONDS
PS1='${PS1[!(PS1[1]=!1&(PS1[3]=(PS1[2]=$SECONDS-${PS1[3]})/3600))
   ]#${PS1[3]%%*??}0}$((PS1[3]=(PS1[2]/60%60),  ${PS1[3]})):${PS1[1
   ]#${PS1[3]%%*??}0}$((PS1[3]=(PS1[2]%60),     ${PS1[3]})):${PS1[1
   ]#${PS1[3]%%*??}0}$((PS1[3]=(SECONDS),       ${PS1[3]})):'$PS1

This handles the formatting by calculation - so, while it does expand several times, it doesn't do any subshells or pipes.

It just treats $PS1 as an array and uses the higher indices to store/calculate any/all necessary state between prompts. No other shell state is affected.

00:00:46:[mikeserv@desktop tmp]$
00:00:01:[mikeserv@desktop tmp]$
00:00:00:[mikeserv@desktop tmp]$
00:00:01:[mikeserv@desktop tmp]$
00:00:43:[mikeserv@desktop tmp]$ sleep 10
00:00:33:[mikeserv@desktop tmp]$ sleep 10
00:00:15:[mikeserv@desktop tmp]$
00:00:15:[mikeserv@desktop tmp]$
00:00:02:[mikeserv@desktop tmp]$
00:02:27:[mikeserv@desktop tmp]$

I can break it down a little maybe...

First, save the current value of $SECONDS:

PS1[3]=$SECONDS

Next, define $PS1[0] to be self-recursive in a way that will always set the right values to $PS1[1-3] while simultaneously self-referencing. To get this part you have to consider the order in which shell-math expressions are evaluated. Most importantly, shell-math is always the last order of business for shell-math. Before all else, the shell expands values. In this way you can reference an old-value for a shell-variable in a math expression after assigning it by using $.

Here is a simple example first:

x=10; echo "$(((x+=5)+$x+x))" "$x"

40 15

The shell will evaluate that statement by first substituting the value of $x wherever the $ dollar-sign reference is used, and so the expression becomes:

(x+=5)+10+x

...then the shell adds 5 to the value of $x and afterward expands the whole expression to x+10+x, while retaining only the actually assigned value in the reference variable. And so the math expression's expanded value is 40, but the ultimate value of $x is 15.

That is largely how the $PS1 equation works as well, except that there is a further level of math expansion/evaluation exploited in the array indices.

PS1='${PS1[!(PS1[1]=!1&(...))]#...}...'

I'm not really sure why I chose to use PS1[1]=!1 there - I guess it was probably just silly aesthetics - but this assigns 0 to $PS1[1] while expanding it for parameter substitution. The value of a bitwise AND for 0 and anything else will always be 0, but it doesn't short-circuit as a boolean && does when the left-most primary is 0 and so the parenthetical expression still gets evaluated every time. That is important, of course, because that first elipsis is where the initial values for $PS1[2,3] are set.

Anyway, $PS1[1] is here assured to be 0 even if it is tampered w/ between prompt draws. Within the parentheses there...

PS1[3]=(PS1[2]=$SECONDS-${PS1[3]})/3600

...$PS1[2] is assigned the difference of $PS1[3] and $SECONDS, and $PS1[3] is assigned the quotient of that value and 3600. All values are here initialized. And so:

${PS1[1]#${PS1[3]%%*??}0}

...if there are at least two digits in $PS1[3] then the inner expansion there is null, and because we know $PS1[1] is 0 then if $PS1[3] can be substituted away to nothing, so also is $PS1[1] else it is expanded to its value. In this way only single digit values for each iteration of $PS1[3] assignments will expand a leading zero, and $PS1[3] is itself expanded modulo 60 immediately thereafter while being concurrently assigned the next successively smaller value for each of hours, minutes, seconds.

Rinse and repeat, until the last iteration when $PS1[3] is overwritten w/ the current value of $SECONDS so that it may be compared to $SECONDS once more when the prompt is next drawn.

Tags:

Bash

Prompt