Submit Hint Search The Forums LinksStatsPollsHeadlinesRSS
14,000 hints and counting!

Use Growl to monitor long-running shell commands UNIX
While waiting for long-running shell commands to finish, I often switch to Mail or Safari. Here's how I made Bash notify me via Growl whenever one of those commands finishes. It's a combination of a clever bash script by glyf and Growl's growlnotify shell script. Install Growl, including the growlnotify shell script (found in the Extras directory). Next, download preexec.bash.txt [hints mirror] and save it under ~/.preexec.bash, as an invisible file in your home directory (in case you choose a different filename or location, make sure to adjust it in the following script).

Then add the following code to your ~/.bashrc:

. ~/.preexec.bash

# called before each command and starts stopwatch
function preexec () {
	export PREEXEC_CMD="$BASH_COMMAND"
	export PREEXEC_TIME=$(date +'%s')
}

# called after each command, stops stopwatch
# and notifies if time elpsed exceeds threshold
function precmd () {
	stop=$(date +'%s')
	start=${PREEXEC_TIME:-$stop}
	let elapsed=$stop-$start
	max=${PREEXEC_MAX:-10}
	
	if [ $elapsed -gt $max ]; then
		growlnotify -n "iTerm" -m "took $elapsed secs" ${PREEXEC_CMD:-Some Command}
	fi
}

preexec_install

Now whenever a shell command takes longer than 10 seconds to finish, Growl will notify you about it. To change the threshold temporarily, use export PREEXEC_MAX=20. To change it permanently, just change it in the code you just appended to ~/.bashrc.

