3

I need to know if Bash has some solution for my case. I need after some conditions to do a "double return". I mean, to perform a return of a function and also return the parent function to skip the rest of the code of that parent function.

I know that I can do a conditional using function return values to achieve this. But I'd like to know if in Bash exist something like "break 2" for functions. I don't want if possible to modify the code of the parent function because as you can imagine, in my real script there are dozens of functions and I don't want to modify all of them.

Example:

#!/bin/bash

function sublevelone() {
    echo "sublevelone"
    # Return 2, or break 2 or something :D
}

function main() {
    sublevelone
    echo "This is the part of the code to being avoid executed"
}

main
OscarAkaElvis
  • 5,384
  • 4
  • 27
  • 51
  • 1
    Sorry, I don't think you will find that in any major programming languages, other than out-of-band mechanisms like setjmp and exceptions, and bash doesn't have either of those mechanisms. – Gem Taylor Sep 25 '19 at 17:39
  • I think the best you could do is return a value and use if statements to return. But you'd be writing a lot of code anyway, then. – CrippledTable Sep 25 '19 at 17:45
  • I have no idea why you would, but could you use subshells? – that other guy Sep 25 '19 at 18:29

4 Answers4

5

I don't know what the bash experts will think, but this works at least for simple cases:

multireturn(){
    [ -n "$1" ] && poplevel="$1"
    if [ "$poplevel" -ge 0 ]; then
        trap multireturn DEBUG
        shopt -s extdebug
        (( poplevel-- ))
        return 2
    else
        shopt -u extdebug
        trap - DEBUG
        return 0
    fi
}

This makes use of the DEBUG trap and the extdebug flag:

extdebug
    If  set  at  shell  invocation,  arrange  to execute the
    debugger profile before the shell starts,  identical  to
    the  --debugger option.  If set after invocation, behav-
    ior intended for use by debuggers is enabled:
    1.     The -F option to the declare builtin displays the
           source file name and line number corresponding to
           each function name supplied as an argument.
    2.     If the command run by the DEBUG  trap  returns  a
           non-zero  value,  the next command is skipped and
           not executed.
    3.     If the command run by the DEBUG  trap  returns  a
           value  of 2, and the shell is executing in a sub-
           routine (a shell function or a shell script  exe-
           cuted  by  the  .  or source builtins), the shell
           simulates a call to return.
    4.     BASH_ARGC and BASH_ARGV are updated as  described
           in their descriptions above.
    5.     Function  tracing  is  enabled: command substitu-
           tion, shell functions, and subshells invoked with
           ( command ) inherit the DEBUG and RETURN traps.
    6.     Error  tracing  is enabled: command substitution,
           shell functions, and  subshells  invoked  with  (
           command ) inherit the ERR trap.

Example usage:

#!/bin/bash

multireturn(){
    [ -n "$1" ] && poplevel="$1"
    if [ "$poplevel" -ge 0 ]; then
        trap multireturn DEBUG
        shopt -s extdebug
        (( poplevel-- ))
        return 2
    else
        shopt -u extdebug
        trap - DEBUG
        return 0
    fi
}

# define 8 levels of function calls
# (level N prints output, calls level N+1, then prints more output)
for i in $(seq 1 8); do
    eval \
'level'$i'(){
    echo -n " '$i'"
    level'$((i+1))'
    echo -n "('$i')"
}'
done

# final level calls multireturn
level9(){
    echo -n " 9"
    multireturn $n
    echo -n "(9)"
}

# test various skip amounts
for i in $(seq 0 10); do
    echo -n "$i:"
    n=$i
    level1
    echo .
done

echo
echo done

Result:

0: 1 2 3 4 5 6 7 8 9(9)(8)(7)(6)(5)(4)(3)(2)(1).
1: 1 2 3 4 5 6 7 8 9(8)(7)(6)(5)(4)(3)(2)(1).
2: 1 2 3 4 5 6 7 8 9(7)(6)(5)(4)(3)(2)(1).
3: 1 2 3 4 5 6 7 8 9(6)(5)(4)(3)(2)(1).
4: 1 2 3 4 5 6 7 8 9(5)(4)(3)(2)(1).
5: 1 2 3 4 5 6 7 8 9(4)(3)(2)(1).
6: 1 2 3 4 5 6 7 8 9(3)(2)(1).
7: 1 2 3 4 5 6 7 8 9(2)(1).
8: 1 2 3 4 5 6 7 8 9(1).
9: 1 2 3 4 5 6 7 8 9.
10: 1 2 3 4 5 6 7 8 9
done
John Kugelman
  • 349,597
  • 67
  • 533
  • 578
jhnc
  • 11,310
  • 1
  • 9
  • 26
  • 1
    btw, re "simple cases", it is important to note that my function is [non-reentrant](https://en.wikipedia.org/wiki/Reentrancy_(computing)) – jhnc Mar 08 '21 at 18:26
2

This is kinda whacky but if you use parentheses to define levelone, it will execute the function in a subshell and then you can exit out of that shell from an inner function. That said, I think it's more appropriate to use return to send back value that you check for in the parent function.

#!/bin/bash

function leveltwo() {
        echo "two"
        exit
}

function levelone() (
        echo "one"
        leveltwo
        echo "three"
)

levelone
echo "four"

Will print:

one
two
four

informatik01
  • 16,038
  • 10
  • 74
  • 104
Neil
  • 345
  • 6
  • 14
  • Wow... this seems to be working as I want. Thanks... but, can I ask why exit is only exiting one function level and is not exiting all the script? I'll do some tests before marking as accepted answer – OscarAkaElvis Sep 27 '19 at 08:56
  • 2
    level one is defined with parentheses instead of curly braces, this makes it execute in a sub-shell. so when you exit it leaves the sub-shell, does that make sense? – Neil Sep 27 '19 at 13:29
1

Why don't you return an exit status from the function and add an if-statement to main like you normally would in any other programming/scripting language?

#!/bin/bash

function sublevelone() {
    echo "sublevelone"
    [ 0 -eq 1 ]
    return $? # Returns 1 in this case because 0 != 1
}

function main() {
    sublevelone
    [ $? -eq 0 ] || return 1 # Return in case of error
    echo "This is the part of the code to being avoid executed"
}

main
exit 0
Bayou
  • 3,293
  • 1
  • 9
  • 22
1

Just a short version of @jhnc answer for exactly 2-level return from a function:

trap 'trap "shopt -u extdebug; trap - DEBUG; return 0" DEBUG; return 2' DEBUG
shopt -s extdebug
return
Alek
  • 634
  • 7
  • 7