2

I am trying to make a "toolkit", and what I want is for it to have an "exit" functionality. Meaning that whenever I invoke "exit", whatever I have started should stop. However, not just the toolkit script, but the main script as well. Meaning the following:

Let A.sh be the master script. At some point, A.sh will trigger B.sh, which gives the user options. I want B.sh to always have an "exit" option. When chosen, it should exit B.sh (easy, just run "exit"), but also end A.sh.

Of course, I could do something like

if [[ $(B.sh) == "exit" ]]; then exit; fi

but that is tedious at best, and generally not what a "toolkit" should do.

Any ideas how I can do it? Cheers.

7
  • "generally not what a "toolkit" should do", says who? Why shouldn't it? There aren't a lot of ways for scripts to communicate. Output is one, exit status is another.
    – muru
    Mar 24 at 10:04
  • True enough, but what I mean is that, in this case, I would have to "remember" to do that every time it is invoked. It would be much nicer if did not have to do that.
    – I.P
    Mar 24 at 10:05
  • 1
    Put a wrapper function around your invocation of B.sh: my_B() { if [[ $(B.sh) == "exit" ]]; then exit; fi; }. Or source it, if every time B.sh exits you want A.sh to exit too.
    – muru
    Mar 24 at 10:07
  • It will likely depend on the mechanism used by A.sh to "trigger" B.sh. There are many ways to do it, and it's hard giving you a generic answer. It's also likely to be a programming question, not a Unix one.
    – rahmu
    Mar 24 at 10:19
  • 1
    this already reads a lot like you're kind of stretching what shell scripts are good at. Maybe consider a different scripting language for this – calling subroutines and allowing them to return a variety of states, or cause exceptions, comes with it. Basically, every modern programming language allows for that in some form or other – aside from shell scripts. Mar 24 at 12:39

2 Answers 2

4

At least on Linux, you can have the child script kill the process group of the parent. That will kill the parent itself and any child processes it may have launched. From man kill:

ARGUMENTS
       The list of processes to be signaled can be a mixture of names and
       PIDs.

[. . .]
           -n
               where n is larger than 1. All processes in process group n are
               signaled. When an argument of the form '-n' is given, and it
               is meant to denote a process group, either a signal must be
               specified first, or the argument must be preceded by a '--'
               option, otherwise it will be taken as the signal to send.

So your function can be something like this:

killGroup(){
    pgid=$(ps -p "$$" -o pgid=)
    kill -- -"$pgid"
}

That will get the process group ID, and the run kill on it to kill all processes under this group ID.

4
  • Cheers, that works. Very nice and clear. (As far as I am concerned, the thread can be considered closed)
    – I.P
    Mar 24 at 16:25
  • This method relies on setpgid(2) executed by interactive shell when calling A.sh. In a sequence of calls: interactive_shell -> AA.sh -> A.sh -> B.sh -> kill_pgid, AA.sh will also be killed which is too much. To limit the killing to A.sh and its children we need A.sh to establish new process group, but doing so will decouple them from terminal signals (man 3 tcsetpgrp) which is not good also. In bash, trap my_error_handler ERR in A.sh might be better way than using the signal hacks.
    – sizif
    Mar 25 at 11:23
  • @sizif yes, exactly. I don't see why that is too much, I am showing how the OP can kill the entire process group which is what I think they want since they talk about a "toolkit". If you want to kill the direct parent and nothing else, this isn't what you should use.
    – terdon
    Mar 25 at 12:03
  • @terdon ♦ by "too much" I meant any wrapper scripts around the master script A.sh will be victims as well, everything up to the interactive shell. IMHO this is a weird behavior, the cleanup should be limited to A.sh and anything started from that. But the code to do this is educational, thanks
    – sizif
    Mar 25 at 13:42
1

Clearly hackish, but to answer the question: you can reach the parent bash script by $PPID.

$ cat > A.sh
#!/bin/bash
/bin/bash B.sh
echo "A continues"
exit 0

$ cat > B.sh
#!/bin/bash
echo "B entered"
kill $PPID
sleep 3
echo "B finishes"
exit 0

$ bash A.sh
B entered
Terminated
$ B finishes

You can add a trap in A.sh to let A exit cleanly, not by SIGTERM which triggers the Terminated message from the invoking shell.

A better solution might be to set -e in A.sh which will make A.sh to terminate should any of the commands in it exit non-zero:

$ cat >A.sh
#!/bin/bash
set -e
bash B.sh
echo "A continues"
exit 0

$ cat > B.sh
#!/bin/bash
echo "B finishes with exit code 1"
exit 1

$ bash A.sh
B finishes with exit code 1

$ echo $?
1
2
  • 2
    The problem is that this only finds the parent, not any other children of the parent. And it also won't catch nested processes where scriptA launched scriptB which launched scriptC. The PPID of scriptC will be scriptB and not scriptA.
    – terdon
    Mar 24 at 14:19
  • Thanks @sizif, I think it will work as well (at least for my case). @ terdons answer is a bit clear to be honest, but still, I like your answer too and it would do in a pinch. Thanks both (and all who answered) for the help!
    – I.P
    Mar 24 at 16:27

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .