我一直在尝试从程序输出中读取环境变量的输入,如下所示:
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中执行的吗?
答案 0 :(得分:61)
在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
不会给出预期的结果! :
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 进程替换,此处文档或此处字符串,您可以反转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
无法在主脚本中使用(在右上方的大括号}
之后)。
正如@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
会产生不同的行为。这不会使这很容易理解或调试......
答案 1 :(得分:19)
首先,执行此管道链接:
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)
答案 2 :(得分:15)
更清洁的解决方法......
read -r a b < <(echo "$first $second")
echo "$a $b"
这样,read不会在子shell中执行(一旦子shell结束就会清除变量)。相反,您要使用的变量将在子shell中回显,该子shell会自动从父shell继承变量。
答案 3 :(得分:1)
你看到的是进程之间的分离:read
出现在子shell中 - 一个单独的进程,它不能改变主进程中的变量(以后会发生echo
命令)。
管道(如A | B
)隐式将每个组件放在子shell(一个单独的进程)中,即使对于通常在shell上下文中运行的内置函数(如read
)也是如此(在同一过程中)。
&#34;管道进入的情况的差异&#34;是一种幻觉。同样的规则也适用于:循环是管道的后半部分,所以它在子shell中,但整个循环在相同的子shell中,因此分离过程不适用。