5

If I have a function like this:

TEST()
{
    if [[ "$1" == "hi" ]]
    then
        exit 1
    fi

    echo "Some text"
}

and if I run the function in the current shell with:

TEST "hi"

everything works as expected. The if statement in the function will be true, and the entire script will exit. If I do this on the other hand:

FUNCTION_OUTPUT=$(TEST "hi")

So that I can catch the stdout from the function in a variable, the if statement inside the function will still be true, the "exit 1" will still fire, but since we are running in a subshell, the script will keep going.

Instead of using VAR_NAME=$() to run something in a subshell and assign it to a variable, is there a way to run it in the current shell so that the "exit 1" line in my function actually exits the entire script?

2
  • It won't work with functions to my knowledge, but the source command lets you run a file without spawning a subshell.
    – Wildcard
    Commented Oct 8, 2015 at 1:25
  • 1
    since the function is running in the current shell, you can set global variables or other side-effects within the function. Commented Oct 8, 2015 at 1:33

4 Answers 4

4

Variable assignment is just a Simple Command, so you can use the if condition to check whether function success of fail:

if ! FUNCTION_OUTPUT=$(TEST hi); then
  echo Function return non-zero status
  exit 1
fi

# This line never printed
printf '%s\n' "$FUNCTION_OUTPUT"

If the function success, you will have the variable FUNCTION_OUTPUT with the result of function:

if ! FUNCTION_OUTPUT=$(TEST hii); then
  echo Function return non-zero status
  exit 1
fi

# Output content of FUNCTION_OUTPUT
printf '%s\n' "$FUNCTION_OUTPUT"
2
  • 4
    In addition to that, the function should have return 1 rather than exit 1 so that the behaviour is exactly the same whether it is called in a sub-shell or not. The caller should check the return value, just as the caller should check the return value from an external program that it calls (if that return value is important)
    – cas
    Commented Oct 8, 2015 at 3:11
  • TEST is still called inside a subshell, and any variable set inside TEST will be lost. Commented Mar 24, 2021 at 8:55
1

Redirect the command to a file and read the file into a variable with a builtin, e.g. with mapfile.

1

There are a lot of ways to exit a script.

VAR=$(FUNCTION) || kill -"$(($?&127))" 0

...might be one way. It should pass on to the parent shell's process group whatever the return is from FUNCTION. It's not really fleshed out, but you can easily get the return from a command substitution if that is what you are asking. Else if you want suggestions about assigning a variable, well, from within a current shell function you can do it like:

fn(){ var=x; }; fn; echo "$var"

x

If it must be some command's output then you're talking about layered evaluation. You really need to trust that output, or else you need to capture the entire stream, in which case you'd better be sure about when to expect it. Such things are usually done with pipes - which is how command substitutions work - well, that and subshells, of course. Just as the shell does, you have to read the data in, as is elsewhere suggested.

I like to use my own little pipe - I write a little at a time to the pipe's buffer and read it in again when I'm ready.

fn(){ echo hey; read hey; } <> <(:) >&0
fn; echo "$hey"

hey

...I guess I should mention that if you follow this last example you should take special care not to fill the buffer. Only write a little at a time, and read it in as soon as you can. On practically any system you can count on a baseline 512 byte buffer - which isn't much - but they are usually bigger.

If you're not careful, then, as the owner of both ends of that pipe, your shell won't receive any frustrated quit signals from any other process when it deadlocks, and it will just be stuck forever. I never point the output of any non-builtin command at one of those file-descriptors unless I've already forked off a reader process to drain the pipe as necessary.

Its stuff like that which makes same process messaging difficult - you have to manage it meticulously, in perfect synchronicity, and track all the separate threads of a task simultaneously. That's why shells fork.

1

I use:

tmp_fifo=$(mktemp)
mkfifo ${tmp_fifo}
exec 5<>${tmp_fifo}

assign() {
  local var=$1;
  shift
  "$@" >&5
  read ${var} <&5
}

assign myVar echo hola
echo $myVar

rm -f ${tmp_fifo}

You must log in to answer this question.

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