14

Edit:

My comment below regarding sed 's@^@ @' <(f1) is incorrect While $BASH_SUBSHELL indicates that we are in the same level as the launch, the variables are lost in the main script. based on Gordons answer I tested f1 > >(sed 's@^@ @') instead and that seems to work correctly. Still, shouldn't BASH_SUBSHELL should be 1 and not 0 for the first form?


Consider this small test

#!/bin/bash
declare -i i=0
function f1()
{
  let i++
  echo "In f1, SUBSHELL: $BASH_SUBSHELL, i=$i" >&2
}

f1
f1 | sed 's@^@     @'

echo "at end, i=$i"

with the following output:

In f1, SUBSHELL: 0, i=1
In f1, SUBSHELL: 1, i=2
at end, i=1

(the purpose of the sed is just to have a pipe to something, don't expect it to do anything because f1 outputs to stderr)

The function f1 logs the current BASH_SUBSHELL and the current value of i

I know why at the end of the script we get i=1, its because the second invocation was in a subshell, and the value of i at subshell 1 was lost.

What I don't know is why the left side of the pipe was not executed in the current shell

Though I figured that I could avoid this with sed 's@^@ @' <(f1) I would like to know why the left side is not at the same level as the main script

nhed
  • 5,774
  • 3
  • 30
  • 44
  • I think the shell is allowed to have both ends of the pipe in subshell – sehe Apr 22 '11 at 22:32
  • A quick google found this: http://www.linuxprogrammingblog.com/pipe-in-bash-can-be-a-trap – Brian Roach Apr 22 '11 at 22:37
  • @Brian that article's does not discuss the LEFT side of the pipe ... I already know that it is bad news for variable assignments on the right side of a pipe – nhed Apr 22 '11 at 22:51

2 Answers2

25

From the bash man page: "Each command in a pipeline is executed as a separate process (i.e., in a subshell)." I suppose it would be possible to execute one component of a pipeline in the current shell (i.e. the first, or the last, or maybe one in the middle), it doesn't play favorites like this: they all execute in subshells. If you modify your script like this:

#!/bin/bash
declare -i i=0
function f1()
{
    let i++
    echo "In f1, SUBSHELL: $BASH_SUBSHELL, i=$i" >&2
}

f1
f1 | f1 | f1

echo "at end, i=$i"

it prints:

In f1, SUBSHELL: 0, i=1
In f1, SUBSHELL: 1, i=2
In f1, SUBSHELL: 1, i=2
In f1, SUBSHELL: 1, i=2
at end, i=1

because all 3 invocations of f1 in the pipeline run in subshells.

Gordon Davisson
  • 118,432
  • 16
  • 123
  • 151
  • I guess my confusion comes from thinking of pipe and file redirection are somewhat equal `f1 < /etc/passwd`, for example, does not start a new subshell (and i figured if we are just temporarily redirecting a descriptor - why not be consistent about it). But as it is in the manpage - I guess it will have to do – nhed Apr 22 '11 at 23:14
  • 1
    @nhed: I wouldn't think of a pipe like an output redirection. A pipeline is a series of commands strung together by I/O redirections, and all run simultaneously; since the shell doesn't do multitasking, it can run at most one of them in the main shell, and since they're all basically equal there's no reason to pick one to run in the main shell, so for fairness they're all run in subshells. In bash, you can use `< <()` and `> >()` to explicitly relegate some of the commands to subshells, allowing one command to run in the main shell. – Gordon Davisson Apr 23 '11 at 04:42
  • 6
    @nhed: (continuing) for example, to force the first command in a pipeline to run in the main shell, use `cmd1 > >(cmd2 | cmd3 | ...)`. To run cmd2 in the main shell, use `cmd2 < <(cmd1) > >(cmd3 | ...)`, etc. – Gordon Davisson Apr 23 '11 at 04:45
  • thanks - I thought I was missing something (the additional `<` or `>` before the `<()` or `>()`). Still wondering about the BASH_SUBSHELL ... if it was in a subshell it should not print `0` – nhed Apr 23 '11 at 05:43
  • 3
    Minor quibble, as bash does play favorites: [`jobs` on the left-hand side of a pipe doesn't run in a sub-shell](http://unix.stackexchange.com/a/279792/50240). – bishop Apr 28 '16 at 17:07
  • <3 @GordonDavisson, save my day, i need exactly 1 must be in current shell – Dee Oct 04 '18 at 05:22
-2

Here is a much concise example if someone cares :

cd / && cd /tmp/ | pwd  ; pwd
/
/

Or :

cd / && cd /tmp/ | cd /var/  ; pwd
/

Yes this page says it all

http://linux.die.net/man/1/bash# Each command in a pipeline is executed as a separate process (i.e., in a subshell).

RzR
  • 3,068
  • 29
  • 26