Bash 在从管道读取的while read循环之后重置变量
我正在编写一个bashshell脚本,使用Bash 在从管道读取的while read循环之后重置变量,bash,shell,while-loop,subshell,Bash,Shell,While Loop,Subshell,我正在编写一个bashshell脚本,使用bzip2压缩目录中的所有.bsp文件。在这个脚本中,我有几个用于计算总数的变量(文件、成功的拉链、成功的完整性扫描),但是我似乎遇到了一个问题 当find$loc-name“*.bsp”在读取时为和在读取时退出而提供的文件用完时,它会将$filcount、$zipcount和$scacount归零(所有这些文件都在initiate()、bzip()(在initiate()期间调用)或bzipint()(也在initiate()中调用) 为了测试它是否与
bzip2
压缩目录中的所有.bsp
文件。在这个脚本中,我有几个用于计算总数的变量(文件、成功的拉链、成功的完整性扫描),但是我似乎遇到了一个问题
当find$loc-name“*.bsp”
在读取时为和在读取时退出而提供的文件用完时,它会将$filcount
、$zipcount
和$scacount
归零(所有这些文件都在initiate()
、bzip()
(在initiate()
期间调用)或bzipint()
(也在initiate()
中调用)
为了测试它是否与initiate()
内部的变量更改或从中访问的其他函数有关,我使用了echo$valid
,它是在initiate()
外部定义的(如$filcount
,$zipcount
等),但不会从initiate()内部的另一个函数更改
或内部启动()
本身
有趣的是,$valid
不像initiate中的其他变量那样重置为0
有人能告诉我为什么我的变量在读取退出时神奇地复位吗?我昨天遇到了这个问题
问题是您正在执行find$loc-name“*.bsp”| while read
。因为这涉及到一个管道,while read
循环实际上不能与脚本的其余部分在同一个bash进程中运行;bash必须生成一个子进程,以便它可以将find
的stdout连接到while
循环的stdin
这是非常聪明的,但这意味着循环中设置的任何变量在循环之后都看不到,这完全违背了我编写循环时的全部目的
您可以尝试在不使用管道的情况下将输入馈送到循环,也可以在不使用变量的情况下从循环获取输出。我最终遇到了一个可怕的问题,即写入临时文件并将整个循环包装到$(…)
,如下所示:
initiate () {
read -p "Location(s) to look for .bsp files in? " loc
find $loc -name "*.bsp" | while read
do
if [ -f "$loc.bz2" ]
then
continue
else
filcount=$[$filcount+1]
bzip $loc
fi
if [ "$scan" == "1" ]; then bzipint $loc
fi
echo $filcount #Correct counting
echo $zipcount #Correct counting
echo $scacount #Correct counting
echo $valid #Equal to 1
done
echo $filcount #Reset to 0
echo $zipcount #Reset to 0
echo $scacount #Reset to 0
echo $valid #Still equal to 1
}
这使我将var设置为所有从循环中得到响应的内容。我可能弄乱了该示例的语法;我现在手头没有我编写的代码。如果使用bash
var="$(producer | while read line; do
...
echo "${something}"
done)"
读取时
做
如果[-f“$REPLY.bz2”]
然后
持续
其他的
filcount=$[$filcount+1]
bzip$回复
fi
如果[“$scan”==“1”];则bzipint$REPLY
fi
echo$filcount#正确计数
echo$zipcount#正确计数
echo$scacount#正确计数
echo$valid#等于1
使用POSIX类Shell中管道的[概念等价物]结尾处的read
,完成到总结的选项:
总而言之:在bash中默认情况下,在严格兼容POSIX的shell中,管道中的所有命令总是在子shell中运行,因此它们创建或修改的变量对当前shell不可见(管道结束后不存在)
以下介绍了bash
、ksh
、zsh
和sh
([大部分]POSIX只提供了dash
)等shell,并展示了避免创建子shell的方法,以保留由read
创建/修改的变量
如果没有给出最低版本号,那么假设即使是“非常旧”的版本也支持它(有问题的功能已经存在很长时间了,但我不知道它们是什么时候引入的)。
请注意,作为以下解决方案的[POSIX compliant]替代方案,您始终可以在[temporary]文件中捕获命令的输出,然后将其作为
提供给read
,这也避免了子shell。
ksh
和zsh
:根本不需要解决方案/配置更改:
默认情况下,read
内置命令作为管道中的最后一个命令在当前shell中运行
默认情况下,ksh
和zsh
在当前shell中管道的最后阶段运行任何命令。
在ksh 93u+
和zsh 5.0.5
中观察到
如果您知道该功能是在什么版本中引入的,请告诉我。
bash4.2+
:使用lastpipe
shell选项
在bash版本4.2或更高版本中,启用shell选项lastpipe
会导致最后一个管道段在当前shell中运行,从而允许read创建当前shell可见的变量
#!/usr/bin/env ksh
#!/usr/bin/env zsh
out= # initialize output variable
# Pipe multiple lines to the `while` loop and collect the values in the output variable.
printf '%s\n' one two three |
while read -r var; do
out+="$var/"
done
echo "$out" # -> 'one/two/three/'
bash
,ksh
,zsh
:使用
松散地说,进程替换是使命令的输出像临时文件一样工作的一种方法
#!/usr/bin/env bash
shopt -s lastpipe # bash 4.2+: make the last pipeline command run in *current* shell
out=
printf '%s\n' one two three |
while read -r var; do
out+="$var/"
done
echo "$out" # -> 'one/two/three/'
请注意,需要双引号引用命令替换以保护其输出不受影响
兼容POSIX的解决方案(sh
):将
!/bin/sh
出去=
while read-r var;do
out=“$out$var/”
回答得很好,谢谢你的澄清。是的,这肯定会导致一些复杂问题…我可能不得不使用一两个临时文件来解决这个问题,就像你做的那样。:P+1比将变量作为输出传递干净得多;这与管道版本基本相同,但在主P中使用while循环因此,只有shell内置程序可以影响shell环境,并且只有shell内置程序在当前shell中运行--并且shell历史上只直接从第一个管道阶段运行内置程序。因此,一些假设的奇妙shell可以决定,如果有一个阶段运行内置程序,那么它应该运行该阶段直接,但现在你要么在
#!/usr/bin/env bash
shopt -s lastpipe # bash 4.2+: make the last pipeline command run in *current* shell
out=
printf '%s\n' one two three |
while read -r var; do
out+="$var/"
done
echo "$out" # -> 'one/two/three/'
out=
while read -r var; do
out+="$var/"
done < <(printf '%s\n' one two three) # <(...) is the process substitution
echo "$out" # -> 'one/two/three'
out=
while read -r var; do
out+="$var/"
done <<< "$(printf '%s\n' one two three)" # <<< is the here-string operator
echo "$out" # -> 'one/two/three'
#!/bin/sh
out=
while read -r var; do
out="$out$var/"
done <<EOF # <<EOF ... EOF is the here-doc
$(printf '%s\n' one two three)
EOF
echo "$out" # -> 'one/two/three'