The Bash child of the script exits along with the parent script when the parent call is activated / terminated by the terminal, but not when invoked silently / cron

This is parent.sh:

#!/bin/bash trap 'exit' SIGHUP SIGINT SIGQUIT SIGTERM if ! [ -t 0 ]; then # if running non-interactively sleep 5 & # allow a little time for child to generate some output set -bm # to be able to trap SIGCHLD trap 'kill -SIGINT $$' SIGCHLD # when sleep is done, interrupt self automatically - cannot issue interrupt by keystroke since running non-interactively fi sudo ~/child.sh 

This is child.sh:

 #!/bin/bash test -f out.txt && rm out.txt for second in {1..10}; do echo "$second" >> out.txt sleep 1 done 

If you run the parent script in the terminal, for example ...

 ~/parent.sh 

... and after about 3 seconds, interrupt by pressing the key. When checking out.txt after a few seconds, it will look like ...

 1 2 3 

... thus indicating that the parent and child are finished with an interruption (by pressing a key). This is confirmed by checking ps -ef in real time and observing that script processes are present before the interrupt and leave after the interrupt.

If the parent script is called cron, like ...

 * * * * * ~/parent.sh 

... the contents of out.txt are always ...

 1 2 3 4 5 6 7 8 9 10 

... thus indicating that at least the child has not completed the kill command. This is confirmed by checking ps -ef in real time and observing that script processes are present before the interrupt, and only the interrupt is interrupted only by the parent process, but the child process is saved until it passes.

Attempts to solve ...

  • Shell parameters can only be a factor here, since the non-interactive calls to the parent run set -bm (which entails PGIDs of children other than the parent’s PGIDs are in front). In addition, both scenarios show only hB options enabled, whether it is interactively executed or not.
  • I looked through the bash man for clues, but found nothing.
  • I tried several web searches that included a lot of stackoverflow, but while some of them looked like this question, none of us are the same. The nearest answers entailed ...
    • using wait to get the identifier of the child process and call kill on it - result: /parent.sh: line 30: kill: (17955) - operation not allowed "
    • calling kill in a process group - leads to "~ / parent.sh: line 31: kill: (-15227) - the operation is not allowed" (kill using a child’s PGID that differs from the parent in a non-interactive way, due to the ability to control the task )
    • iterate over current tasks and kill each

The problem with these solutions is that the parent runs as a regular user, while the child works as root via sudo (in the end it will be binary, not suid script), so the parent can not kill him? If this means that “Operation is not allowed” means why the process called by sudo can be killed when sending a keypress interrupt through the terminal?

The natural course is to avoid extra code unless it is necessary, i.e. since scripts behave correctly when interactively launched, if possible, it is much more preferable to simply use the same behavior when working non-interactively / using cron.

The bottom question is about what can be done to make the interrupt signal (or term) issued during operation without interactive interaction lead to the same behavior as the interrupt signal when starting in interactive mode?

Thanks. Any help is appreciated.

+6
source share
1 answer
  • When you manually run a script from an interactive shell (it usually works on pty), it is a terminal driver that catches CTRL-C and converts it to SIGINT and sends all processes in the foreground to a group of processes (the script itself and the sudo ).
  • When your script is run from cron, you only send SIGINT to the shell of the script, and the sudo will continue to work, and bash will not kill its child when it exits for this type of script.

To explicitly send a signal to an entire process group, you can use a negative process group identifier. For your case, pgid should be the PID of the shell script, so try like this:

 trap 'kill -SIGINT -$$' SIGCHLD 

UPDATE:

It turns out my assumption about pgid is wrong. Just made a test with this simple cron.sh :

 #!/bin/bash set -m sleep 888 & sudo sleep 999 

and crontal -l as follows:

 30 * * * * /root/tmp/cron.sh 

When the cron job runs ps outputs as follows:

  PPID PID PGID SID COMMAND 15486 15487 15487 15487 /bin/sh -c /root/tmp/cron.sh 15487 15488 15487 15487 /bin/bash /root/tmp/cron.sh 15488 15489 15489 15487 sleep 888 15488 15490 15490 15487 sudo sleep 999 15490 15494 15490 15487 sleep 999 

So, sudo (and its child) works in a separate pgrp, and pgid is not a pid from cron.sh , so my solution ( kill -INT -$$ ) will not work.

Then I think that we can solve the problem as follows:

 #!/bin/bash set -m sudo sleep 999 & # run sudo in backgroup pid=$! # save the pid which is also the pgid sleep 5 sudo kill -INT -$pid # kill the pgrp. # Use sudo since we're killing root processes 
+2
source

Source: https://habr.com/ru/post/1013319/


All Articles