10
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
}

我正在编写一个 bash shell 脚本,用于bzip2压缩.bsp目录中的所有文件。在这个脚本中,我有几个用于计算总数的变量(文件、成功的 zip、成功的完整性扫描),但是我似乎遇到了问题。

find $loc -name "*.bsp"文件用完while readwhile read退出时,它会将 out和$filcount(所有这些都在内部更改(增加),(在 期间调用)或(也称为 in )归零。$zipcount$scacountinitiate ()bzip ()initiate ()bzipint ()initiate ()

为了测试它是否与内部变量的变化initiate ()或从中访问的其他函数有关,我使用了 echo $valid,它是在外部定义的initiate ()(如$filcount$zipcount等),但不会从内部initiate ()或内部的另一个函数更改initiate ()

有趣的是,$valid它不会像内部的其他变量一样重置为 0。

谁能告诉我为什么我的变量在读取退出时会神奇地重置?

4

3 回答 3

11

如果你使用 bash

while read
do
    if [ -f "$REPLY.bz2" ]
    then
        continue
    else
        filcount=$[$filcount+1]
        bzip $REPLY
    fi
    if [ "$scan" == "1" ]; then bzipint $REPLY
    fi
    echo $filcount    #Correct counting
    echo $zipcount    #Correct counting
    echo $scacount    #Correct counting
    echo $valid       #Equal to 1
done < <(find $loc -name "*.bsp")
于 2011-09-06T02:45:10.877 回答
9

我昨天遇到了这个问题。

问题是你在做find $loc -name "*.bsp" | while read。因为这涉及管道,所以while read循环实际上不能与脚本的其余部分在同一个 bash 进程中运行;bash 必须产生一个子进程,以便它可以将标准输出连接到循环find的标准输入。while

这一切都很聪明,但这意味着循环中设置的任何变量在循环之后都看不到,这完全违背了while我编写循环的整个目的。

您可以尝试在不使用管道的情况下向循环提供输入,或者在不使用变量的情况下从循环中获取输出。我最终得到了一个可怕的憎恶,包括写入临时文件并将整个循环包装在 中$(...),如下所示:

var="$(producer | while read line; do
    ...
    echo "${something}"
done)"

这让我 var 设置了从循环中回显的所有内容。我可能弄乱了那个例子的语法;我现在手头没有我写的代码。

于 2011-09-05T23:28:02.407 回答
9

总结在类似 POSIX 的 shell 中的管道[概念等价物] 末尾使用的选项:read

回顾一下:默认情况下在 bash 和严格遵守 POSIX 的 shell中,管道中的所有命令都在subshel​​l中运行,因此它们创建或修改的变量对当前shell不可见(管道结束后将不存在)。

以下内容涵盖bashkshzshsh([主要是] POSIX-features-only shells,例如dash),并展示了避免创建子 shell 以保留由 . 创建/修改的变量的方法read

如果没有给出最低版本号,假设即使是“相当旧的”版本也支持它(有问题的功能已经存在了很长时间,但我不知道具体是什么时候引入的。

请注意,作为以下解决方案的 [POSIX-compliant] 替代方案,您始终可以在 [temporary] 文件中捕获命令的输出,然后将其提供给readas < file,这也避免了子 shell。


ksh, 和zsh: 根本不需要解决方法/配置更改:

当用作管道中的最后一个read命令时,内置函数默认当前shell 中运行。

看起来,ksh默认zsh 情况下,在当前shell的管道的最后阶段运行任何命令。在和中 观察到。 如果您具体知道此功能是在哪个版本中引入的,请告诉我。
ksh 93u+zsh 5.0.5

#!/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 4.2+: 使用lastpipeshell 选项

在 bash 4.2 或更高版本中,打开 shell 选项lastpipe会导致最后一个管道段在当前shell 中运行,从而允许 read 创建对当前 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/'

bash, ksh, zsh: 使用进程替换

粗略地说,进程替换是一种让命令的输出像临时文件一样的方式。

out=
while read -r var; do
  out+="$var/"
done < <(printf '%s\n' one two three) # <(...) is the process substitution

echo "$out" # -> 'one/two/three'

bash, ksh, zsh: 使用带有命令替换的here-string

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'

请注意需要双引号命令替换以保护其输出免受shell 扩展


符合 POSIX 的解决方案 ( sh):使用带有命令替换的here-document

#!/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'

请注意,默认情况下,您需要将结束分隔符 - EOF,在这种情况下 - 放在行的最开头,并且后面不能有任何字符。

于 2015-03-07T17:31:53.380 回答