7

There are situations where you want to terminate the script from inside a function:

function die_if_fatal(){
    ....
    [ fatal ] && <termination statement>
}

If the script is sourced, $ . script, and the termination statement is:

  • return, as expected, will return from die, but will not finish the script
  • exit terminate the session (doesn't return to the script).

Now, if the script is executed: chmod +x script; ./script:

  • return, as expected, will return from die, but will not finish the script
  • exit doesn't return to die and terminate the script.

The simple method is to use return codes and check them upon return, however, I need to stop the parent, without modifying the caller script.

There are alternatives to handle this, however, imagine you are 5 level into a complex script and you find that the script must end; maybe a 'magic' exit code? I just want the execute behavior on a sourced code.

I'm looking for a simple one statement, to end a running sourced script.

When sourcing, what is the right method to finish the script from inside a function?

2
  • 2
    I think this is difficult. See stackoverflow.com/questions/33303605/…, which has no useful answer. Commented Sep 8, 2016 at 20:24
  • Is the caller script sourced? Execute the caller script in a subshell, and source your script. Exit should terminate the subshell with the caller script without terminating the session. Commented Sep 8, 2016 at 21:37

3 Answers 3

2

Assuming that your script will NOT be sourced from inside a loop you can enclose the main body of your script into an artificial run-once loop and break out of the script using the break command.

Formalizing this idea and providing a couple of supporting utilities, your scripts will have to have the following structure:

#!/bin/bash

my_exit_code=''
bailout() {
    my_exit_code=${1:-0}

    # hopefully there will be less than 10000 enclosing loops
    break 10000
}

set_exit_code() {
    local s=$?
    if [[ -z $my_exit_code ]]
    then
        return $s
    fi
    return $my_exit_code
}

###### functions #######

# Define your functions here.
#
# Finish the script from inside a function by calling 'bailout [exit_code]'

#### end functions #####

for dummy in once;
do

    # main body of the script
    # 
    # finish the script by calling 'bailout [exit_code]'

done
set_exit_code
Sign up to request clarification or add additional context in comments.

Comments

1

My idea is to base on PGID (Process Group Identifier) and SID (Session identifier). Unless you source your script directly from a session leader, the following solution works. On the other hand, session leaders are mostly daemons and interactive shells. You can verify which processes run as session leaders by ps aux | awk '$8 ~ /s/ { print }'.

Source

/tmp/my_quit.sh:

get_pid()
{
    pgid=$( ps -q $$ -o pgid= )
    sid=$( ps -q $$ -o sid= )
    if [[ $pgid == $sid ]]; then
        echo 0
    else
        echo $pgid
    fi
}

fatal()
{
    echo "1"
}

die_if_fatal()
{
    if [ $(fatal) -ne 0 ]; then
        pid=$( get_pid )
        if [ $pid -ne 0 ]; then
            echo "      >> Kill $pid"
            kill $pid
        else
            return
        fi
    fi
    echo "Rest of die_if_fatal's logic"
}

die_if_fatal
echo "      >> Sourced from a session leader. Will not end the process."

/tmp/pack1.sh:

echo "$0: PID: $$ PGID: $( ps -q $$ -o pgid= ) SID=$( ps -q $$ -o sid= )"
echo "  >> Sourcing my_quit..."
. /tmp/my_quit.sh
echo "  >> Executing my_quit..."
/tmp/my_quit.sh

/tmp/pack2.sh:

echo "$0: PID: $$ PGID: $( ps -q $$ -o pgid= ) SID=$( ps -q $$ -o sid= )"
echo "Sourcing pack1..."
. /tmp/pack1.sh
echo "Executing pack1"
/tmp/pack1.sh

Use cases

Executing die_if_fatal (my_quit.sh) directly from shell - no script above

Execute the script:

[kan@pckan ~]$ /tmp/my_quit.sh 
      >> Kill 11360
Finished
[kan@pckan ~]$

Source the script:

[kan@pckan ~]$ . /tmp/my_quit.sh 
      >> Sourced from a session leader. Will not end the process.
[kan@pckan ~]$ 

Executing from pack1.sh - 1 level of nesting

Executing pack1.sh from shell:

[kan@pckan ~]$ /tmp/pack1.sh 
/tmp/pack1.sh: PID: 11260 PGID: 11260 SID= 1630
  >> Sourcing my_quit...
      >> Kill 11260
Finished
[kan@pckan ~]$

Sourcing pack1.sh from shell:

[kan@pckan ~]$ . /tmp/pack1.sh 
/bin/bash: PID: 1630 PGID:  1630 SID= 1630
  >> Sourcing my_quit...
      >> Sourced from a session leader. Will not end the process.
  >> Executing my_quit...
      >> Kill 11316
Finished
[kan@pckan ~]$

Executing from pack2.sh - 2 (and possibly more) levels of nesting

Executing pack2.sh from shell:

[kan@pckan ~]$ /tmp/pack2.sh
/tmp/pack2.sh: PID: 11535 PGID: 11535 SID= 1630
Sourcing pack1...
/tmp/pack2.sh: PID: 11535 PGID: 11535 SID= 1630
  >> Sourcing my_quit...
      >> Kill 11535
Finished
[kan@pckan ~]$ 

Sourcing pack2.sh from shell:

[kan@pckan ~]$ . /tmp/pack2.sh
/bin/bash: PID: 1630 PGID:  1630 SID= 1630
Sourcing pack1...
/bin/bash: PID: 1630 PGID:  1630 SID= 1630
  >> Sourcing my_quit...
      >> Sourced from a session leader. Will not end the process.
  >> Executing my_quit...
      >> Kill 11618
Finished
Executing pack1
/tmp/pack1.sh: PID: 11627 PGID: 11627 SID= 1630
  >> Sourcing my_quit...
      >> Kill 11627
Finished
[kan@pckan ~]$ 

Comments

1

The script already knows its pid via $$. It can commit suicide easily.

kill -SIGPIPE "$$"  # Die with exit code 141

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.