Unix & Linux
bash pipe read subshell
Updated Fri, 29 Jul 2022 15:55:46 GMT

Why must I put the command read into a subshell while using pipeline


The command df . can show us which device we are on. For example,

me@ubuntu1804:~$ df .
Filesystem     1K-blocks    Used Available Use% Mounted on
/dev/sdb1       61664044 8510340  49991644  15% /home

Now I want to get the string /dev/sdb1.

I tried like this but it didn't work: df . | read a; read a b; echo "$a", this command gave me an empty output. But df . | (read a; read a b; echo "$a") will work as expected.

I'm kind of confused now.

I know that (read a; read a b; echo "$a") is a subshell, but I don't know why I have to make a subshell here. As my understanding, x|y will redirect the output of x to the input of y. Why read a; read a b; echo $a can't get the input but a subshell can?




Solution

The main problem here is grouping the commands correctly. Subshells are a secondary issue.

x|y will redirect the output of x to the input of y

Yes, but x | y; z isn't going to redirect the output of x to both y and z.

In df . | read a; read a b; echo "$a", the pipeline only connects df . and read a, the other commands have no connection to that pipeline. You have to group the reads together: df . | { read a; read a b; } or df . | (read a; read a b) for the pipeline to be connected to both of them.

However, now comes the subshell issue: commands in a pipeline are run in a subshell, so setting a variable in them doesn't affect the parent shell. So the echo command has to be in the same subshell as the reads. So: df . | { read a; read a b; echo "$a"; }.

Now whether you use ( ... ) or { ...; } makes no particular difference here since the commands in a pipeline are run in subshells anyway.