Reputation: 1331
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 scriptexit
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 scriptexit
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?
Upvotes: 7
Views: 9052
Reputation: 893
The script already knows its pid via $$. It can commit suicide easily.
kill -SIGPIPE "$$" # Die with exit code 141
Upvotes: 1
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 }'
.
/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
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 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 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
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