Why piping input to “read” only works when fed into “while read …” construct?
我一直试图从程序输出中读取环境变量的输入,如下所示:
1 | echo first second | read A B ; echo $A-$B |
结果是:
1 | - |
A和B都是空的。我读到了关于bash在子shell中执行管道命令的内容,这基本上阻止了一个人通过管道输入来读取。但是,以下内容:
1 | echo first second | while read A B ; do echo $A-$B ; done |
似乎有效,结果是:
1 | first-second |
有人能解释一下这里的逻辑吗?是不是
如何对stdin执行循环并将结果存储在变量中
在bash(以及其他shell)下,当您使用
因此:
1 2 3 4 5 6 7 8 9 | TOTAL=0 printf"%s %s " 9 4 3 1 77 2 25 12 226 664 | while read A B;do ((TOTAL+=A-B)) printf"%3d - %3d = %4d -> TOTAL= %4d " $A $B $[A-B] $TOTAL done echo final total: $TOTAL |
不会给出预期的结果!:
1 2 3 4 5 6 7 | 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 |
其中计算的汇总不能在主脚本中重用。
倒转叉子通过使用bash进程替换、这里的文档或这里的字符串,您可以反转fork:
这里的字符串1 2 3 4 5 6 | read A B <<<"first second" echo $A first echo $B second |
这里的文件
1 2 3 4 5 6 7 8 9 | while read A B;do echo $A-$B C=$A-$B done << eodoc first second third fourth eodoc first-second third-fourth |
循环外:
1 2 | echo : $C : third-fourth |
这里命令
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | TOTAL=0 while read A B;do ((TOTAL+=A-B)) printf"%3d - %3d = %4d -> TOTAL= %4d " $A $B $[A-B] $TOTAL done < <( printf"%s %s " 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 |
现在您可以在主脚本中使用
但对于只针对stdin的工作,您可以在fork中创建一种脚本:
1 2 3 4 5 6 7 8 9 10 | printf"%s %s " 9 4 3 1 77 2 25 12 226 664 | { TOTAL=0 while read A B;do ((TOTAL+=A-B)) printf"%3d - %3d = %4d -> TOTAL= %4d " $A $B $[A-B] $TOTAL done echo"Out of the loop total:" $TOTAL } |
将给予:
1 2 3 4 5 6 | 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 |
注意:主脚本中不能使用
正如@charlesduffy正确指出的那样,有一个bash选项用来改变这种行为。但为此,我们必须首先禁用作业控制:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | shopt -s lastpipe # Set *lastpipe* option set +m # Disabling job control TOTAL=0 printf"%s %s " 9 4 3 1 77 2 25 12 226 664 | while read A B;do ((TOTAL+=A-B)) printf"%3d - %3d = %4d -> TOTAL= %4d " $A $B $[A-B] $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 |
这是可行的,但我(个人)不喜欢这样,因为这不是标准的,也无助于使脚本可读。此外,禁用作业控制对于访问此行为似乎代价高昂。
注意:默认情况下,作业控制仅在交互式会话中启用。所以在普通脚本中不需要
因此,如果在控制台中运行或在脚本中运行,脚本中被遗忘的
首先,执行该管链:
1 | echo first second | read A B |
然后
1 | echo $A-$B |
因为
1 | echo first second | (read A B ; echo $A-$B) |
然后,
更清洁的工作…
1 2 | read -r a b < <(echo"$first $second") echo"$a $b" |
这样,就不会在子shell中执行read(一旦子shell结束,就会清除变量)。相反,要使用的变量将被回送到一个子shell中,该子shell自动继承父shell中的变量。
您看到的是进程之间的分离:
管道(如
不同的情况下"管道进入时",是一个错觉。同样的规则在这里也适用:循环是管道的后半部分,所以它在子shell中,但是整个循环都在同一子shell中,所以流程分离不适用。