为啥管道输入到“read”只有在输入“while read ...”结构时才有效? [复制]
Posted
技术标签:
【中文标题】为啥管道输入到“read”只有在输入“while read ...”结构时才有效? [复制]【英文标题】:Why piping input to "read" only works when fed into "while read ..." construct? [duplicate]为什么管道输入到“read”只有在输入“while read ...”结构时才有效? [复制] 【发布时间】:2012-11-25 16:21:52 【问题描述】:我一直在尝试从程序输出中读取环境变量的输入,如下所示:
echo first second | read A B ; echo $A-$B
结果是:
-
A 和 B 始终为空。我读到了 bash 在子 shell 中执行管道命令,这基本上阻止了一个管道输入来读取。但是,以下内容:
echo first second | while read A B ; do echo $A-$B ; done
似乎可行,结果是:
first-second
谁能解释一下这里的逻辑是什么? while
... done
构造中的命令实际上是在与 echo
相同的 shell 中执行的,而不是在子 shell 中执行的吗?
【问题讨论】:
这是 BashFAQ #24:mywiki.wooledge.org/BashFAQ/024 【参考方案1】:如何对 stdin 执行循环并将结果存储在变量中
在bash(以及其他shell)下,当您通过|
将某些内容传递给另一个命令时,您将隐式创建一个fork,这是一个子shell,它是current 的子shell会议。 subshell 不能影响当前会话的环境。
所以这个:
TOTAL=0
printf "%s %s\n" 9 4 3 1 77 2 25 12 226 664 |
while read A B;do
((TOTAL+=A-B))
printf "%3d - %3d = %4d -> TOTAL= %4d\n" $A $B $[A-B] $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 不能在主脚本中重复使用。
倒叉
通过使用bashProcess Substitution、Here Documents 或 Here Strings,您可以反转 fork:
这里是字符串
read A B <<<"first second"
echo $A
first
echo $B
second
这里的文档
while read A B;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 A B;do
((TOTAL+=A-B))
printf "%3d - %3d = %4d -> TOTAL= %4d\n" $A $B $[A-B] $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
。
管道到一个命令列表
但如果只针对 stdin 工作,您可以在 fork 中创建一种脚本:
printf "%s %s\n" 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\n" $A $B $[A-B] $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 A B;do
((TOTAL+=A-B))
printf "%3d - %3d = %4d -> TOTAL= %4d\n" $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
这可行,但我(个人)不喜欢这样,因为这不是标准,并且无助于使脚本可读。此外,禁用作业控制对于访问此行为似乎很昂贵。
注意: 作业控制 默认情况下仅在交互式 会话中启用。所以set +m
在普通脚本中是不需要的。
如果在控制台中运行或在脚本中运行,那么在脚本中忘记set +m
会产生不同的行为。这不会使其易于理解或调试...
【讨论】:
谢谢。在我发布我的问题后不久,我意识到while
循环仍然在子代而不是父代中执行,并且我不能在循环外使用A
和B
。
这里有一点需要注意——有些 shell 支持为管道的其他组件创建子 shell,并在右侧使用现有的 shell。这是 bash 中的 shopt -s lastpipe
; ksh88 和 ksh93 开箱即用的行为相似。
可能值得注意的是,作业控制在非交互式 shell 中默认关闭(因此,在所有未明确重新启用它的脚本中)。因此,您注意到的限制并不适用于所有情况。 (但可以肯定的是,在交互式和非交互式 shell 之间工作不同的代码本身就是一个缺点)。
这个答案非常彻底,但给人的印象是,当您想将 read 放在类似管道的语法的尾部时,仍然无法使用 read
捕获标准输出单线。这就是OP试图做的。正如其他答案所示,您可以使用子外壳或分组来执行此操作。但是有没有办法既容易又正确地做到这一点? ——也就是说,没有循环、子shell或组,并且有进程替换?
@F.Hauri 当我阅读问题本身的正文时,而不仅仅是标题,在我看来,OP 正试图将 read 放在最后,并且想知道为什么他或她必须使用尽管。他们没有尝试使用while。他们试图把 read 放在最后。也许我假设太多了。【参考方案2】:
一个更干净的解决方法...
first="firstvalue"
second="secondvalue"
read -r a b < <(echo "$first $second")
echo "$a $b"
这样,read 不会在子 shell 中执行(一旦子 shell 结束就会清除变量)。相反,您要使用的变量会在子 shell 中回显,该子 shell 会自动从父 shell 继承变量。
【讨论】:
对于可能对 tldp.org/LDP/abs/html/process-sub.html 如果我想 (echo "$first"; echo "$second") 进入 A 和 B 怎么办? (或者更确切地说,一个输出两行的命令,我希望第一行进入 A,第二行进入 B) 您还可以通过执行以下操作来防止生成子shell 只是为了运行 echo:read -r a b <<< "$first $second"
【参考方案3】:
首先,这个管道链被执行:
echo first second | read A B
然后
echo $A-$B
因为read A B
在子shell 中执行,A 和B 丢失。
如果你这样做:
echo first second | (read A B ; echo $A-$B)
那么read A B
和echo $A-$B
都在同一个子shell 中执行(参见bash 的联机帮助页,搜索(list)
【讨论】:
顺便说一句,你不需要 subshell。您可以简单地使用分组。 @anishsane:这很奇怪:如果你打开两个控制台,首先点击tty
以了解使用了哪个 pty,而不是第二个 watch ps --tty pts/NN
(其中 NN 在第一个控制台的答案中) .比第一种,尝试:echo | (sleep 10)
和echo | sleep 10 ;
,你会看到第二种语法会生成两个分叉,而第一次只反弹一个子shell。
如果您想使用分组运算符而不是创建子shell,则需要记住空格和分号:echo something | read myvar; echo $myvar;
谢谢,最好的答案。【参考方案4】:
您看到的是进程之间的分离:read
出现在子 shell 中 - 一个无法更改主进程中的变量的单独进程(稍后会出现 echo
命令)。
管道(如A | B
)隐式地将每个组件放置在一个子shell(一个单独的进程)中,即使对于通常在shell上下文中运行的内置(如read
)(在同一过程)。
“进入while”情况的区别是一种错觉。相同的规则适用于那里:循环是管道的后半部分,因此它位于子外壳中,但 整个 循环位于 same 子外壳中,因此分离的进程不适用。
【讨论】:
语法:A | B
将为B
创建一个分离的进程!不是子外壳中的每个组件:A
将保留在主(父)外壳中! (除非shopt -s lastpipe
A
将在子shell 中运行,但没有B
!)
@F.Hauri 不,你倒退了。 A 将始终位于子shell中; B 将在子外壳中除非您使用shopt -s lastpipe
( set x y z ; set a b c | set d e f ; echo "$@" )
x y z ( shopt -s lastpipe ; set x y z ; set a b c | set d e f ; echo "$@" )
d e f以上是关于为啥管道输入到“read”只有在输入“while read ...”结构时才有效? [复制]的主要内容,如果未能解决你的问题,请参考以下文章