how to prevent a bash script called via su -c from hanging after SIGINT

4.9k Views Asked by At

I have two scripts. These are simplified. The root-script.sh calls userscript.sh:

root-script.sh:

 #!/bin/bash 
 su - user1 -c "/user1path/user-script.sh"

user-script.sh:

 #!/bin/bash
 trap 'echo please use x for exit' 2
 while x=0; do
    read -p "enter x for exit:" answer
    if [[ $answer = 'x' ]]; then
            echo "exit now"
            exit 0
    fi
 done

If I call user-script.sh it just works as it should:

 enter x for exit:
 enter x for exit: ^C_please use x for exit
^C_please use x for exit
x
exit now

If I call root-script.sh as root and enter a Ctrl-C I get

 enter x for exit: ^C
Session terminated, killing shell... ...killed.

Than I get back the root-prompt but the prompt is blocked. With ps I don't see the root-script, only the user-script. If I kill the user-script the root-prompt is usable again.

How can I prevent the root-script-user-script-construction from hanging after SIGINT? Means for me

  1. Exiting root-script.sh and user-script.sh or
  2. the root-script-user-script-construction should work as same as user-script.sh

    • bash-version: 3.2.51(1)-release (x86_64-suse-linux-gnu)
    • os-version: sles11 3.0.93-0.8-default
2

There are 2 best solutions below

6
On

I just tested:

su -c 'trap /bin/true 2; while true; do sleep 1; done' user

verses

su -c 'while true; do sleep 1; done' user

and found that the former could not be terminated via SIGINT but the latter could. My guess is that perhaps su -c opens the user's shell to run the command passed by -c, and that is what will catch the SIGINT and terminate - but your script only captures the SIGINT in a subshell - which is maybe passed SIGTERM by the parent shell's SIGINT handler.

Hopefully that works for you.

EDIT:

su -c 'echo $0; echo $SHELL' user

confirms that the command is run with the user's shell.

Perhaps you will find

su -c 'exec my_script.sh' user

To be a more elegant solution. I think it will work but have not tested it. exec will replace the current shell process with your script's process, though, so I think it should work.

EDIT 2:

Looking back at your question, you just need the trap in the root script, I think. Or maybe:

exec 'su -c "exec script.sh" user'

If you want to completely inherit the script's trap behaviour.

3
On

This article explains why SIGINT is not passed into su -c and gives a solution:

http://sethmiller.org/it/su-forking-and-the-incorrect-trapping-of-sigint-ctrl-c/

In your case: su - user1 --session-command "/user1path/user-script.sh"

As --session-command is a discouraged option (see man su), if you don't feel safe, it's also possible, in your case, to use the -s option:

su - user1 -s /user1path/user-script.sh