为什么我必须在使用管道时将读取的命令放入子shell中

为什么我必须在使用管道时将读取的命令放入子shell中

该命令df .可以显示我们所在的设备。例如,

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

现在我想获取字符串/dev/sdb1

我尝试这样做,但它不起作用:df . | read a; read a b; echo "$a",这个命令给了我一个空的输出。但df . | (read a; read a b; echo "$a")会按预期工作。

我现在有点困惑。

我知道这(read a; read a b; echo "$a")是一个子shell,但我不知道为什么我必须在这里制作一个子shell。据我了解,x|y会将 的输出重定向x到 的输入y。为什么read a; read a b; echo $a无法获取输入但子 shell 可以?

答案1

这里的主要问题是正确地对命令进行分组。子shell 是次要问题。

x|y将重定向输出x到输入y

是的,但x | y; z不会将 的输出重定向xyz

在 中df . | read a; read a b; echo "$a",管道仅连接df .read a,其他命令与该管道没有连接。您必须将read它们组合在一起:df . | { read a; read a b; }或者df . | (read a; read a b)将管道连接到它们。

但是,现在出现了子 shell 问题:管道中的命令在子 shell 中运行,因此在其中设置变量不会影响父 shell。因此该echo命令必须与reads 位于同一子 shell 中。所以:df . | { read a; read a b; echo "$a"; }

现在,无论您使用( ... )还是{ ...; }使用,都没有什么特别的区别,因为管道中的命令无论如何都在子 shell 中运行。

答案2

另一种方法是使用流程替代:

{ read header; read filesystem rest; } < <(df .)
echo "$filesystem"

进程<(...)替换执行包含的脚本(在子 shell 中),但它的作用类似于文件名,因此您需要首先<将内容(即脚本的输出)重定向到大括号脚本中。分组的命令在当前的shell中执行;。

使其可读可能很困难,但您可以将任意空格放入大括号和过程替换中。

{
    read header
    read filesystem rest
} < <(
    df .
)
echo "$filesystem"

使用外部工具提取文件系统可能会更容易:

filesystem=$( df . | awk 'NR == 2 {print $1}' )

答案3

你的第一个命令

df . | read a; read a b; echo "$a"

实际上被解释为

( df . | read a ) ; read a b; echo "$a"

所以管道只输入read a命令。

由于您希望从管道中进行多次读取,因此您需要将命令分组在一起。

现在它不必是一个子shell;而是一个子shell。可以是一个分组..

bash-4.2$ df | { read a ; read a b ; echo $a ; }
devtmpfs

更常见的是,您可能需要一个循环

bash-4.2$ df | while read a
> do
> read a b
> echo $a
> done
devtmpfs
tmpfs
/dev/vda3
/dev/vdb

还有一个次要问题是,bash管道的右侧正在运行子 shell,因此$a $b无法在while循环外部访问这些值,但这是一个不同的问题!

相关内容