How can a bash script execute the equivalent of Ctrl-C for a background task?

Is there a way to call a subprocess so that it and all its descendants are sent by interrupt, just as if you made Ctrl-C a foreground task? I am trying to kill a script launcher that causes a perennial child. Ive tried kill -SIGINT $child (which does not send an interrupt to its descendants, so this is no-op) and kill -SIGINT -$child (which works when invoked interactively, but not when run in a script).

Here is a test script. Long running script - test.sh --child . When you call test.sh --parent , it calls test.sh --child & , and then tries to kill it. How to make a parent successfully kill a child?

 #!/bin/bash if [ "$1" = "--child" ]; then sleep 1000 elif [ "$1" = "--parent" ]; then "$0" --child & for child in $(jobs -p); do echo kill -SIGINT "-$child" && kill -SIGINT "-$child" done wait $(jobs -p) else echo "Must be invoked with --child or --parent." fi 

I know that you can change a long child signal to trap , send it to your subprocess, and then wait (from the Bash script to kill the background of the (large) children in Ctrl + C ), but is there a way without changing the child script?

+7
bash
Feb 04 '13 at 21:59
source share
4 answers

Read the following: How to send a SIGINT signal from script to script? Bash

Also from info bash

  To facilitate the implementation of the user interface to job control, the operating system maintains the notion of a current terminal process group ID. Members of this process group (processes whose process group ID is equal to the current terminal process group ID) receive keyboard- generated signals such as SIGINT. These processes are said to be in the foreground. Background processes are those whose process group ID differs from the terminal's; such processes are immune to keyboard-genโ€ erated signals. 

So, bash differentiates background processes from foreground processes using the process group identifier. If the process group identifier is equal to the process identifier, then the process is the front process and terminates when it receives a SIGINT signal. Otherwise, it will not be completed (if it does not fall into the trap).

You can see the process group id with

 ps x -o "%p %r %y %x %c " 

Thus, when you start a background process (with & ) from a script, it will ignore the SIGINT signal if it does not fall into the trap.

However, you can kill the child process with other signals such as SIGKILL , SIGTERM , etc.

For example, if you change your script to the following, it will successfully complete the child process:

 #!/bin/bash if [ "$1" = "--child" ]; then sleep 1000 elif [ "$1" = "--parent" ]; then "$0" --child & for child in $(jobs -p); do echo kill "$child" && kill "$child" done wait $(jobs -p) else echo "Must be invoked with --child or --parent." fi 

Output:

 $ ./test.sh --parent kill 2187 ./test.sh: line 10: 2187 Terminated "$0" --child 
+4
Feb 04 '13 at 22:36
source share

For someone wondering, here's how you launch child elements in the background and kill them on ctrl + c:

 #!/usr/bin/env bash command1 & pid[0]=$! command2 & pid[1]=$! trap "kill ${pid[0]} ${pid[1]}; exit 1" INT wait 
+8
Oct 09 '13 at 14:26
source share
 somecommand & 

returns the pid of the child in $!

 somecommand & pid[0]=$! anothercommand & pid[1]=$! trap INT " kill ${pid[0]} ${pid[1]}; exit 1" wait 

I would start with this model, and not with the help of bash job management (bg, fg, jobs). Usually init inherits and uses orphan processes. What problem are you trying to solve?

+5
Feb 04 '13 at 22:20
source share

You can use SIGINT with background tasks with a slight slight twist: put your asynchronous subprocess call into a function or { } and give it setsid so that it has its own process group.

Here your script holds it in full:

  • use and distribution of SIGINT and not using another signal

  • only changes the call from: "$0" --child & to { setsid "$0" --child; } & { setsid "$0" --child; } &

  • Adding the code necessary to get the PID of your child instance, which is the only process in the background subshell.

Here is your code:

 #!/bin/bash if [ "$1" = "--child" ]; then sleep 1000 elif [ "$1" = "--parent" ]; then { setsid "$0" --child; } & subshell_pid=$! pids=$(ps -ax -o ppid,pid --no-headers | sed -r 's/^ +//g;s/ +/ /g' | grep "^$subshell_pid " | cut -f 2 -d " "); for child in $pids; do echo kill -SIGINT "-$child" && kill -SIGINT "-$child" done wait $subshell_pid else echo "Must be invoked with --child or --parent." 

Here is an important part of the document from bash manual

The identifier of the process group in the background process (in the Job Management section of the document):

[...] processes whose process group identifier is equal to the current terminal The process group identifier [..] receives keyboard signals, such as SIGINT. These processes are said to be in the foreground. Background processes are those whose process group identifier is different from the terminal; such processes are immune to keyboard signals .

The default handler for SIGINT and SIGQUIT (in the Signals section of the document):

The non-configured commands executed by bash have signal handlers set to values โ€‹โ€‹inherited by the shell from the parent. When job control fails, asynchronous commands ignore SIGINT and SIGQUIT in addition to these legacy handlers.

and about trap modification (in trap builtin doc):

Signals ignored when entering the shell cannot be captured or reset .

0
Jul 30 '16 at 12:20
source share



All Articles