Signals cause wait to exit with a status equal to the signal number plus 128. To get the real exit status, loop until $? is less.
#!/bin/bash
trap 'echo trapped' INT TERM
sleep 10 & pid=$!
while wait $pid; test $? -ge 128
do echo 'finished wait'
done
echo bye
Note that test overwrites $? with its own exit status, so if you care about the real exit status from wait, you'll have to create a variable between wait and test.
Signals cause wait to exit with a status equal to the signal number plus 128. To get the real exit status, loop until $? is less.
#!/bin/bash
trap 'echo trapped' INT TERM
sleep 10 & pid=$!
while wait $pid; test $? -ge 128
do echo 'finished wait'
done
echo bye
Note that test overwrites $? with its own exit status, so if you care about the real exit status from wait, you'll have to create a variable between wait and test.
Solved with this:
#!/bin/bash
trap 'echo SIGTERM' SIGTERM
sleep 1m &
echo waiting
wait $!
echo Got signal but still waiting
wait $!
echo bye
output:
waiting
SIGTERM
Got signal but still waiting
bye
It will "swallow" one SIGTERM signal and exit after sleep 1m & is finished.
bash - How can I kill and wait for background processes to finish in a shell script when I Ctrl+C it? - Unix & Linux Stack Exchange
shell - Interrupt sleep in bash with a signal trap - Stack Overflow
bash - Wait for signal - Unix & Linux Stack Exchange
How to trap and exit cleanly?
Can anybody tell why there the difference is?
Ctrl+C gets to sleep as well, while kill <pid> doesn't. In effect the latter way needs sleep to exit by itself before hi! is printed. What you call "1 second delay" is about 1 second at most and about 0.5 on average (it may take longer if the OS is very busy with other tasks).
You can clearly see the difference by using
pv -qL 1 <<< "0123456789"
instead of sleep 1.
Note that
kill -- -<pid> # note the minus sign before PID
would send the signal to the entire process group, so to sleep (or pv) as well.
Is there a way that always trigger trap command immediately no matter what is going on?
There is this question How to handle signals in bash during synchronous execution?
From one of the answers:
If bash is waiting for a command to complete and receives a signal for which a trap has been set, the trap will not be executed until the command completes. When bash is waiting for an asynchronous command via the
waitbuiltin, the reception of a signal for which a trap has been set will cause thewaitbuiltin to return immediately with an exit status greater than 128, immediately after which the trap is executed.So, run your external program asynchronously and use
wait. Kill it using$!.
In your case this method leads to the following quick and dirty script:
#!/bin/bash
trap 'kill "$!"; echo hi!' SIGINT SIGTERM
echo "pid is $$"
while true; do
sleep 1 &
wait
done
Instead of sleep 1 try sleep 1 <&0 & wait and then you'll find it quits right away.
The sleep command will remain running, however.
This works because bash's wait command can be interrupted by a signal, but the internal wait can't be.
Of course <&0 isn't required for sleep, but for other commands that are being run in the background this way, stdin would be disconnected unless we deliberately connect it in this way.
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.
#!/bin/bash
trap 'echo "Caught SIGUSR1"' SIGUSR1
echo "Sleeping. Pid=$$"
while :
do
sleep 10 &
wait $!
echo "Sleep over"
done
Just a point about the wait after the sleep because I've just made this error in my script:
You should use
wait $!instead ofwaitif, inside your script, you already launched other processes in background
For example, the wait inside the next snippet of code will wait for the termination of both process_1 and sleep 10:
process_1 &
...
sleep 10 &
wait
If you use, instead of wait, wait $! your script will wait only for sleep 10, because $! means PID of last backgrounded process.
No.
wait is exclusively used in a parent process to wait for the termination of a child process (and to access its exit status).
Furthermore, no process may trap the KILL signal (the original question used KILL as example).
Also, to "wait for a signal" is an unusual thing to want to do, as signals are asynchronous events, meaning you don't wait for them, but instead you install a signal handler (using trap in the shell) that would handle the signal whenever it arrives. A signal could be arriving at any time during the script's execution, and the signal handler would be executed when that happens (the normal program flow would temporarily pause while the signal is being handled).
You could, obviously, do something like
trap 'quit=1' USR1
quit=0
while [ "$quit" -ne 1 ]; do
printf 'Do "kill -USR1 %d" to exit this loop after the sleep\n' "$$"
sleep 1
done
echo The USR1 signal has now been caught and handled
to do a sort of a "waiting for signal to arrive" loop.
Here, the "trap" would "catch" the USR1 signal, the "handler" would set quit to 1, and control would be returned to the code, which would exit the loop.
Without loop and second process
trap "echo >&3" SIGINT
exec 3<> <(:)
read <&3
This creates pipe fd 3, opened both for read and write by starting subshell with command ":" (alias for "true"). And then blocks at reading from it. Signal handler unblocks read by sending empty line into the pipe. The same could be achieved using named pipe (fifo).
Code:
find . -maxdepth 1 -printf "%P\n" | egrep -v ^$ | while read -r line ; do aws s3 sync $line s3://$bucket/$line done
I'd like to add a trap to exit cleanly , but everything I've tried kills the current running aws s3 sync then moves to the next read line and continues on. I know it's probably something small I am overlooking, how do I get the script exit out on ctrl + c correctly ?