4

Some programs will exit when their STDIN is closed. Those that do this work nicely with Erlang/Elixir supervision via "ports".

For those that don't, the Elixir docs suggest this wrapper script:

#!/bin/bash
# wrapper.sh
"$@" &
pid=$!
while read line ; do
  :
done
kill -KILL $pid

This allows calling wrapper.sh my_script arg1 arg2. wrapper.sh starts and backgrounds the specified program, and when wrapper.sh's STDIN is closed, its blocking read finishes and it terminates the backgrounded process.

This approach has a downside, however; if the backgrounded process terminates for some other reason, wrapper.sh doesn't notice, and therefore neither does Erlang/Elixir.

I'd like to modify this script to do the following:

In a loop:

  • If STDIN is closed, kill $pid
  • If $pid is dead, exit
  • Otherwise, sleep briefly

Can anyone suggest a way to do this?

1

If your shell is dash/busybox, ksh93 or zsh, you could set a trap on SIGCHLD:

#! /bin/sh
trap exit CHLD
"$@" &
while read line; do :; done
kill $!
wait

This will exit as soon as the "$@" & process has exited or the read has got an EOF.

But bash has a nasty read built-in, which is restarted in place when interrupted by a signal, so something clunkier is needed. You can arrange for the background process to send a terminating signal to its parent upon exiting:

#! /bin/sh
{ "$@"; kill $$; } &
while read line; do :; done
pkill -P $!
kill $!
wait

This will have to waste another process to wait for the "$@" command, and use the -P (parent pid) selector of pkill since $! no longer refer to the "$@" command, but to its parent. The extra kill $! is still needed in case the "$@" happens to be a built-in. Notice that there's no job control in scripts; all processes (including those started with &) run in the same process group.

You can use p/kill -KILL everywhere if a TERM signal isn't enough.

Another, even clunkier workaround that only works with newer (>= 4.0) versions of bash is to use the nonstandard -t (timeout) option of read and exploit the fact that read will return 1 upon EOF and a status > 128 upon timeout:

#! /bin/bash
"$@" &
while :; do
        read -t 1 line
        case $? in
        0)      ;;
        1)      kill $!; wait; exit;;
        *)      kill -0 $! || exit;;
        esac
done 2>/dev/null

This somehow seems to work with mksh, too.

  • This is helpful! Note that the different return values for read on timeout vs EOF match what I see in help read in Bash 5.0.2, but in Bash 3.2 there is no such distinction. – Nathan Long Jan 30 at 15:32
3

You can check if $pid is dead like so:

while kill -0 $pid >/dev/null; do
    # $pid is still alive
done

# $pid is dead or you lack permissions to send signals to it

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.