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 
 有人能解释一下这里的逻辑吗? 是不是在while … done构造中的命令实际上是在与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 进程replace , Here Documents或Here 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 AB和echo $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