Bash 在从管道读取的while read循环之后重置变量

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()中调用) 为了测试它是否与

我正在编写一个bashshell脚本,使用
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'