6

If I have a function:

myfunction() {
  [ -d somedirectory ] && [ "some other condition" ] || { echo "error" >&2 ; exit 1; }
  global_var=somevalue
}

And I call it from within another function:

some_other_function() {
  myfunction
  # Do something with "$global_var"
}

This works as expected: If the conditions in myfunction fail, the error exit kills the whole script to prevent additional code from executing.

In refactoring a very large script that shares function definitions with other scripts (in a sourced file), I want to get rid of some of the global variables by returning them like so:

myfunction() {
  [ -d somedirectory ] && [ "some other condition" ] || { echo "error" >&2 ; exit 1; }
  local somevar=somevalue
  # do stuff with "$somevar", then...
  # return value of somevar to calling function.
  printf %s "$somevar"
}

some_other_function() {
  local anothervar="$(myfunction)"
  # Do something with "$another_var"
}

However, the error exit here fails to work as intended. Instead of killing the script, it only kills that function, which is executed in a subshell because of command substitution.

Is there a way to modularize this large script to allow text values to be returned from functions (rather than using global variables) and still allow functions to error exit from the entire script?

5
  • 2
    If you want to exit for every error that happens in your script, you can set -e.
    – pfnuesel
    Jan 21, 2016 at 21:49
  • In some_other_function you have local anothervar=$(myfunction). The $(myfunction) runs in a separate instance of the shell. The exit in myfunction exits that subshell, not the "parent" shell. Jan 21, 2016 at 21:50
  • 1
    @AndyDalton, thanks, but I know that...I said it fails to work as intended, not that it fails to work as expected (and I mentioned the subshell issue). I want to know how to make it work as intended.
    – Wildcard
    Jan 21, 2016 at 21:53
  • set -e and echo "error" >&2 ; exit 1; works for me. It first prints "error" then exits. May 2, 2018 at 11:59
  • @pfnuesel that doesn't work for errors in command substitutions
    – OrangeDog
    Jan 9 at 15:35

2 Answers 2

3

You have to send a signal to the main shell:

# start of the main script:
MAIN_SHELL_PID=$$

[...]

myfunction() {
    ... || { echo "error" >&2 ; kill -HUP "$MAIN_SHELL_PID"; }
}
2
  • 2
    You don't need the variable MAIN_SHELL_PID. Using $$ will work just as well. Jan 22, 2016 at 22:48
  • with this, you can make a function exit_all () { kill -HUP $$ } and reuse it, plus it's more readable. Aug 14, 2018 at 8:55
2

An easier solution is to write the local declaration and the assignment in two separate statements: This causes the function's exit status to survive as the exit status of the assignment and thus helps to kill the while script in case of an error in the function:

some_other_function() {
  local anothervar
  anothervar="$(myfunction)"
  # Do something with "$another_var"
}

More detailed explanation.

1
  • The magic here is splitting the local declaration from the assignment. See Shellcheck warning SC2155 for an explanation. @Wildcard any reason this isn't accepted?
    – Tom Hale
    Jun 30 at 7:03

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .