2

I'm trying to launch a couple of sub-programs from a bash script and then wait on either of them to quit before quitting the other and exiting the script.

CMD1PID=
CMD2PID=

exit_trap () {
    echo "exit trap: killing jobs..." 
    sudo kill $CMD1PID $CMD2PID
    exit
}

set -bm
trap exit_trap EXIT INT TERM

sudo cmd1 --args &
CMD1PID=$?

sudo cmd2 --args &
CMD2PID=$?

wait $CMD1PID $CMD2PID

(For reference, this is GNU bash, version 4.2.37(1)-release (arm-unknown-linux-gnueabi) on Debian Weezy)

Unfortunately, this doesn't seem to work properly. I've tried several variants, such as:

  • using curly braces around the commands and explicitly killing the shell process afterwards, ie { cmd1 --args ; kill -USR1 $$ ; } & or similar, and then trapping on USR1
  • using kill 0 to kill the still-running commands at exit
  • trapping CHLD - this has the disadvantage that I can't sleep between the commands if I need to, which I might eventually need to do.

...but the issues I tend to run into are:

  • Killing one of the commands doesn't cause the parent Bash process to exit and/or kill the other command
  • Killing the parent Bash process doesn't cause both commands to be killed

I suspect that perhaps my requirement to use sudo is tripping me up, as other folks in other scripts seem to be having good luck with similar scripts. Is there a good way of doing what I'm trying to do in bash?

1
  • trapping CHLD does not mean you can't sleep. It just means that to sleep and catch the signal in a timely fashion you must do sleep $n & wait instead of simply sleep $n Oct 12, 2019 at 1:49

2 Answers 2

3

You have two problems: You need to identify when any of the interesting children is done, and you need to kill all of them.

The first means you cannot use the "wait" builtin, because that will wait for all children (or all of the listed ones, if any), not for any. Your USR1-based suggestion works well for that (and for EXIT).

The second is much harder: Because you're using sudo, you don't actually have permission to send a signal to the processes; to send a signal to a process, either your userid must match, or you must be root.

You could try to use filesystem- or filehandle-based signaling or something like that, but frankly, that's going to be much more understandable and readable and maintainable in some other scripting language (python, perl, whatever), and even then it's overly complicated. You're probably better off running the entire script under the target user.

0

If I may suggest a UNIX-style “divide a complex task into smaller tasks”, I would start by solving the sudo issue, so that you’re left with a simpler “exiting a Bash script when a child quits” problem, where your shell is the direct parent of the children.

#!/bin/sh

exec sudo sh -c '
  exit_trap() {
     kill # pids
  }
  trap exit_trap SIGNALS
  # run children &
  wait # pids
'

Note that at this point, the script is basically a sudo-izing wrapper to the inner script in single quotes. I’m not sure it’s worth it, but maybe you have additional code to run before or after (in which case you want to remove exec) the sudo invocation, which makes the weirdness worthwhile.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .