9

I currently use this to display the current time in my bash prompt:

PS1=\[\e[0;32m\]\t \W>\[\e[1;37m\]

20:42:23 ~>

Is it possible to display the elapsed time since the previous prompt? Such as:

00:00:00 ~> sleep 10
00:00:10 ~> sleep 20
00:00:20 ~>

This has nothing in common with Is it possible to change the PS1 periodically by a script in the background?

  • No, there is no answer in that post and I expect the prompt to change only when a new prompt is displayed. – TeasingDart Dec 29 '15 at 20:50
  • There is not really a feasible way to do what you ask, no. – DopeGhoti Dec 29 '15 at 20:55
  • 1
    It's feasible if the displayed value is static (OP's question did not appear to allow that). Elapsed time could be maintained by saving the epoch time of the previous time in a shell variable. Implementing it seems a lot of work though (an hour or so -- perhaps someone will provide a simpler solution than what I have in mind). This question would be helpful. – Thomas Dickey Dec 29 '15 at 21:05
9

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.

  • This is real close. It works when I copy/paste from a bash prompt, but when I tried to add it to my .bashrc it prints "1451424431: command not found" – TeasingDart Dec 29 '15 at 21:26
  • maybe a little too much got copy/pasted? – Jeff Schaller Dec 29 '15 at 21:30
  • This last version worked, exactly what I wanted! I think it had something to do with my trap to set the text color after the prompt. Thank you. – TeasingDart Dec 29 '15 at 21:32
  • when you reset $SECONDS it ceases to track the time since the shell started, – mikeserv Dec 30 '15 at 15:50
  • 1
    @chepner - well, sure, but it doesn't keep the shell's time ever again. don't get me wrong - i upvoted this because it's a good answer - but i think that redefining an interactive shell's $SECONDS for every prompt is likely to elicit unexpected behaviors. any other shell function which might use it for any reason associated with evaluating run-time will misbehave. – mikeserv Dec 31 '15 at 2:19
4
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.

0

The best solution I found so far is this: https://github.com/jichu4n/bash-command-timer

Which prints [ 1s011 | May 25 15:33:44 BST ] aka the elapsed time on the right hand side after the executed command, so it doesn't clutter you PS1.

The whole string and time format is configurable. Even the color and the precision is configurable. I know it might be a bit much for some minimalist out there, but it's pretty cool.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.