8

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?

fedorqui
  • 275,237
  • 103
  • 548
  • 598

3 Answers3

8

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.

Tom Fenech
  • 72,334
  • 12
  • 107
  • 141
  • Ooooh, so `v=1 | echo "hi"` would be the same case. – fedorqui Jun 09 '15 at 09:09
  • Yes, the change to `v` would only last for the lifetime of the subshell. – Tom Fenech Jun 09 '15 at 09:12
  • 2
    True, because `v=2; v=1 | echo "hi, v=$v"` return "hi, v=2". Interesting, many thanks! – fedorqui Jun 09 '15 at 09:13
  • 1
    @fedorqui Look at my answer [here](http://stackoverflow.com/questions/30458681/bash-piping-output-from-a-loop-seems-to-change-the-scope-within-the-loop-why/30459206#30459206). It's pretty much the same thing. – 123 Jun 09 '15 at 09:14
4

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)

fedorqui
  • 275,237
  • 103
  • 548
  • 598
anubhava
  • 761,203
  • 64
  • 569
  • 643
0

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

alchemy
  • 954
  • 10
  • 17