fcm
fcm

Reputation: 1331

bash exit script from inside a function

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:

Now, if the script is executed: chmod +x script; ./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?

Upvotes: 7

Views: 9052

Answers (3)

sqqqrly
sqqqrly

Reputation: 893

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

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

Upvotes: 1

Krzysztof Kaszkowiak
Krzysztof Kaszkowiak

Reputation: 896

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 ~]$ 

Upvotes: 1

Leon
Leon

Reputation: 32514

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

Upvotes: 2

Related Questions