在没有 while 循环的情况下使用 bash 的 read 内置

Ban*_*uin 10 bash pipe shell-script subshell read

我已经习惯了 while 循环中bash的内置read函数,例如:

echo "0 1
      1 1
      1 2
      2 3" |\
while read A B; do
    echo $A + $B | bc;
done
Run Code Online (Sandbox Code Playgroud)

我一直在做一些make项目,拆分文件和存储中间结果变得谨慎。因此,我经常最终将单行分解为变量。虽然下面的例子效果很好,

head -n1 somefile | while read A B C D E FOO; do [... use vars here ...]; done
Run Code Online (Sandbox Code Playgroud)

这有点愚蠢,因为 while 循环永远不会运行超过一次。但没有while,

head -n1 somefile | read A B C D E FOO; [... use vars here ...]
Run Code Online (Sandbox Code Playgroud)

当我使用它们时,读取变量总是空的。我从未注意到 的这种行为read,因为通常我会使用 while 循环来处理许多类似的行。如何在没有 while 循环的情况下使用bash's readbuiltin?或者是否有另一种(甚至更好)的方法将一行读入多个(!)变量?

结论

答案告诉我们,这是一个范围界定的问题。该声明

 cmd0; cmd1; cmd2 | cmd3; cmd4
Run Code Online (Sandbox Code Playgroud)

被解释为使得命令cmd0cmd1cmd4在同一范围内执行,而命令cmd2cmd3每个都被赋予自己的子shell,因此具有不同的范围。原始外壳是两个子外壳的父级。

cha*_*aos 10

这是因为您使用 vars 的部分是一组新命令。改用这个:

head somefile | { read A B C D E FOO; echo $A $B $C $D $E $FOO; }
Run Code Online (Sandbox Code Playgroud)

请注意,在此语法中,在 之后{;之前必须有一个空格(分号)}。也-n1没有必要; read只读取第一行。

为了更好地理解,这可能对您有所帮助;它的作用与上面相同:

read A B C D E FOO < <(head somefile); echo $A $B $C $D $E $FOO
Run Code Online (Sandbox Code Playgroud)

编辑:

人们常说接下来的两个语句做同样的事情:

head somefile | read A B C D E FOO
read A B C D E FOO < <(head somefile)
Run Code Online (Sandbox Code Playgroud)

嗯,不完全是。第一个是来自headtobashread内置管道。一个进程的标准输出到另一个进程的标准输入。

第二个语句是重定向和进程替换。它bash自己处理。它创建一个 FIFO(命名管道<(...)),该 FIFOhead的输出连接到该 FIFO ,并将其重定向 ( <) 到read进程。

到目前为止,这些看起来是等价的。但是当使用变量时,它可能很重要。在第一个中,变量在执行后没有设置。在第二个中,它们在当前环境中可用。

在这种情况下,每个 shell 都有另一种行为。请参阅它们所在的链接。在bash你可以解决该行为与指挥编组{},进程替换(< <())或以下字符串(<<<)。

  • 我想我明白了:实际的一点是,当使用管道时,bash 会为管道的每个进程创建子外壳,并且由于作用域范围,变量在管道之后消失了。使用“{}”使该行的其余部分成为管道的(一个!)元素,而在该子外壳内,变量可用。使用进程替换不使用管道,因此`bash` 不会产生子shell。 (2认同)