6

What I am trying to achieve here is, via script I'm running 3 different custom application in wait simultaneously, if any application get exit, give alert via notify or print exit code.

System in use: Centos 6.8

5 Answers 5

5

The wait command in bash (4.3 and newer) has a -n option:

If the -n option is supplied, wait waits for any job to terminate and returns its exit status.

This means that you may do

command1 &
command2 &
command3 &

wait -n
printf 'One command exited with exit code %d\n' "$?"
5
  • 1
    Thanks for the answer, but -n option not working. 'wait: -n: invalid option' wait: usage: wait [id]
    – Rakesh.N
    Commented May 26, 2017 at 8:22
  • @Rakesh.N You tagged the question with tho bash tag, but it appears as if you're not running bash (or you are running an older version of bash). Could you please verify that you are in fact using a recent version of bash?
    – Kusalananda
    Commented May 26, 2017 at 8:24
  • running bash version 4.1.2
    – Rakesh.N
    Commented May 26, 2017 at 8:33
  • 3
    According to http://centos-packages.com/7/package/bash/, even CentOS/RHEL 7 has only Bash 4.2, and wait -n was added in 4.3-alpha (http://wiki.bash-hackers.org/scripting/bashchanges)
    – ilkkachu
    Commented May 26, 2017 at 8:36
  • 1
    @ilkkachu ... 3 years ago.
    – Kusalananda
    Commented May 26, 2017 at 8:37
4

You can use wait -n to wait for a child to exit, then test each child to if they are still running with kill -0, to see which one just exited, like this:

for f in 15 10 15; do
    sleep $f &
    PIDS+="$! "
done
wait -n
for f in $PIDS; do
    if ! kill -0 $f 2> /dev/null; then
        echo $f
    fi
done

wait -n only returns the exit status of the child, not which PID it was.

2

I thought about Bash's wait -n, but it doesn't let you know which child process exited. How about a simple Perl script?

#!/usr/bin/perl

use strict;
use warnings;
use POSIX ":sys_wait_h";

sub spawn(@) {
    my $pid = fork();
    die "fork: $!" if not defined $pid;
    if ($pid == 0) {
        exec @_ or die "exec: $!";
    }
    return $pid;
}

# Commands to run
my $p1 = spawn '/bin/bash -c "sleep 6; kill $$"';
my $p2 = spawn '/bin/bash -c "sleep 4; exit 4"';

print "spawned PIDs $p1 and $p2\n";

while ((my $child = waitpid(-1, 0)) > 0) {
    my $code = $? >> 8;
    my $status = $? & 0xff;
    printf "child %d finished with exit code %d (status/sig %d)\n", $child, $code, $status;
}
1
  • 1
    explaination will do help, not much into perl.
    – Rakesh.N
    Commented May 26, 2017 at 8:35
1

Unfortunately even when using kill -0 after wait -n to see which processes are still running, it's impossible to reliably get exit codes. This is because (a) if you find that 2 or more processes have exited, then which process's exit code was returned by wait -n? and (b) there's now no longer any way to get the other process's exit code (because it may have exited between the time when wait -n returned, and when you ran kill -0 to check on it - and you can't wait for it anymore because it's already gone). kill -0 has the additional problem of pid reuse, ie. after the process endded, some other process might have come along and been assigned the same pid. This script illustrates the problem:

#!/bin/bash

function workload {
    # Pretend workload that waits a bit and then returns a random result
    sleep 1
    ret=$(( $RANDOM % 200 ))
    echo "$1 process returning $ret"
    return $ret
}

workload First &
pid1=$!
workload Second &
pid2=$!

wait -n
rc=$?

pid1_running=n
if kill -0 $pid1 2>/dev/null; then
    pid1_running=y
fi

pid2_running=n
if kill -0 $pid2 2>/dev/null; then
    pid2_running=y
fi

declare -p pid1_running pid2_running rc
if [ $pid1_running = y -a $pid2_running = y ]; then
    echo "At least one of the processes has ended, returning $rc, but no idea which (pid reuse)"
