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

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

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 

但是,看起来好像$ LINE和$ ISLINEINFILE在完成do循环时都被清除了。 我怎样才能避免这一点?

使用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 cat "$file" | while do ...donewhile 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 -vs="foobar" '$0==s{f=1} {print} ENDFILE{if (f==0) print s; f=0}' file* 

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

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

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

我怎样才能避免这一点?

有几种方法:

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

    #!/bin/ksh

    当最后一个lastpipe选项被设置时,这种行为也可以是bash

    shopt -s lastpipe

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

 STRING = foobar的
猫“$ FILE”|  (当读LINE时
    做
         if [“$ STRING”==“$ LINE”]; 然后
            导出ISLINEINFILE =“是”
        科幻
     DONE
    如果[!  “$ ISLINEINFILE”==是]; 然后
        回声“$ LINE”>>“$ FILE”
    科幻
     ) 

尽管如此,该变量仍然会丢失。

  • 你可以简单地避开管道,在你的情况下,这是cat不必要的:

  STRING = foobar的
同时阅读LINE
做
     if [“$ STRING”==“$ LINE”]; 然后
        导出ISLINEINFILE =“是”
    科幻
完成<“$ FILE”
如果[!  “$ ISLINEINFILE”==是]; 然后
    回声“$ LINE”>>“$ FILE”
科幻 

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

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