No. What you can do is write a loop with kill -0 $PID. If this call fails ($? -ne 0), the process has terminated (after your normal kill):

while kill -0 $PID; do 
    sleep 1
done

(kudos to qbolec for the code)

Related:

  • What does `kill -0 $pid` in a shell script do?
Answer from Aaron Digulla on Stack Overflow
Top answer
1 of 5
23

Your kill command is backwards.

Like many UNIX commands, options that start with a minus must come first, before other arguments.

If you write

kill -INT 0

it sees the -INT as an option, and sends SIGINT to 0 (0 is a special number meaning all processes in the current process group).

But if you write

kill 0 -INT

it sees the 0, decides there's no more options, so uses SIGTERM by default. And sends that to the current process group, the same as if you did

kill -TERM 0 -INT    

(it would also try sending SIGTERM to -INT, which would cause a syntax error, but it sends SIGTERM to 0 first, and never gets that far.)

So your main script is getting a SIGTERM before it gets to run the wait and echo DONE.

Add

trap 'echo got SIGTERM' TERM

at the top, just after

trap 'killall' INT

and run it again to prove this.

As Stephane Chazelas points out, your backgrounded children (process1, etc.) will ignore SIGINT by default.

In any case, I think sending SIGTERM would make more sense.

Finally, I'm not sure whether kill -process group is guaranteed to go to the children first. Ignoring signals while shutting down might be a good idea.

So try this:

#!/bin/bash
trap 'killall' INT

killall() {
    trap '' INT TERM     # ignore INT and TERM while shutting down
    echo "**** Shutting down... ****"     # added double quotes
    kill -TERM 0         # fixed order, send TERM not INT
    wait
    echo DONE
}

./process1 &
./process2 &
./process3 &

cat # wait forever
2 of 5
11

Unfortunately, commands started in background are set by the shell to ignore SIGINT, and worse, they can't un-ignore it with trap. Otherwise, all you'd have to do is

(trap - INT; exec process1) &
(trap - INT; exec process2) &
trap '' INT
wait

Because process1 and process2 would get the SIGINT when you press Ctrl-C since they're part of the same process group which is the foreground process group of the terminal.

The code above will work with pdksh and zsh which in that regard are not POSIX conformant.

With other shells, you would have to use something else to restore the default handler for SIGINT like:

perl -e '$SIG{INT}=DEFAULT; exec "process1"' &

or use a different signal like SIGTERM.

Top answer
1 of 2
2

First, you should know that kill 0 send the signal to all process in current process group. (see kill(1) - Linux man page) My guess is that it is killing more than it should. This is why wait is not waiting, it is being terminated for some reason. My best bet to solve this problem is to iterate over running jobs from jobs -pr killing one by one: this way I assure the signal is sent only to child processes in that moment and nothing more.

To get this to work I did several tests, but I was stuck on sending SIGINT to child processes. They simple don't react to it! Searching the web I've found this answer on Stack Overflow:

https://stackoverflow.com/questions/2524937/how-to-send-a-signal-sigint-from-script-to-script-bash

So the real problem is that you cannot send SIGINT from one script to other, because in non-interactive shells the signal is ignored. I was not able to workaround this issue (using bash -i to call the child script does not to work).

I know you probably want to send SIGINT and wait for the child processes to shutdown gracefully, but if you do not mind to use SIGTERM (or any other signal but SIGINT) this is the best script I wrote:

#!/bin/bash
trap 'killall' INT

killall() {
    echo '**** Shutting down... ****'
    jobs -pr
    for i in $(jobs -pr); do
        kill -TERM $i
    done
    wait
    echo DONE
}

./long.sh &
./long.sh &
./long.sh &

cat # wait forever

To test, I've created this long.sh script:

#!/bin/bash
trap 'killall' TERM

echo "Started... $$" >> file.txt

killall() {
    # shutting down gracefully
    echo "Finished... $$" >> file.txt
    exit # don't forget this!
}

# infinite loop
while true; do echo loop >/dev/null ; done

