I am trying to implement a single command that I assume would be a wrapper for the normal 'exit' and 'return' shell builtins that come with Bash (Bourne, et al), a command that is not plagued by the incompatibility issues of these in that if I use 'exit 1' to end a script with error level 1, if I source that script it will cause the shell I am in to terminate.
Likewise, if I use return, it has the problems that: a) It will only return to the calling function, not end the whole script being run without additional logic. b) If I use return in the main script and execute it, it will error rather than terminating with a level.
The best solution I have come up with is this:
All shell scripts I write start with this preamble anyway to get some stable base variables:
# Core Functions and Alias definitions Used For Script Setup
getScriptFile () { basename "${BASH_SOURCE[0]}" ; } # This filename as executed
getScriptPath () { local f="${BASH_SOURCE[0]}" ; local p ; while test -L "${f}"
do p="$( cd -P "$( dirname "${f}" )" && pwd )" ; f="$( readlink "${f}" )" &&
f="${p}/${f}" ; done ; p="$( cd -P "$( dirname "${f}" )" && pwd )" ;
printf "%s\n" "${p}" ; }
shopt -s expand_aliases
SCRIPTpath="$( getScriptPath ; )"
SCRIPTfile="$( getScriptFile ; )"
So, now I add this to it:
testEq () { [ "${1}" = "${2}" ] ; } # True if there is equality
testMatch () { [[ ${1} =~ ${2} ]] ; } # True if ARG1 MATCHES the Extended RegEx in ARG2
testInt () { [ "${1}" -eq "${1}" ] 2>/dev/null ; } # I know this is okay with bash, to test with bourne
testIntLt () { testInt "${1}" && testInt "${2}" && [ "${1}" -lt "${2}" ] ; }
testSourced () { local c="0" ; local m="${#BASH_SOURCE[@]}" ;
until testEq "$( basename "${BASH_SOURCE[${c}]}" )" "${SCRIPTfile}" &&
testMatch "${FUNCNAME[${c}]}" "source|main" &&
testIntLt "${c}" "${m}" ; do ((c++)) ; done ;
if testEq "source" "${FUNCNAME[${c}]}" ; then return 0 ;
else return 1 ; fi ; } # True if calling script is sourced into environment
getValidTerminationCommand () { testSourced && printf "%s" "return" || printf "%s" "exit" ; }
alias finish='eval $( getValidTerminationCommand ; )'
...and when I want to finish a script abruptly, I use my new finish command, i.e.
$ finish 5 <-- to finish with an error level of 5
Now, regardless of if the script is sourced or executed, I get an exit code of 5. No errors for the wrong type of "exit" or "return" .
The reason I have the complication on the getScriptPath command is to deal with script files that might live on a filesystem path that is under symlinks.
The reason for the complication on testSourced is that the test needs to work even if the script that is testing if it is sourced is being done so by a calling script, i.e. it needs to make sure that it is testing the fact that that itself is sourced, not say a calling script.
Now, I know that the normal way of doing this is as simple as follows:
$ return 5 2>/dev/null || exit 5 # If I want to end a script with error level 5
Problem with that is that I cannot figure out how to wrap it to parameterize it so that I can use it as a 'finish' or 'terminate' command that will terminate the script with this error level as if I alias it, I can do this without a code for the return, or if it functionise it then it will just return to the calling function/script if it has one.
There has got to be a portable, simpler way than what I came up with on my implementation of 'finish' above!?! Surely this is a standard thing to do?
Anyone solved this before?? Am I missing something obvious?
Just to confirm, what I want to make is this: - single command to instantly terminate a script - same command if script is executed, sourced - same command if used in a function, the main script body, or a sub function - behave the same if the script is called or sourced either directly or by another script that may be executed or sourced, (or potentially n levels of execution leading up to the script that is being run). - use as standard commands to the Bash shell (Bourne if possible) so as portable as possible.
Anyone have some tool they use for this job? please let me know?
Thanks in anticipation!?! :)
Test script that I used to try this:
#!/bin/bash
# Core Functions and Alias definitions Used For Script Setup
getScriptFile () { basename "${BASH_SOURCE[0]}" ; } # This filename as executed
getScriptPath () { local f="${BASH_SOURCE[0]}" ; local p ; while test -L "${f}"
do p="$( cd -P "$( dirname "${f}" )" && pwd )" ; f="$( readlink "${f}" )" &&
f="${p}/${f}" ; done ; p="$( cd -P "$( dirname "${f}" )" && pwd )" ;
printf "%s\n" "${p}" ; }
shopt -s expand_aliases
SCRIPTpath="$( getScriptPath ; )"
SCRIPTfile="$( getScriptFile ; )"
testEq () { [ "${1}" = "${2}" ] ; } # True if there is equality
testMatch () { [[ ${1} =~ ${2} ]] ; } # True if ARG1 MATCHES the Extended RegEx in ARG2
testInt () { [ "${1}" -eq "${1}" ] 2>/dev/null ; } # I know this is okay with bash, to test with bourne
testIntLt () { testInt "${1}" && testInt "${2}" && [ "${1}" -lt "${2}" ] ; }
testSourced () { local c="0" ; local m="${#BASH_SOURCE[@]}" ; until testEq "$( basename "${BASH_SOURCE[${c}]}" )" "${SCRIPTfile}" && testMatch "${FUNCNAME[${c}]}" "source|main" && testIntLt "${c}" "${m}" ; do ((c++)) ; done ; if testEq "source" "${FUNCNAME[${c}]}" ; then return 0 ; else return 1 ; fi ; } # True if calling script is sourced into environment
getValidTerminationCommand () { testSourced && printf "%s" "return" || printf "%s" "exit" ; }
alias finish='eval $( getValidTerminationCommand ; )'
anotherTestFunc ()
{
echo "anotherTestFunc ()"
finish 10
echo "anotherTestFunc () finish"
}
testFunc ()
{
echo "testFunc ()"
anotherTestFunc
echo "testFunc () finish"
}
echo test start
testFunc
echo more outside text