[robg adds: I haven't tested this one.]
    •    
  • Currently 3.60 / 5
  You rated: 5 / 5 (5 votes cast)
 
[19,562 views]  

Use Growl to monitor long-running shell commands | 22 comments | Create New Account
Click here to return to the 'Use Growl to monitor long-running shell commands' hint
The following comments are owned by whoever posted them. This site is not responsible for what they say.
Use Growl to monitor long-running shell commands
Authored by: boredzo on Oct 11, '07 08:30:07AM

growlnotify isn't a shell-script. It's a full-fledged program written in Objective-C.

The technique is cool, though. ☺



[ Reply to This | # ]
Use Growl to monitor long-running shell commands
Authored by: skwerl on Oct 11, '07 08:37:44AM

Genius.



[ Reply to This | # ]
Use Growl to monitor long-running shell commands
Authored by: badcarl on Oct 11, '07 10:02:04AM

+1



[ Reply to This | # ]
Use Growl to monitor long-running shell commands
Authored by: ScottTFrazer on Oct 11, '07 02:19:56PM

This hint didn't work for me as suggested. When I created the ~/.bashrc and ~/.preexec_bash files, then restarted terminal, nothing happened. I verified that growlnotify was working, but I couldn't get anything to output.

I moved the .bashrc commands to /etc/bashrc and moved the preexec_bash portion to /etc/ as well (updating the reference in bashrc to point at the right place)

It works now, but doesn't grab the command name, reporting instead that "Some Command" took X seconds to finish. I tested with "sleep 20"



[ Reply to This | # ]
Use Growl to monitor long-running shell commands
Authored by: Red Phoenix on Oct 11, '07 08:02:16PM

I ended up putting the part it said to put in ~/.bashrc in to ~/.profile. If the .profile file exists, it ignores anything in .bashrc. I do have the same problem of it saying "Some Command" still.

--
Red Phoenix
Talamathi


[ Reply to This | # ]
Use Growl to monitor long-running shell commands
Authored by: nmerriam on Oct 11, '07 08:21:01PM

Great idea!

Don't forget to make .preexec.bash executable, just "chmod +x .preexec.bash"

unfortunately, I just get "-bash: preexec_install: command not found" when I start a terminal. the .preexec.bash seems to be fine, I see the function defined -- are we sure this works with the default bash setup and default terminal on Tiger?



[ Reply to This | # ]
Use Growl to monitor long-running shell commands
Authored by: kyngchaos on Oct 11, '07 08:42:57PM

I don't think sourced shell init files need to be executable. .bash_profile isn't. I didn't make .preexec.bash executable, and it works for me.

But I do have the unhelpful "Some command" message.



[ Reply to This | # ]
Use Growl to monitor long-running shell commands
Authored by: sapporo on Oct 12, '07 01:40:45AM
To debug the "Some Command" problem, you could start by adding a line to preexec() that displays the command it stores in the environment:
function preexec () {
	export PREEXEC_CMD="$BASH_COMMAND"
	echo "DEBUG: PREEXEC_CMD set to '$BASH_COMMAND'"
	export PREEXEC_TIME=$(date +'%s')
}
If that looks correct, then something goes wrong in precmd().

[ Reply to This | # ]
Use Growl to monitor long-running shell commands
Authored by: atr000 on Oct 11, '07 09:01:35PM

Amazing work here, especially the bash code for doing this. Even with how customized I've got bash to be, zsh seems to incorporate many of the great features that don't require scripts like this to be created.

A few thoughts:
1) Would it be possible to retain another PROMPT_COMMAND function? I have (had) a very nice newCMD to truncate the PWD on PS1 (the shell prompt), which required using the PROMPT_COMMAND variable?

2) Quickly, I did modify the ten second limit to much longer but have found that the growl popups from any command are annoying. I think putting in a white/black list scheme would help here since commands like make are optimal for something like this, while closing a tab after being connected for an hour via SSH is not.



[ Reply to This | # ]
Use Growl to monitor long-running shell commands
Authored by: sapporo on Oct 12, '07 01:56:54AM
1) Not unless you improve preexec.bash to allow this. It explicitly states:
"Note: this module requires 2 bash features which you must not otherwise be using: the "DEBUG" trap, and the "PROMPT_COMMAND" variable. preexec_install will override these and if you override one or the other this _will_ break".

2) Depending on your shell usage pattern, yes, there will be false positives (notifications you don't need or want). I thought about a white/blacklist approach, but it's too much hassle for my taste.

I like to keep stuff like that simple, and I don't mind the false positives. They do distract me for a second, but I still prefer that to having to decide in advance whether or not I'll be notified. Making that decision in advance each time I execute a shell command would take me more time than looking at a superfluous notification once in a while.

And while this setup works fine for me, I consider that a technique rather than a product, so go ahead and tweak it to your tastes.

[ Reply to This | # ]

Use Growl to monitor long-running shell commands
Authored by: stevev on Oct 12, '07 06:15:21AM

as for #1, i use a different technique which may be of use to you in this scenario. specifically, the steps below do not use PROMPT_COMMAND.

1. take the contents of your newPWD command and put them in an executable shell script somewhere in your path.

2. change PS1 to use something along the lines of "\$(<shell name here>)". for example, my PS1 is "\\h:\$(trim-pwd.bash)\$ \[\e]0;\H:\w\a\]" (as an aside, the escape sequence is there to change the title of terminal to the working directory)

and that's it. you now should have the path trimmed without using the PROMPT_COMMAND environment variable. i can post the contents of "trim-pwd.bash" if anyone needs it.

hth



[ Reply to This | # ]
Use Growl to monitor long-running shell commands
Authored by: Anonymous on Oct 11, '07 09:31:24PM
This seems like it could be annoying, triggering when you don't want it (Commands like "less" can run for over 10 seconds, and I couldn't care less when it exits)

Instead of blacklisting or whitelisting applications, I found a little script ages you call like:
gnotif gzip -9 somefile.tar

Then when it completes, it creates a growl notification. It's similar to the 'time' command, you prepend it when you want to know how long a command takes.

I've saved it to /usr/local/bin/ called 'gnotif'

#!/bin/sh
#  Runs script, and prints a notification with growl when it finishes
# Written sometime in 2006, posted 2007/08
# With Tips from Ranger Rick, Tim Bunce and Ruben Fonseca

# Run the command, including arguments with spaces
"$@"
status=$?

# decide which status to use
if [ "$status" == "0" ] ; then
    result="completed"
else
    result="FAILED ($status)"
fi

# decide which notifier we have
env growlnotify -h > /dev/null 2> /dev/null
has_growl=$?
env notify-send > /dev/null 2> /dev/null
has_libnotify=$?

# notify the user, growl or libnotify
if [ "$has_growl" == "0" ] ; then
    growlnotify -m "Script '$@' $result" -s "Background script notification" &
elif [ "$has_libnotify" == "0" ] ; then
    notify-send "Script '$@' $result" "Background script notification" &
fi

# exit with the original status
exit $status


[ Reply to This | # ]
Use Growl to monitor long-running shell commands
Authored by: Lutin on Oct 12, '07 04:34:56AM

I had implemented your solution before reading this hint.
Unfortunately, I often realize a command will take longer than expected once it's already running (like a slow server with wget...).

The idea in the hint is really great.
Each reader will pick what he prefers. And both hints aren't exclusive (but you might want to add some code to prevent a double notification).



[ Reply to This | # ]
Use Growl to monitor long-running shell commands
Authored by: 4ndrew on Oct 13, '07 02:23:00PM
Use Growl to monitor long-running shell commands
Authored by: dbingham on Oct 12, '07 08:05:05AM
For those of you with the "Some Command" problem:

On OS X 10.4, you can fix this by changing

export PREEXEC_CMD="$BASH_COMMAND"

in the preexec function in your bashrc to

export PREEXEC_CMD="$1"

Since the version of bash that ships with 10.4 doesn't support the BASH_COMMAND variable, you can depend on the value passed by the .preexec.bash.

Also, I changed the default text for the title to "Shell Command" instead of "Some Command" so that to a casual observer, the message seems more purposeful and less confused. You know...to each his own.

-dbingham

[ Reply to This | # ]
Use Growl to monitor long-running shell commands
Authored by: kyngchaos on Oct 12, '07 09:10:26AM

Ack! That works, but is not good. $1 is the WHOLE command, including all parameters. First, long commands can create a large notify box. Second, growlnotify tries to interpret any flags in that command - those parameters that start with a hyphen (this must be a bug). I tried quoting ${PREEXEC_CMD:-Some Command} but that didn't help.

How would you get the first word of $1, which would be just the command with no parameters?



[ Reply to This | # ]
Use Growl to monitor long-running shell commands
Authored by: dbingham on Oct 12, '07 09:48:53AM
Try:

export PREEXEC_CMD="$(echo $1 | awk '{print $1}')"

However, note that unless I'm mistaken, $BASH_COMMAND would include the entire command and all its arguments were it working. So, not sure if just getting the first word is entirely correct. I concur that this seems more like an issue with growlcode than with using $1 or $BASH_COMMAND.

Perhaps instead of just taking the first word, a better workaroud would be to just replace the dash with another character like tilde:

export PREEXEC_CMD="$(echo $1 | sed 's/-/~/g')"

Now, at a quick glance it still looks right.

-dbingham

[ Reply to This | # ]
Use Growl to monitor long-running shell commands
Authored by: kyngchaos on Oct 12, '07 11:53:36AM

that awk bit works. Thanks.

The full command may be what was originally intended. But whether that's appropriate or not gets back to my first point - long commands (I often run configures with a long stream of options) can either create a large growl box, unless limited in the growl prefs. In the end, all I need to see for a quick notification is the command and not all its parameters.



[ Reply to This | # ]
ZSH version
Authored by: Lutin on Oct 13, '07 06:41:10AM
I love this hint. Very good idea.

I adapted it to get it to work for zsh.
It seems to require a lot less code.
It works for me, but it might due to my particular config.
If it doesn't work for you, please let me know.

How to install:
Just add this two functions in your ~/.zshrc:


# Before a command execution
preexec() {
	# Define timer and cmd for growl notification
	export PREEXEC_TIME=$(date +'%s')
	export PREEXEC_CMD="the cmd: $1"
}

# After a command execution
precmd() {

	# Growl notify
	# Time after which trigger a growl notification
	DELAY_AFTER_NOTIFICATION=1
	
	# Get the start time, or set it to now if not set
	start=${PREEXEC_TIME:-`date +'%s'`}

	stop=$(date +'%s')
	
	let elapsed=$stop-$start
	
	if [ $elapsed -gt $DELAY_AFTER_NOTIFICATION ]; then
		growlnotify -n "Terminal" -m "took $elapsed secs" ${PREEXEC_CMD:-Some command}
	fi
}

Congratulations to sapporo for the original idea.
Enjoy!


[ Reply to This | # ]
ZSH version
Authored by: Lutin on Oct 13, '07 06:43:50AM

And change the variable DELAY_AFTER_NOTIFICATION to match your own needs (first line of code of function precmd).



[ Reply to This | # ]
Use Growl to monitor long-running shell commands
Authored by: 4ndrew on Oct 13, '07 02:20:41PM
I'm using exclude list of parameters instead of "gnotif-method":

My ~/.profile file:


export PREEXEC_EXCLUDE_LIST="mc less bash"

. ~/.preexec.bash

# called before each command and starts stopwatch
function preexec () {
	export PREEXEC_CMD=`echo $1 | awk '{ print $1; }'`
	export PREEXEC_TIME=$(date +'%s')
}

# called after each command, stops stopwatch
# and notifies if time elpsed exceeds threshold
function precmd () {
	stop=$(date +'%s')
	start=${PREEXEC_TIME:-$stop}
	let elapsed=$stop-$start
	max=${PREEXEC_MAX:-10}
	
	for i in ${PREEXEC_EXCLUDE_LIST:-}; do
	  if [ "x$i" = "x$PREEXEC_CMD" ]; then
	    max=999999;
	    break;
	  fi
	done
	
	if [ $elapsed -gt $max ]; then
		growlnotify -n "iTerm" -m "took $elapsed secs" ${PREEXEC_CMD:-Shell Command}
	fi
}

preexec_install


[ Reply to This | # ]
Use Growl to monitor long-running shell commands
Authored by: sentience on Nov 07, '08 12:22:32AM
Finally got this to work on Leopard after some fiddling. First, as a previous commenter noted, if you have a .profile file in your home directory, bash will ignore a .bashrc file. You must put the script into your .profile instead. Second, the script as written does not deal well with certain commands because the scripter left out some quotes. Here is the script as it works for me in my .profile file:
. ~/.preexec.bash

# called before each command and starts stopwatch
function preexec () {
	export PREEXEC_CMD="$BASH_COMMAND"
	export PREEXEC_TIME=$(date +'%s')
}

# called after each command, stops stopwatch
# and notifies if time elpsed exceeds threshold
function precmd () {
	stop=$(date +'%s')
	start=${PREEXEC_TIME:-$stop}
	let elapsed=$stop-$start
	max=${PREEXEC_MAX:-10}

	if [ $elapsed -gt $max ]; then
		growlnotify -n "Terminal" -m "took $elapsed secs" "${PREEXEC_CMD:-Terminal Command}"
	fi
}

preexec_install


[ Reply to This | # ]