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?
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?
In my case it was easier to use the name of the process. It works like this:
pkill <process_name> && while pgrep -l <process_name>; do sleep 1;done;
Then, restart the process.
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
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.
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.
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
Can I use `wait` with commands run in a subshell?
What is the difference between `wait` and `wait -n`?
What does `$!` do in Bash?
To wait for any process to finish
Linux (doesn't work on Alpine, where ash doesn't support tail --pid):
tail --pid=$pid -f /dev/null
Darwin (requires that $pid has open files):
lsof -p $pid +r 1 &>/dev/null
With timeout (seconds)
Linux:
timeout $timeout tail --pid=$pid -f /dev/null
Darwin (requires that $pid has open files):
lsof -p $pid +r 1m%s -t | grep -qm1 $(date -v+${timeout}S +%s 2>/dev/null || echo INF)
There's no builtin. Use kill -0 in a loop for a workable solution:
anywait(){
for pid in "$@"; do
while kill -0 "$pid"; do
sleep 0.5
done
done
}
Or as a simpler oneliner for easy one time usage:
while kill -0 PIDS 2> /dev/null; do sleep 1; done;
As noted by several commentators, if you want to wait for processes that you do not have the privilege to send signals to, you have find some other way to detect if the process is running to replace the kill -0 $pid call. On Linux, test -d "/proc/$pid" works, on other systems you might have to use pgrep (if available) or something like ps | grep "^$pid ".
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.
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:
shut down socket connections
clean up temp files
inform its children that it is going away
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.
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.
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);
}
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
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
FOO1somehow, in order forBAR exit FOO1to 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 FOO1is 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 capturingFOO1'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 waitfor each individual$pid...- followed by another optional
echoto let you know thewaiting is over
CREDIT
Shotts 2009 asynchronous scripts example on the free pdf's page 506
wait also (optionally) takes the PID of the process to wait for, and with $! you get the PID of the last command launched in the background.
Modify the loop to store the PID of each spawned sub-process into an array, and then loop again waiting on each PID.
# run processes and store pids in array
pids=()
for i in $n_procs; do
./procs[${i}] &
pids[${i}]=$!
done
# wait for all pids
for pid in ${pids[*]}; do
wait $pid
done
http://jeremy.zawodny.com/blog/archives/010717.html :
#!/bin/bash
FAIL=0
echo "starting"
./sleeper 2 0 &
./sleeper 2 1 &
./sleeper 3 0 &
./sleeper 2 0 &
for job in `jobs -p`
do
echo $job
wait $job || let "FAIL+=1"
done
echo $FAIL
if [ "$FAIL" == "0" ];
then
echo "YAY!"
else
echo "FAIL! ($FAIL)"
fi
The problem is that all commands are executed from the first line to the last in order. So as @Scott Stensland mentoined you have to put it into background via & (ampersand) to make the timer start.
And furthermore I think getting the PID of your app via searching through process names is a dangerous practice since you might accidentally kill a program which contains the string app in its name. So a safer way is to use the variable ! to get the PID . So your modified script should now looks like this :
while (true); do
./app &
app_pid=$!
sleep 3600
kill $app_pid
sleep 60
done
When you put a process into background via & it goes into the job list of the parent bash process , and via ! you can get the PID of the last job.So it's safe.
You could consider using the timeout command:
NAME
timeout - run a command with a time limit
SYNOPSIS
timeout [OPTION] DURATION COMMAND [ARG]...
timeout [OPTION]
DESCRIPTION
Start COMMAND, and kill it if still running after DURATION.
ex.
#!/bin/bash
while :
do
timeout 1h ./app
sleep 1m
done