Sound when bash command is ending Sound when bash command is ending shell shell

Sound when bash command is ending


I had the same question and thanks to @rici's answer I was able to come up with a working solution. You can join PROMPT_COMMAND into a single line if you want, or leave it like this:

trap '_T=${_T:-$SECONDS}' DEBUGPROMPT_COMMAND='    ((SECONDS - _T > 10)) &&        { play ~/done.wav &disown;}&>/dev/null;    unset _T'

This combination of settings will play ~/done.wav whenever a command-line takes more than 10 seconds to complete. It will not play the sound just by leaving your terminal idle.

On OS X the command to play a sound is afplay and a few sounds can be found in /System/Library/Sounds

But the version above cannot tell between long-running batch commands, for which we want a sound, and interactive ones, such as your choice of terminal editor, man pages, |less, and such.

Here's a version that does tell, 99% of the time, by checking the last commandline against a regexp with one's list of common interactive programs or pipes:

trap '_T=${_T:-$SECONDS}' DEBUGPROMPT_COMMAND='    if ((SECONDS - _T > 10)); then         l=$(history 1);        [[ ! ${l:7} =~ (^vim|^man|^ssh|less$) ]] &&         { play ~/done.wav &disown;}&>/dev/null;    fi;    unset _T'

You should adjust the regexp to taste. Again, you can join PROMPT_COMMAND into a single line if you want, I've left the ; where they should be on a single-liner.

This is still efficient, because:

  • the shell doesn't spawn any external process while evaluating either hook (except for the obvious sound playing, which is launched in the background);
  • the DEBUG hook is very fast; I believe it doesn't even access the "magic variable" $SECONDS (which probably forces a syscall to get the current time) more than twice per command-line, in the first DEBUG and in PROMPT_COMMAND to compare the values;
  • the history check and regexp is only done if the command took more than N seconds.

How it works

To understand how it works, you can set these debug prints:

trap 'echo DEBUG' DEBUGPROMPT_COMMAND='echo PROMPT_COMMAND'

and then pay attention to when they are called:

PROMPT_COMMAND$ sleep 2; sleep 2DEBUG  (1st sleep takes its time)DEBUG  (2nd sleep takes its time)DEBUGPROMPT_COMMAND$ (user waits before pressing enter)DEBUGPROMPT_COMMAND$

In light of this, my settings above:

  • unset _T whenever a prompt is shown to the user—at the end of $PROMPT_COMMAND;
  • if _T is unset, which happens in the first DEBUG of every command-line, right after you press enter; then set it to the current time (using $SECONDS which is more efficient than launching an external date process);
  • at the next prompt, compare _T with the current time and play the sound if needed, launching the play command in the background and discarding any output (including [1] PID and other job-related info.)


You can build a wrapper to do this:

#!/bin/bash# Play sound if the command takes longer than this many seconds.alert_sec=60# Your system may have a different media player.# Use "man -k player" to see what's installed.play=/usr/bin/play# Use any sound file here. Look under /usr/share/sounds.sound=/usr/share/sounds/gnome/default/alerts/bark.ogg# Start time, in seconds since the epoch.start=$(date +%s)# Send the remainder of the command line to bash."$@"end=$(date +%s)# Calculate how long the command ran.run_sec=$((end - start))# Decide whether to alert.if ((run_sec > alert_sec)); then    $play $sound >&/dev/nullfi

Let's call the script notify. Copy it to a directory in your $PATH so bash will find it, and make it executable with chmod +x notify. Now you can execute commands like this:

notify ls -l /var/log  # Shorter than 60 seconds, so no alarm.notify sleep 70        # Longer than 60 seconds, so alarm.

If you'd also like a visual alert, look into notify-send.


Here's an imperfect solution, which you can put in your bash profile:

_PREVT=$(date +%s)PROMPT_COMMAND='[[ -n $_PREVT ]] && ((_PREVT<$(date +%s)-60)) && mplayer /usr/share/sounds/pop.wav;_PREVT=$(date +%s)'

It's imperfect because it measures the time between command prompts, rather than since the current command started executing. So if you leave your terminal session to do something else, the next command will trigger the sound.

If that irritates you too much, you could try to reset the timer by putting _PREVT=$(date +%S) into a DEBUG trap, but that might cause it to be reset too often if your long-running commands are single-line bash-scripts.