huo*_*sto 69 bash pipe while-loop piping
我一直在尝试从程序输出中读取环境变量的输入,如下所示:
echo first second | read A B ; echo $A-$B
Run Code Online (Sandbox Code Playgroud)
结果是:
-
Run Code Online (Sandbox Code Playgroud)
A和B都是空的.我读到了bash在子shell中执行管道命令,并且基本上阻止了管道输入读取.但是,以下内容:
echo first second | while read A B ; do echo $A-$B ; done
Run Code Online (Sandbox Code Playgroud)
似乎工作,结果是:
first-second
Run Code Online (Sandbox Code Playgroud)
有人可以解释一下这里的逻辑是什么吗?是while...... done构造中的命令实际上是在同一个shell中执行echo而不是在子shell中执行?
F. *_*uri 63
在bash(以及其他shell)下,当你通过使用|另一个命令来管道时,你将隐含地创建一个fork,一个子shell,它是当前会话的子节点,并且不能影响当前会话的环境.
所以这:
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
Run Code Online (Sandbox Code Playgroud)
不会给出预期的结果!:
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
Run Code Online (Sandbox Code Playgroud)
计算TOTAL的地方不能在主脚本中重用.
通过使用bash Process Substitution,Here Documents或Here Strings,你可以反转fork:
read A B <<<"first second"
echo $A
first
echo $B
second
Run Code Online (Sandbox Code Playgroud)
while read A B;do
echo $A-$B
C=$A-$B
done << eodoc
first second
third fourth
eodoc
first-second
third-fourth
Run Code Online (Sandbox Code Playgroud)
循环之外:
echo : $C
: third-fourth
Run Code Online (Sandbox Code Playgroud)
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
Run Code Online (Sandbox Code Playgroud)
现在您可以$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
}
Run Code Online (Sandbox Code Playgroud)
会给:
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
Run Code Online (Sandbox Code Playgroud)
注意:$TOTAL无法在主脚本中使用(在最后一个右括号后}).
正如@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
Run Code Online (Sandbox Code Playgroud)
这将有效,但我(个人)不喜欢这样,因为这不是标准的,也无助于使脚本可读.同样禁用作业控制对于访问此行为似乎很昂贵.
注意: 默认情况下,仅在交互式会话中启用作业控制.因此在普通脚本中不需要.set +m
set +m如果在脚本中运行或在脚本中运行,那么在脚本中忘记会创建不同的行为.这不会使这容易理解或调试......
pbh*_*bhd 21
首先,执行此管道链:
echo first second | read A B
Run Code Online (Sandbox Code Playgroud)
然后
echo $A-$B
Run Code Online (Sandbox Code Playgroud)
因为它read A B是在子shell中执行的,所以A和B都会丢失.如果你这样做:
echo first second | (read A B ; echo $A-$B)
Run Code Online (Sandbox Code Playgroud)
然后两个read A B和echo $A-$B都在同一个子shell中执行(参见bash手册,搜索(list)
小智 18
一个更清洁的解决方案......
read -r a b < <(echo "$first $second")
echo "$a $b"
Run Code Online (Sandbox Code Playgroud)
这样,read不会在子shell中执行(一旦子shell结束就会清除变量).相反,您要使用的变量在子shell中回显,该子shell自动从父shell继承变量.