为什么用管道将输入“读取"?仅当输入"while read ..."时有效构造? [英] Why piping input to "read" only works when fed into "while read ..." construct?
问题描述
我一直在尝试从程序输出中将输入读入环境变量,如下所示:
I've been trying to read input into environment variables from program output like this:
echo first second | read A B ; echo $A-$B
结果是:
-
A和B始终为空.我读到有关bash在子shell中执行管道命令的信息,基本上可以防止bash用管道传输输入以进行读取.但是,以下内容:
Both A and B are always empty. I read about bash executing piped commands in sub-shell and that basically preventing one from piping input to read. However, the following:
echo first second | while read A B ; do echo $A-$B ; done
似乎可以正常工作,结果是:
Seems to work, the result is:
first-second
有人可以解释一下这里的逻辑吗?
Can someone please explain what is the logic here? Is it that the commands inside the while
... done
construct are actually executed in the same shell as echo
and not in a sub-shell?
推荐答案
如何针对 stdin 进行循环并获取存储在变量中的结果
在 bash 的问题(以及其他 shell 的问题),当您使用|
另一个命令,您将隐式创建一个 fork ,这是当前会话的子级,并且不会影响当前会话的环境的子外壳.
How to do a loop against stdin and get result stored in a variable
Under bash (and other shell also), when you pipe something by using |
to another command, you will implicitely create a fork, a subshell who's a child of current session and who can't affect current session's environ.
所以这个:
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
不会给出预期的结果! :
won't give expected result! :
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 不能在主脚本中重复使用.
Where computed TOTAL could'nt be reused in main script.
通过使用 bash 处理替代,此处文档或此处字符串,您可以将叉子反向:
By using bash Process Substitution, Here Documents or Here Strings, you could inverse the 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
.
Now you could use $TOTAL
in your main script.
但是对于仅针对 stdin 的工作,您可以在 fork 中创建一种脚本:
But for working only against stdin, you may create a kind of script into the 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
不能在主脚本中使用(在最后一个大括号}
之后).
Note: $TOTAL
could not be used in main script (after last right curly bracket }
).
正如@CharlesDuffy正确指出的那样,有一个bash选项用于更改此行为.但是为此,我们必须首先禁用 工作控制:
As @CharlesDuffy correctly pointed out, there is a bash option used to change this behaviour. But for this, we have to first disable job control:
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
这可以工作,但是我(个人)不喜欢这样,因为这不是标准,也无助于使脚本可读.同样,禁用工作控制对于访问这种行为来说似乎也很昂贵.
This will work, but I (personally) don't like this because this is not standard and won't help to make script readable. Also disabling job control seem expensive for accessing this behaviour.
注意:默认情况下,仅在 interactive 会话中启用 Job control .因此,普通脚本中不需要set +m
.
Note: Job control is enabled by default only in interactive sessions. So set +m
is not required in normal scripts.
因此,如果在控制台中运行或在脚本中运行,则在脚本中被遗忘的set +m
会创建不同的行为.这不会使它易于理解或调试...
So forgotten set +m
in a script would create different behaviours if run in a console or if run in a script. This will not going to make this easy to understand or to debug...
这篇关于为什么用管道将输入“读取"?仅当输入"while read ..."时有效构造?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!