elif [ $pid1_running = y -a $pid2_running = n ]; then
    echo "First process ended and returned $rc"
elif [ $pid1_running = n -a $pid2_running = y ]; then
    echo "Second process ended and returned $rc"
else
    echo "Both processes have ended, one of which returned $rc, but no idea which"
fi
$ bash wait-n-problem
First process returning 199
Second process returning 152
declare -- pid1_running="n"
declare -- pid2_running="n"
declare -- rc="199"
Both processes have ended, one of which returned 199, but no idea which

The way I worked around this was to use a sub-shell to run the workload, and capture the resulting exit code into a temporary file. Then, just using wait -n to signal that one (or more) of the processes has exited (ie. ignoring the exit code of wait -n). This ends up being more reliable, although a little clunkier. (It should also be possible to use an fd instead of temp files, and so avoid needing a functional writable filesystem, but this would complicate things a fair bit.)

#!/bin/bash

function workload {
    # Pretend workload that waits a bit and then returns a random result
    sleep 1
    ret=$(( $RANDOM % 200 ))
    echo "$1 process returning $ret"
    return $ret
}

pid1_rcfile="$(mktemp)"
pid2_rcfile="$(mktemp)"
trap 'rm -f "$pid1_rcfile" "$pid2_rcfile"' EXIT

( trap - EXIT; workload First; echo $? > "$pid1_rcfile"; ) &
( trap - EXIT; workload Second; echo $? > "$pid2_rcfile"; ) &

wait -n

pid1_rc="$(<"$pid1_rcfile")"
pid2_rc="$(<"$pid2_rcfile")"

pid1_running=n
if [ "$pid1_rc" = "" ]; then
    pid1_running=y
fi

pid2_running=n
if [ "$pid2_rc" = "" ]; then
    pid2_running=y
fi

declare -p pid1_running pid2_running pid1_rc pid2_rc
if [ $pid1_running = y -a $pid2_running = y ]; then
    echo "This can only happen if one of the sub-shells has been forcibly killed (before it could write to the rcfile)"
elif [ $pid1_running = y -a $pid2_running = n ]; then
    echo "First process ended and returned $pid1_rc"
elif [ $pid1_running = n -a $pid2_running = y ]; then
    echo "Second process ended and returned $pid2_rc"
else
    echo "Both processes have ended, first process returned $pid1_rc, second process returned $pid2_rc"
fi
$ bash wait-n-workaround
First process returning 183
Second process returning 185
declare -- pid1_running="n"
declare -- pid2_running="n"
declare -- pid1_rc="183"
declare -- pid2_rc="185"
Both processes have ended, first process returned 183, second process returned 185
1

With bash 5.1 or newer, you can do:

while
  wait -np id
  status=$?
  [ -n "$id" ]
do
  echo "process of ID $id exited with status $status"
done
bash-5.2$ help wait
wait: wait [-fn] [-p var] [id ...]
    Wait for job completion and return exit status.

    Waits for each process identified by an ID, which may be a process ID or a
    job specification, and reports its termination status.  If ID is not
    given, waits for all currently active child processes, and the return
    status is zero.  If ID is a job specification, waits for all processes
    in that job's pipeline.

    If the -n option is supplied, waits for a single job from the list of IDs,
    or, if no IDs are supplied, for the next job to complete and returns its
    exit status.

    If the -p option is supplied, the process or job identifier of the job
    for which the exit status is returned is assigned to the variable VAR
    named by the option argument. The variable will be unset initially, before
    any assignment. This is useful only when the -n option is supplied.

    If the -f option is supplied, and job control is enabled, waits for the
    specified ID to terminate, instead of waiting for it to change status.

    Exit Status:
    Returns the status of the last ID; fails if ID is invalid or an invalid
    option is given, or if -n is supplied and the shell has no unwaited-for
    children.

You must log in to answer this question.

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