In this last script I did not use sleep function because it creates a new process that was staying on memory even after the script finishes. In your script, you can call new processes but you should assure to broadcast the SIGTERM (or any other signal you've used) to all of them.

2 of 2
0

This seems to work. Be sure to use "/usr/bin/kill" and not the Bash built-in "kill".

[myles@marklar ~]$ /usr/bin/kill --version
kill from util-linux-2.13-pre7

Note that job control is not enabled in non-interactive Bash shells (e.g. scripts).

#!/bin/bash

trap kill_jobs 2

kill_jobs()
{
        while /usr/bin/kill 0; do :; done
        exit 0
}

foo &
foo &
foo &

sleep 777777
People also ask

Can I use `wait` with commands run in a subshell?
No. `wait` can only wait for child processes of the current shell. Processes started in a subshell or a different shell session cannot be waited on.
🌐
linuxize.com
linuxize.com › home › bash › bash wait command
Bash wait Command | Linuxize
What is the difference between `wait` and `wait -n`?
`wait` without arguments waits for all background jobs to complete. `wait -n` waits for only the first job to finish and returns its exit status.
🌐
linuxize.com
linuxize.com › home › bash › bash wait command
Bash wait Command | Linuxize
What does `$!` do in Bash?
`$!` is a special Bash variable that holds the process ID (PID) of the last command run in the background.
🌐
linuxize.com
linuxize.com › home › bash › bash wait command
Bash wait Command | Linuxize
Top answer
1 of 5
11

The problem with repeatedly killing a process is that you've got a race condition with new process creation. It's not particularly likely, but it's possible that the process will exit and a new process start up with the same PID while you're sleeping.

Why are you having to repeatedly kill the process? If it's because the process will exit, but it may take some time to quit after receiving the signal, you could use wait:

 kill $PID
 wait $PID

In an ideal system, you'd never have to repeat a kill or issue kill -9 $PID. If you do have to, you might want to consider fixing whatever it is that you're running so you don't have to. In the meantime, you will probably not hit the race condition, and you can guard against it by (say) checking the timestamp of /proc/$PID/ just before killing the process. That's bad hackiness, though.

2 of 5
9

For everyone recommending the step from kill $PID to kill -9 $PID, I'd have to remind you of Useless use of kill -9.

No no no. Don't use kill -9.

It doesn't give the process a chance to cleanly:

  1. shut down socket connections

  2. clean up temp files

  3. inform its children that it is going away

  4. reset its terminal characteristics

and so on and so on and so on.

Generally, send 15, and wait a second or two, and if that doesn't work, send 2, and if that doesn't work, send 1. If that doesn't, REMOVE THE BINARY because the program is badly behaved!

Don't use kill -9. Don't bring out the combine harvester just to tidy up the flower pot.

Now, I don't agree with the "remove the binary part", but the progression seems less damaging than just a kill -9.

I also agree with the care about race conditions on the creation of new processes with the same PID mentioned here.

🌐
Linuxize
linuxize.com › home › bash › bash wait command
Bash wait Command | Linuxize
January 30, 2026 - When invoked with the -n option, the command waits only for a single job from the given PIDs or jobspecs to complete and returns its exit status. If no arguments are provided, wait -n waits for any background job to complete: ... By default, ...
🌐
Baeldung
baeldung.com › home › scripting › the wait command in linux
The wait Command in Linux | Baeldung on Linux
March 18, 2024 - Next, let’s modify the parent process to kill the second child process with killjob: echo parent process $$ starting child & pid1=$! sleep 1 child & pid2=$! killjob $pid2 & wait $pid1 $pid2 echo parent wait finished, exit status $?
🌐
Greg's Wiki
mywiki.wooledge.org › ProcessManagement
ProcessManagement - Greg's Wiki
1 #!/usr/bin/env bash 2 set -m 3 trap 'kill %%' EXIT 4 command1 | command2 & 5 wait
Find elsewhere
🌐
Baeldung
baeldung.com › home › scripting › kill a child process after a given timeout in bash
Kill a Child Process After a Given Timeout in Bash | Baeldung on Linux
March 18, 2024 - However, if we just spawn child processes in a Bash script, the script might exit before the child process has finished. We can use the wait command to wait for a child process to exit: $ sleep 5 & $ wait; echo Slept Slept [1]+ Done sleep 5 · We might want to kill a child process after a given timeout for a variety of reasons, such as restarting misbehaving programs.
🌐
The Linux Documentation Project
tldp.org › LDP › abs › html › x9644.html
15.1. Job Control Commands
Suspend script execution until all jobs running in background have terminated, or until the job number or process ID specified as an option terminates. Returns the exit status of waited-for command.
🌐
SDSU
edoras.sdsu.edu › doc › bash › abs › x8618.html
14.1. Job Control Commands
Suspend script execution until all jobs running in background have terminated, or until the job number or process ID specified as an option terminates. Returns the exit status of waited-for command.
🌐
Atomic Spin
spin.atomicobject.com › 2017 › 08 › 24 › start-stop-bash-background-process
A Script to Start (and Stop!) Background Processes in Bash
July 14, 2020 - Now, our script will stay running until the background processes finish, but if we ctrl+c, our background processes will still stay running. We can fix that with a trap. trap "kill 0" EXIT ./someProcessA & ./someProcessB & wait
🌐
w3tutorials
w3tutorials.net › blog › kill-a-process-and-wait-for-the-process-to-exit
How to Kill a Process and Wait for It to Exit in Bash: Ensuring Proper Termination Before Starting a TCP Server — w3tutorials.net
If the process ignores SIGTERM (e.g., due to a bug), use SIGKILL (signal 9) to force termination: # Wait a few seconds, then force kill if still running sleep 5 if ps -p "$PID" > /dev/null; then echo "Process $PID not exiting; sending SIGKILL..." ...
Top answer
1 of 2
4

Let me start by saying, you could just inline all the stuff you have in scannew, since you're waiting anyway, unless you intend to scan again at some other point in your script. It's really the call to wc that you're concerned might take too long, which, if it does, you can just terminate it. This is a simple way to set that up using trap which allows you to capture signals sent to a process and set your own handler for it:

#! /usr/bin/env bash

# print a line just before we run our subshell, so we know when that happens
printf "Lets do something foolish...\n"

# trap SIGINT since it will be sent to the entire process group and we only
# want the subshell killed
trap "" SIGINT

# run something that takes ages to complete
BAD_IDEA=$( trap "exit 1" SIGINT; ls -laR / )

# remove the trap because we might want to actually terminate the script
# after this point
trap - SIGINT

# if the script gets here, we know only `ls` got killed
printf "Got here! Only 'ls' got killed.\n"

exit 0

However, if you want to retain the way you do things, with scannew being a function run as a background job, it takes a bit more work.

Since you want user input, the proper way to do it is to use read, but we still need the script to go on if scannew completes and not just wait for user input forever. read makes this a bit tricky, because bash waits for the current command to complete before allowing traps to work on signals. The only solution to this that I know of, without refactoring the entire script, is to put read in a while true loop and give it a timeout of 1 second, using read -t 1. This way, it'll always take at least a second for the process to finish, but that may be acceptable in a circumstance like yours where you essentially want to run a polling daemon that lists usb devices.

#! /usr/bin/env bash

function slow_background_work {
    # condition can be anything of course
    # for testing purposes, we're just checking if the variable has anything in it
    while [[ -z $BAD_IDEA ]]
    do
        BAD_IDEA=$( ls -laR / 2>&1 | wc )
    done

    # `$$` normally gives us our own PID
    # but in a subshell, it is inherited and thus
    # gives the parent's PID
    printf "\nI'm done!\n"
    kill -s SIGUSR1 -- $$
    return 0
}

# trap SIGUSR1, which we're expecting from the background job
# once it's done with the work we gave it
trap "break" SIGUSR1

slow_background_work &

while true
do
    # rewinding the line with printf instead of the prompt string because
    # read doesn't understand backslash escapes in the prompt string
    printf "\r"
    # must check return value instead of the variable
    # because a return value of 0 always means there was
    # input of _some_ sort, including <enter> and <space>
    # otherwise, it's really tricky to test the empty variable
    # since read apparently defines it even if it doesn't get input
    read -st1 -n1 -p "prompt: " useless_variable && {
                              printf "Keypress! Quick, kill the background job w/ fire!\n"
                              # make sure we don't die as we kill our only child
                              trap "" SIGINT
                              kill -s SIGINT -- "$!"
                              trap - SIGINT
                              break
                            }
done

trap - SIGUSR1

printf "Welcome to the start of the rest of your script.\n"

exit 0

Of course, if what you actually want is a daemon that watches for changes in the number of usb devices or something, you should look into systemd which might provide something more elegant.

2 of 2
1

A (somewhat) general solution for running a given command and killing it should there be input from the user, otherwise exiting. The gist is to perform the read in a somewhat-raw terminal mode looking for the "any" key, and to handle the run-program-exits case via the SIGCHLD signal that should be sent when the child exits (assuming no funny business from the child process). Code and docs (and eventual tests).

#ifdef __linux__
#define _POSIX_SOURCE
#include <sys/types.h>
#endif

#include <err.h>
#include <fcntl.h>
#include <getopt.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sysexits.h>
#include <termios.h>
#include <unistd.h>

int Flag_UserOkay;              // -U

struct termios Original_Termios;
pid_t Child_Pid;

void child_signal(int unused);
void emit_help(void);
void reset_term(void);

int main(int argc, char *argv[])
{
    int ch, status;
    char anykey;
    struct termios terminfo;

    while ((ch = getopt(argc, argv, "h?U")) != -1) {
        switch (ch) {
        case 'U':
            Flag_UserOkay = 1;
            break;
        case 'h':
        case '?':
        default:
            emit_help();
            /* NOTREACHED */
        }
    }
    argc -= optind;
    argv += optind;

    if (argc == 0)
        emit_help();

    if (!isatty(STDIN_FILENO))
        errx(1, "must have tty to read from");

    if (tcgetattr(STDIN_FILENO, &terminfo) < 0)
        err(EX_OSERR, "could not tcgetattr() on stdin");

    Original_Termios = terminfo;

    // cfmakeraw(3) is a tad too raw and influences output from child;
    // per termios(5) use "Case B" for quick "any" key reads with
    // canonical mode (line-based processing) and echo turned off.
    terminfo.c_cc[VMIN] = 1;
    terminfo.c_cc[VTIME] = 0;
    terminfo.c_lflag &= ~(ICANON | ECHO);

    tcsetattr(STDIN_FILENO, TCSAFLUSH, &terminfo);
    atexit(reset_term);

    signal(SIGCHLD, child_signal);

    Child_Pid = fork();

    if (Child_Pid == 0) {       // child
        close(STDIN_FILENO);
        signal(SIGCHLD, SIG_DFL);
        status = execvp(*argv, argv);
        warn("could not exec '%s' (%d)", *argv, status);
        _exit(EX_OSERR);

    } else if (Child_Pid > 0) { // parent
        if ((status = read(STDIN_FILENO, &anykey, 1)) < 0)
            err(EX_IOERR, "read() failed??");
        kill(Child_Pid, SIGTERM);

    } else {
        err(EX_OSERR, "could not fork");
    }

    exit(Flag_UserOkay ? 0 : 1);
}

void child_signal(int unused)
{
    // might try to pass along the exit status of the child, but that's
    // extra work and complication...
    exit(0);
}

void emit_help(void)
{
    fprintf(stderr, "Usage: waitornot [-U] command [args ..]\n");
    exit(EX_USAGE);
}

void reset_term(void)
{
    tcsetattr(STDIN_FILENO, TCSAFLUSH, &Original_Termios);
}
🌐
Linux Hint
linuxhint.com › wait_command_linux
Wait Command in Linux – Linux Hint
#!/bin/bash echo "testing wait ... after terminating the process. sleep command is running as a background process and kill command is executed to terminate the running process....
Top answer
1 of 4
6

If you have no other processes you are waiting for, a wait with no arguments will wait for all background jobs to finish. I often use constructs like

for i in *; do md5sum $i & done
wait

This will however break if your script has any other background tasks, but you can wrap it in a subshell like

(for i in *; do md5sum $i & done; wait) & all_md5sum=$!
...
wait $all_md5sum
2 of 4
0

First try a simpler scenario such as only 2 processes FOO1 and FOO2, and if you were to run within a script for example named parent.sh:

#!/bin/bash

FOO1 &
pid1=$!

echo "Child PID=$pid1 launched"

FOO2 &
pid2=$!

echo "Child PID=$pid2 launched"

BAR exit FOO1
BAR exit FOO2

echo "Pausing to wait for children to finish..."

wait $pid1
wait $pid2

echo "Children finished, moving on."

See if this works with the two FOOs and if so, then implement for the 20.

Explanation

Since we are unable to see the internals of FOOs and BAR process, I am relying only on what you've posted and, as I understand it, you said

  • "Process BAR is used to check FOO and is also used to kill it"
  • meaning, at some point prior to your posted snippet, you started FOO1 somehow, in order for BAR exit FOO1 to affect it

From among your originally posted snippet, you also wrote:

BAR exit FOO1
PID1=$!
  • $! captures the most recently background-ed process
  • but BAR exit FOO1 is not a process going into the background, it is as you claim, a means of using BAR to control FOO: "BAR is used to check FOO and is also used to kill it"
  • thus $! is unlikely to be capturing FOO1's pid

So instead, ensure that right where you actually start a FOO* process, you capture pid. For example if you are running all in parent.sh, and have only 2 processes, you would do:

#!/bin/sh

FOO1 &
pid1=$!

echo "Child PID=$pid1 launched"

FOO2 &
pid2=$!

echo "Child PID=$pid2 launched"
  • lowercase pid1, you can use your original uppercase too, as long as consistent, I just prefer to use lowercase as a convention to differentiate from being mistaken as environment variables
  • The echo is an optional trace so we know what is going on, use it while we are troubleshooting and comment it out with # or delete it once you've solved this problem

We then do the commands you claimed you use to check and kill FOO1 and FOO2:

BAR exit FOO1
BAR exit FOO2

Then,

echo "Pausing to wait for children to finish..."

wait $pid1
wait $pid2

echo "Children finished, moving on."
  • again the echos are optional but informative while you are still troubleshooting this, lets us know what is happening
  • wait for each individual $pid...
  • followed by another optional echo to let you know the waiting is over

CREDIT

Shotts 2009 asynchronous scripts example on the free pdf's page 506