为什么在do循环中设置的变量会消失? (unix shell)

时间:2016-08-06 01:06:41

标签: shell loops variables unix

我的脚本的这一部分是比较文件的每一行以找到预设字符串。如果字符串不作为文件中的一行存在,则应将其附加到文件的末尾。

STRING=foobar
cat "$FILE" | while read LINE
    do
        if [ "$STRING" == "$LINE" ]; then
            export ISLINEINFILE="yes"
        fi
    done
    if [ ! "$ISLINEINFILE" == yes ]; then
        echo "$LINE" >> "$FILE"
    fi

但是,看起来好像完成do循环后$ LINE和$ ISLINEINFILE都被清除了。我怎么能避免这个?

2 个答案:

答案 0 :(得分:1)

  

为什么我在do循环中设置的变量会消失?

它消失了,因为它是在shell管道组件中设置的。大多数shell在子shell中运行管道的每个部分。通过Unix设计,子shell中设置的变量不会影响其父级或任何已经运行的其他shell。

  

我该如何避免这种情况?

有几种方法:

  • 最简单的方法是在子shell中使用不运行管道的最后一个组件的shell。这是ksh默认行为,例如用那个shebang:

    #!/bin/ksh

    设置bash选项后,此行为也可以是lastpipe

    shopt -s lastpipe

  • 您可以在设置它的相同子shell中使用该变量。请注意,您的原始脚本缩进是错误的,并且可能导致错误地假设if块在管道内,而实际情况并非如此。用括号括起整个区块将纠正这一点,并且将是最小的改变(两个额外的字符)以使其工作:

STRING=foobar
cat "$FILE" | ( while read LINE
    do
        if [ "$STRING" == "$LINE" ]; then
            export ISLINEINFILE="yes"
        fi
    done
    if [ ! "$ISLINEINFILE" == yes ]; then
        echo "$LINE" >> "$FILE"
    fi
    )

虽然该变量在该块之后仍然会丢失。

  • 您可能只是避免使用管道,在您的情况下这是不合适的,cat是不必要的:

STRING=foobar
while read LINE
do
    if [ "$STRING" == "$LINE" ]; then
        export ISLINEINFILE="yes"
    fi
done < "$FILE"
if [ ! "$ISLINEINFILE" == yes ]; then
    echo "$LINE" >> "$FILE"
fi

  • 您可以使用其他算法方法,例如使用John1024建议的sedgawk

有关标准合规性详细信息,另请参阅https://unix.stackexchange.com/a/144137/2594

答案 1 :(得分:1)

使用shell

如果我们想对代码进行最小的更改以使其正常工作,我们需要做的就是切换输入重定向:

string=foobar
while read line
do
    if [ "$string" == "$line" ]; then
        islineinfile="yes"
    fi
done <"$file"
if [ ! "$islineinfile" == yes ]; then
    echo "$string" >> "$file"
fi

在上文中,我们将cat "$file" | while do ...done更改为while do...done<"$file"。通过这一次更改,while循环不再位于子shell中,因此,循环完成后,循环中创建的shell变量仍然存在。

使用sed

我相信您的整个脚本都可以替换为:

sed -i.bak '/^foobar$/H; ${x;s/././;x;t; s/$/\nfoobar/}' file*

以上内容将foobar行添加到每个文件的末尾,该文件的行与^foobar$不匹配。

上面显示file*作为sed的最终参数。这将更改应用于匹配glob的所有文件。如果您愿意,可以单独列出特定文件。

以上是在GNU sed(linux)上测试的。 BSD / OSX sed可能需要稍作修改。

使用GNU awk(gawk)

awk -i inplace -v s="foobar" '$0==s{f=1} {print} ENDFILE{if (f==0) print s; f=0}' file*

与sed命令一样,它可以在一个命令中处理多个文件。