A recent Slashdot post on good UNIX habits reminded me of something I really want as far as shell job control: the ability to run a command after another completes.
“Wait a second dude,” you say, “just use a semicolon!”
$ wget http://example.com/bigfile.zip ; mail -s 'done downloading' adamm
…and I wholeheartedly agree. But what if you started that ‘wget’ before you thought to notify yourself when the download completed?
How To Run Y After X
We know how to schedule things in advance and perform one-off jobs, but perhaps a new utility is needed to do Y just after X completes, in the case where X is already running.
For the sake of this post, “completion” is the point when a process‘s PID is no longer visible in /proc.
Idea #1
strace allows attaching to another running process by referencing a PID. My first idea is to strace the pid of the program to wait to finish, then continue.
For example:
$ wget http://example.com/pkg-1.0.rpm & [1] 25120 $ strace -qp 25120 -e trace=none ; rpm -ihv pkg-1.0.rpm
Bummer that I had to re-type the PID. We’ll clean up that detail later.
Nevermind the fact that I could’ve simply done
$ rpm -ihv http://example.com/pkg-1.0.rpm
Ideas #2,3,4,5
All the other ideas involve watching for changes in files belonging to the program which you want to run something after, ie: stuff in /proc/PID. Please forgive the brainstormyness of this section.
Idea #2: Use poll(2)/select(2)?? No idea if this is possible. Probably not… these functions only seem useful with sockets. Perhaps the paradigm can still be used. With inotify, poll/select can be used! See idea #3.
Idea #3: Use inotify (C code).
Idea #4: Use gamin (C code).
Idea #5: Use gamin (Python code).
Conditional Execution
I haven’t looked into it, but I imagine ptrace(2) would allow one to look for a call to exit_group(2) (this happens to be the last thing I see called for any process I strace), check the exit code, and do some behavior based on what it is.
This could also be done with some nasty-looking shell tricks:
$ ./time_consuming_process & [1] 30160 $ result=`strace -e trace=exit_group -qp 30160 2>&1 \ > | sed -e 's/^exit_group(\([0-9]\)).*$/\1/' $ [ $result -eq 0 ] && echo Time consuming process SUCCEEDED.
Shell Integration
Before this is going to catch on, I’m going to need:
- Mass appeal
- A catchy name
- A simple, elegant implementation
For the name of this utility, the best I could come up with is “after”. Not too catchy, but least it gives some idea of what it does.
Perhaps the mass appeal will follow with a simple and elegant implementation within the bash shell. Imagine the following (assuming ‘after’ returned whatever exit code was returned by the process it attached to):
$ ./time_consuming_process & [1] 30160 $ after time_consuming_process && echo Time consuming process SUCCEEDED.
or even:
$ ./time_consuming_process & [1] 30160 $ after %1 && echo Time consuming process SUCCEEDED.
Here’s some possible bash help/usage documentation:
after: after [pid | job_spec] command [args...]
Run command after another process completes. Returns the exit value of
the waited-for process.
The end. Whaddya think?
UPDATE
Andrew thinks that bash’s ‘wait’ builtin does exactly what I want, and I agree! Thanks, Andrew!
Um… what about the wait command in bash?
wait: wait [n]
Wait for the specified process and report its termination status. If
N is not given, all currently active child processes are waited for,
and the return code is zero. N may be a process ID or a job
specification; if a job spec is given, all processes in the job’s
pipeline are waited for.
Comment by Andrew Sweger — December 19, 2006 @ 10:02 pm PDT
Awesome, thanks!
That’ll do quite nicely.
Comment by adam — December 19, 2006 @ 10:11 pm PDT