Logiciel Libre

December 19, 2006

Run Y After X

Filed under: Default — Tags: — adam @ 8:28 pm CST

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:

  1. Mass appeal
  2. A catchy name
  3. 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!

2 Comments »

  1. 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 CST

  2. Awesome, thanks!

    That’ll do quite nicely.

    Comment by adam — December 19, 2006 @ 10:11 pm CST

RSS feed for comments on this post.

Leave a comment

Powered by WordPress