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

时间:2011-09-05 23:13:29

标签: bash shell while-loop subshell

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个文件。在这个脚本中,我有几个用于计算总数的变量(文件,成功的拉链,成功的完整性扫描),但是我似乎遇到了问题。

find $loc -name "*.bsp"用完文件以提供while readwhile read出口时,它会将$filcount$zipcount$scacount归零(所有这些都在initiate ()bzip ()(在initiate ()期间调用)或bzipint ()(也在initiate ()中调用)中更改(增加)。

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

有趣的是,initiate ()不会像启动中的其他变量一样重置为0。

有人可以告诉我为什么我的变量会在读取退出时神奇地重置吗?

3 个答案:

答案 0 :(得分:10)

如果你使用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")

答案 1 :(得分:8)

总结选项,使用read在类似POSIX的shell中[概念等效于]管道的末尾使用bash

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

以下涵盖kshzshshdash ([主要] POSIX-features-only shell,例如{ {1}})并显示避免创建子shell的方法,以便保留由read 创建/修改的变量。

如果没有给出最低版本号,那么假设即使是很老的&#34;版本支持它(有问题的功能已经存在了很长时间,但我不知道它们何时被介绍。 功能

请注意,作为下面的解决方案的[POSIX兼容]替代方案,您始终可以在[临时]文件中捕获命令的输出,然后将其作为提供给read < file,也避免了子弹。 功能


kshzsh:根本不需要解决方法/配置更改:

默认情况下,read内置 当前 shell中运行时用作管道中的 last 命令。

看来,默认情况下kshzsh last 阶段运行任何命令>当前外壳。
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+:使用lastpipe shell选项

在bash版本4.2或更高版本中,启用shell选项lastpipe会导致最后一个管道段在当前 shell中运行,从而允许读取创建当前shell可见的变量。 / p>

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

bashkshzsh:使用process substitution

简而言之,进程替换是一种让命令输出就像临时文件一样的方法。

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

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

bashkshzshhere-string使用command substitution

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 expansions


符合POSIX标准的解决方案(sh):使用here-documentcommand substitution

#!/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,并且不得跟随它。

答案 2 :(得分:7)

昨天我遇到了这个问题。

问题在于你正在做find $loc -name "*.bsp" | while read。因为这涉及管道,while read循环实际上不能在与其余脚本相同的bash进程中运行; bash必须生成一个子进程,以便它可以将find的stdout连接到while循环的stdin。

这一切都非常聪明,但这意味着在循环之后无法看到循环中设置的任何变量,这完全打败了我写的while循环的整个目的。

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

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

让我将var设置为循环中回显的所有内容。我可能搞砸了那个例子的语法;我目前没有自己编写的代码。