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.