Grouping commands in curly braces and piping does not preserve variable

3.8k Views Asked by At

Say I have a file myfile in my current working directory. I want to set a variable if a command executes normally, but also use its result.

$ ls myfile && v=3
myfile
$ echo "$v"
3

But now I also want to pipe the result, so I use the { list; } syntax to group the commands:

$ unset v
$ { ls myfile && v=3; } | grep myf
myfile
$ echo "$v"
                  # v is not set

Bash reference manual -> 3.2.4.3 Grouping Commands says:

{ list; }

Placing a list of commands between curly braces causes the list to be executed in the current shell context. No subshell is created. The semicolon (or newline) following list is required.

So, to my understanding, v should be set to 3. But it is not happening. Why?

3

There are 3 best solutions below

4
On BEST ANSWER

It's not the curly braces that are causing a subshell to be created, it's the pipe.

To prove it:

$ { ls && v=3; } > tmp
$ echo "$v"
3

To quote Greg:

In most shells, each command of a pipeline is executed in a separate SubShell.

2
On

You can use BASH_SUBSHELL variable to verify whether you're in subshell or not.

# BASH_SUBSHELL will print level of subshell from top due to pipe
{ unset v && ls file && v=3 && echo "$BASH_SUBSHELL - $v"; } | nl
     1  file
     2  1 - 3

# outside BASH_SUBSHELL will print 0
echo "$BASH_SUBSHELL - $v";
0 -

You can use for piped command it prints 1 meaning it is in a subshell hence value of v isn't available outside (evident from 2nd output)

0
On

You need to use read and curly braces to do what you want:

ls myfile && echo success | { read result; echo $result; } 

Here's more about why: https://unix.stackexchange.com/questions/338000/bash-assign-output-of-pipe-to-a-variable