Bash:为什么pipe道input“读取”只有在喂入“while read …”构造时才起作用?

我一直在尝试从程序输出读取环境variables的input,如下所示:

echo first second | read AB ; echo $A-$B 

结果是:

 - 

A和B都是空的。 我读了关于在子shell中执行pipe道命令的bash,并且基本上阻止了pipe道input读取。 但是,以下内容:

 echo first second | while read AB ; do echo $A-$B ; done 

似乎工作,结果是:

 first-second 

有人能解释一下这里的逻辑吗? 是不是在whiledone构造中的命令实际上是在与echo相同的shell中执行的,而不是在子shell中执行的?

如何对stdin进行循环,并将结果存储在variables中

在bash (也是其他shell )下,当你用|pipe道时 到另一个命令,你将隐式地创build一个fork ,一个子shell,它是当前会话的子元素,并且不会影响当前会话的环境。

所以这:

 TOTAL=0 printf "%s %s\n" 9 4 3 1 77 2 25 12 226 664 | while read AB;do ((TOTAL+=AB)) printf "%3d - %3d = %4d -> TOTAL= %4d\n" $A $B $[AB] $TOTAL done echo final total: $TOTAL 

不会给预期的结果! :

  9 - 4 = 5 -> TOTAL= 5 3 - 1 = 2 -> TOTAL= 7 77 - 2 = 75 -> TOTAL= 82 25 - 12 = 13 -> TOTAL= 95 226 - 664 = -438 -> TOTAL= -343 echo final total: $TOTAL final total: 0 

在计算TOTAL不能在主脚本中重用的地方。

倒置叉子

通过使用bash 进程replaceHere DocumentsHere Strings ,你可以反转这个fork:

这里串

 read AB <<<"first second" echo $A first echo $B second 

这里的文件

 while read AB;do echo $A-$B C=$A-$B done << eodoc first second third fourth eodoc first-second third-fourth 

循环之外:

 echo : $C : third-fourth 

这里的命令

 TOTAL=0 while read AB;do ((TOTAL+=AB)) printf "%3d - %3d = %4d -> TOTAL= %4d\n" $A $B $[AB] $TOTAL done < <( printf "%s %s\n" 9 4 3 1 77 2 25 12 226 664 ) 9 - 4 = 5 -> TOTAL= 5 3 - 1 = 2 -> TOTAL= 7 77 - 2 = 75 -> TOTAL= 82 25 - 12 = 13 -> TOTAL= 95 226 - 664 = -438 -> TOTAL= -343 # and finally out of loop: echo $TOTAL -343 

现在,您可以在主脚本中使用$TOTAL

pipe道到命令列表

但是仅仅针对stdin ,你可能会在fork中创build一种脚本:

 printf "%s %s\n" 9 4 3 1 77 2 25 12 226 664 | { TOTAL=0 while read AB;do ((TOTAL+=AB)) printf "%3d - %3d = %4d -> TOTAL= %4d\n" $A $B $[AB] $TOTAL done echo "Out of the loop total:" $TOTAL } 

会给:

  9 - 4 = 5 -> TOTAL= 5 3 - 1 = 2 -> TOTAL= 7 77 - 2 = 75 -> TOTAL= 82 25 - 12 = 13 -> TOTAL= 95 226 - 664 = -438 -> TOTAL= -343 Out of the loop total: -343 

注意: $TOTAL不能在主脚本中使用 (在右上括号之后))。

使用lastpipe bash选项

正如@CharlesDuffy正确指出的,有一个bash选项用于改变这种行为。 但为此,我们必须先禁用 任务控制

 shopt -s lastpipe # Set *lastpipe* option set +m # Disabling job control TOTAL=0 printf "%s %s\n" 9 4 3 1 77 2 25 12 226 664 | while read AB;do ((TOTAL+=AB)) printf "%3d - %3d = %4d -> TOTAL= %4d\n" $A $B $[AB] $TOTAL done 9 - 4 = 5 -> TOTAL= -338 3 - 1 = 2 -> TOTAL= -336 77 - 2 = 75 -> TOTAL= -261 25 - 12 = 13 -> TOTAL= -248 226 - 664 = -438 -> TOTAL= -686 echo final total: $TOTAL -343 

这将工作,但我(个人)不喜欢这个,因为这不是标准的 ,并不会帮助使脚本可读。 同时禁用作业控制似乎是昂贵的访问这种行为。

注意:只有在交互式会话中,默认情况下才启用作业控制 。 所以在普通脚本中不需要set +m

因此,如果在控制台中运行或者在脚本中运行,那么在脚本中被遗忘的set +m会创build不同的行为。 这不会使这个容易理解或debugging…

首先,这个pipe道链被执行:

 echo first second | read AB 

然后

 echo $A-$B 

由于read AB是在一个子shell中执行的,所以A和B都会丢失。 如果你这样做:

 echo first second | (read AB ; echo $A-$B) 

然后同时read ABecho $A-$B在相同的子shell中执行(请参阅bash的联机帮助页,search(list)

一个更清洁的工作…

 read -rab < <(echo "$first $second") echo "$a $b" 

这样,读取不会在子shell中执行(只要该子shell已经结束,就会清除variables)。 相反,你想要使用的variables会在一个子shell中回显,该子shell会自动从父shell中inheritancevariables。

你看到的是进程之间的分离: read发生在一个子shell中 – 一个单独的进程,不能改变主进程中的variables(稍后发生echo命令)。

一个stream水线(比如A | B )隐式地将每个组件放置在一个子shell(一个单独的进程)中,即使对于通常在shell的上下文中运行的内置(比如read )也是如此(在同一进程中)。

“pipe道入时”的情况是不一样的。 相同的规则适用于:循环是pipe道的后半部分,所以它在一个子shell中,但整个循环在同一个子shell中,所以进程的分离不适用。

如果将shell更改为ksh ,那么您的第一个示例工作即

 echo first second | read AB ; echo $A-$B