Update: trap requires removing SIG prefix on condition, although some shells support including it. See comment below.
The ampersand "&" runs a command in the background in a new process. When its parent process (the command that runs the bash script in your case) ends, this background process will reset its parent process to init (process with PID 1), but will not die. When you press ctrl+c, you are sending an interrupt signal to the foreground process, and it will not affect the background process.
In order to kill the background process, you should use the kill command with the PID of the most recent background process, which could be obtained by $!.
If you want the to use ctrl+c to kill both the script and background process, you can do this:
trap 'kill $BGPID; exit' INT
sleep 1024 & # background command
BGPID=$!
sleep 1024 # foreground command of the script
trap modifies the trap handler of the SIGINT (trap requires removing the SIG prefix but some shell may support including it) so the script will kills the process with $BGPID before it exits.
Update: trap requires removing SIG prefix on condition, although some shells support including it. See comment below.
The ampersand "&" runs a command in the background in a new process. When its parent process (the command that runs the bash script in your case) ends, this background process will reset its parent process to init (process with PID 1), but will not die. When you press ctrl+c, you are sending an interrupt signal to the foreground process, and it will not affect the background process.
In order to kill the background process, you should use the kill command with the PID of the most recent background process, which could be obtained by $!.
If you want the to use ctrl+c to kill both the script and background process, you can do this:
trap 'kill $BGPID; exit' INT
sleep 1024 & # background command
BGPID=$!
sleep 1024 # foreground command of the script
trap modifies the trap handler of the SIGINT (trap requires removing the SIG prefix but some shell may support including it) so the script will kills the process with $BGPID before it exits.
Programs can ignore Ctrl+c signal, as they can ignore SIGTSTP as well
You can try Ctrl+z in most shells (not always but most of the time)
There are some signals that can not be ignored by the process: SIGKILL, SIGSTOP. You can send the kill command. To kill your frozen process, you have to find the process ID (PID).
use pgrep or ps and then kill it
% kill -9 PID
For anyone wondering, this is how you launch childs in the background and kill them on ctrl+c:
#!/usr/bin/env bash
command1 &
pid[0]=$!
command2 &
pid[1]=$!
trap "kill
{pid[1]}; exit 1" INT
wait
somecommand &
returns a pid of the child in $!
somecommand &
pid[0]=$!
anothercommand &
pid[1]=$!
trap "kill
{pid[1]}; exit 1" INT
wait
I would start with this model rather than with bash job control (bg, fg, jobs). Normally init inherits and reaps orphan processes. What problem are you trying to solve?
I am running RetroPie/Debian 10 on Raspberry Pi 4 with EmulationStation front-end. Sometimes I quit ES and leave the terminal up when I'm not using it. And sometimes I want to start up ES from the couch with my wireless controller, rather than cross the room and use the keyboard. (I know, right?)
Essentially what I want is for a gamepad button combo to have the same effect as typing "logout" on the keyboard.
...anyway, I thought I had it solved with jslisten (watches /dev/input/js{n} for a predefined button combo & runs a command on activation):
https://github.com/workinghard/jslisten
How I've set it up:
To activate it when I quit ES, I add this line to ~/.bashrc:
[[ "$(tty)" == "/dev/tty1" ]] && (/opt/retropie/configs/all/jslisten/jslisten /dev/input/js0 &)
...it is configured to watch for my button combo and then run a script I've called logmeout.sh that looks for and kills the oldest instance of bash on tty1:
#!/bin/bash pid="$(pgrep -o -t tty1 bash)" [[ -n "$pid" ]] && kill "$pid"
And then, to disable it at login, I add pkill jslisten to autostart.sh, before emulationstation #auto is invoked.
This is mostly working as intended. I can pick up my controller from across the room, press select + guide + start and, as long as it was just sitting at the terminal, I'll be logged out, at which point the autostart triggers and logs me back in to EmulationStation. Just like I wanted.
On the other hand if it is NOT just sitting at the terminal (I've tested while editing a file in nano, while watching system temp with watch -n1 vcgencmd measure_temp and while running emulationstation manually from command line), nothing happens; the current task is not interrupted and when I finish it, I am not suddenly logged out or anything bad like that. Also just like I wanted.
There's just one little thorn left I have to work out. Sometimes it just...doesn't do anything. I'll push the buttons and nothing happens. Unplug and replug the controller's wireless dongle, nothing. After a quick relog from the keyboard, everything is working again...weird.
The next time it happened I thought to check the running processes with ps, and the jslisten process was not listed. So why not...
...I tracked it down to any time I hit ctrl-c in the terminal, it's killing the process. Why? And what can I do about it?
Also this only happens when it's started from .bashrc; if I run the exact same command from terminal and ctrl-c, the process persists (terminal output re-enactment):
# {quit from EmulationStation}
pi@retropie:~ $ ps
PID TTY TIME CMD
13883 tty1 00:00:00 bash
14115 tty1 00:00:09 jslisten
16286 tty1 00:00:00 ps
pi@retropie:~ $ ^C
pi@retropie:~ $ ps
PID TTY TIME CMD
13883 tty1 00:00:00 bash
16300 tty1 00:00:00 ps
pi@retropie:~ $ [[ "$(tty)" == "/dev/tty1" ]] && (/opt/retropie/configs/all/jslisten/jslisten /dev/input/js0 &)
pi@retropie:~ $ ps
PID TTY TIME CMD
13883 tty1 00:00:00 bash
16339 tty1 00:00:00 jslisten
16340 tty1 00:00:00 ps
pi@retropie:~ $ ^C
pi@retropie:~ $ ps
PID TTY TIME CMD
13883 tty1 00:00:00 bash
16339 tty1 00:00:00 jslisten
16341 tty1 00:00:00 ps
pi@retropie:~ $
I've written a long bash script that will kick off numerous processes and puts them in the background. For some commands the script will use nohup <cmd> &, and other commands it will runrails server -d for backgrounding. The script can take up to a minute to complete. While the script is running and starting up the processes, sometimes if I hit Ctrl-C and it will die immediately, other times it will not die immediately and just keep chugging along.
Is there a way to have Ctrl-C consistently kill the script immediately?
Just negate the pgid when you specify the pid to kill. You should be able to use $$ as the process group, but sometimes it's convenient to pull the process group id from ps. Try:
#!/bin/bash
echo "This runs 8 similar versions of giza"
pgid=$(ps -o pgid= $$ | tr -d ' ')
trap 'kill -- -$pgid' 0
for d in giza_google giza giza_{1..6}; do
(cd $d; sh runAgent) &
done
wait
Note that I've changed the name of the script to runAgent without the .sh suffix. See https://www.talisman.org/~erlkonig/documents/commandname-extensions-considered-harmful/
You might consider using GNU Parallel to run jobs in parallel. Your whole script becomes:
parallel 'cd {} && ./runAgent' ::: giza*/
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.
&(Ampersand) sends the process to the background. It means if you run "run_webserver &" then this process will run in the backgrond.
You can bring this process to foreground by looking at the job id for the process in the terminal.
Steps to bring the process to foreground and kill
jobs :--> this command will list all the process running on the terminal.
fg :--> this command will bring the process to the foreground.
Then you can use your ctrl-c to kill the process, or any other sequence which kills the process.
Please find the attached screen shots:-


You can set a trap to kill the background process on exit, and wait for the background process.
#!/bin/bash
trap 'kill $!' EXIT
run_webserver &
xdg-open "http://$HOSTNAME/dev-environment"
wait
Alternatively, run the other command in the background instead
#!/bin/bash
xdg-open "http://$HOSTNAME/dev-environment" &
run_webserver
See also http://mywiki.wooledge.org/ProcessManagement
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
There are many ways to go about this.
Method #1 - ps
You can use the ps command to find the process ID for this process and then use the PID to kill the process.
Example
$ ps -eaf | grep [w]get
saml 1713 1709 0 Dec10 pts/0 00:00:00 wget ...
$ kill 1713
Method #2 - pgrep
You can also find the process ID using pgrep.
Example
$ pgrep wget
1234
$ kill 1234
Method #3 - pkill
If you're sure it's the only wget you've run you can use the command pkill to kill the job by name.
Example
$ pkill wget
Method #4 - jobs
If you're in the same shell from where you ran the job that's now backgrounded. You can check if it's running still using the jobs command, and also kill it by its job number.
Example
My fake job, sleep.
$ sleep 100 &
[1] 4542
Find it's job number. NOTE: the number 4542 is the process ID.
$ jobs
[1]+ Running sleep 100 &
$ kill %1
[1]+ Terminated sleep 100
Method #5 - fg
You can bring a backgrounded job back to the foreground using the fg command.
Example
Fake job, sleep.
$ sleep 100 &
[1] 4650
Get the job's number.
$ jobs
[1]+ Running sleep 100 &
Bring job #1 back to the foreground, and then use Ctrl+C.
$ fg 1
sleep 100
^C
$
In bash you can use fg to get the job to the foreground and then use Ctrl+C
Or list the process in the background with jobs and then do
kill %1
(with 1 replaced by the number jobs gave you)
You can get the PID of mplayer and upon trapping send the kill signal to mplayer's PID.
function clean_up {
# Perform program exit housekeeping
KILL $MPLAYER_PID
exit
}
trap clean_up SIGHUP SIGINT SIGTERM
mplayer sound.mp3 &
MPLAYER_PID=$!
wait $MPLAYER_PID
mplayer returns 1 when it is stopped with Ctrl-C so:
mplayer sound.mp3 || break
will do the work.
One issue of this method is that if mplayer exits 1 for another reason (i.e., sound file has a bad format), it will exit anyway, and it's maybe not the desired behaviour.