在while循环中修改的variables不被记住

在下面的程序中,如果我在第一个if语句中将variables$foo设置为值1,它的作用就是在if语句之后记住它的值。 然而,当我设置相同的variables值为2, if这是一个while语句内,它被遗忘后的while循环。 它的行为就像我在while循环中使用某种variables$foo的副本,我只修改那个特定的副本。 这是一个完整的testing程序:

 #!/bin/bash set -e set -u foo=0 bar="hello" if [[ "$bar" == "hello" ]] then foo=1 echo "Setting \$foo to 1: $foo" fi echo "Variable \$foo after if statement: $foo" lines="first line\nsecond line\nthird line" echo -e $lines | while read line do if [[ "$line" == "second line" ]] then foo=2 echo "Variable \$foo updated to $foo inside if inside while loop" fi echo "Value of \$foo in while loop body: $foo" done echo "Variable \$foo after while loop: $foo" # Output: # $ ./testbash.sh # Setting $foo to 1: 1 # Variable $foo after if statement: 1 # Value of $foo in while loop body: 1 # Variable $foo updated to 2 inside if inside while loop # Value of $foo in while loop body: 2 # Value of $foo in while loop body: 2 # Variable $foo after while loop: 1 # bash --version # GNU bash, version 4.1.10(4)-release (i686-pc-cygwin) 
 echo -e $lines | while read line ... done 

while循环在子shell中执行。 所以,一旦subshel​​l退出,对variables所做的任何更改都将不可用。

相反,您可以使用这里的string来重写while循环以便在主shell进程中; 只有echo -e $lines才能在子shell中运行:

 while read line do if [[ "$line" == "second line" ]] then foo=2 echo "Variable \$foo updated to $foo inside if inside while loop" fi echo "Value of \$foo in while loop body: $foo" done <<< "$(echo -e "$lines")" 

更新:#2

Blue Moon的答案就是解释。

替代scheme:

消除echo

 while read line; do ... done <<EOT first line second line third line EOT 

在这里的文档里添加echo

 while read line; do ... done <<EOT $(echo -e $lines) EOT 

在后台运行echo

 coproc echo -e $lines while read -u ${COPROC[0]} line; do ... done 

明确redirect到一个文件句柄(注意< < !!中的空格):

 exec 3< <(echo -e $lines) while read -u 3 line; do ... done 

或者只是redirect到stdin

 while read line; do ... done < <(echo -e $lines) 

和一个chepner (消除echo ):

 arr=("first line" "second line" "third line"); for((i=0;i<${#arr[*]};++i)) { line=${arr[i]}; ... } 

variables$lines可以转换为一个数组,而无需启动一个新的子shell。 字符\n必须被转换成一些字符(例如一个真正的新行字符),并使用IFS(内部字段分隔符)variables将string拆分为数组元素。 这可以像这样完成:

 lines="first line\nsecond line\nthird line" echo "$lines" OIFS="$IFS" IFS=$'\n' arr=(${lines//\\n/$'\n'}) # Conversion IFS="$OIFS" echo "${arr[@]}", Length: ${#arr[*]} set|grep ^arr 

结果是

 first line\nsecond line\nthird line first line second line third line, Length: 3 arr=([0]="first line" [1]="second line" [2]="third line") 

你是742342nd用户问这个bash FAQ。 答案还描述了由pipe道创build的子壳体中设置的variables的一般情况:

E4) 如果我把一个命令的输出传送到read variable ,为什么当读取命令结束时输出不会显示在$variable

这与Unix进程之间的父子关系有关。 它会影响在pipe道中运行的所有命令,而不仅仅是简单的read 。 例如,将一个命令的输出pipe道输送到一个反复调用readwhile循环中将会产生相同的行为。

pipe道的每个元素,即使是内build或shell函数,都在单独的进程中运行,即运行pipe道的shell的subprocess。 子stream程不能影响父级的环境。 当read命令将variables设置为input时,该variables仅在子shell中设置,而不是在父shell中设置。 当子shell退出时,variables的值将丢失。

许多以read variable结尾的stream水线可以被转换成命令replace,它将捕获指定命令的输出。 输出可以被分配给一个variables:

 grep ^gnu /usr/lib/news/active | wc -l | read ngroup 

可以转换成

 ngroup=$(grep ^gnu /usr/lib/news/active | wc -l) 

这不是,不幸的是,工作分裂在多个variables之间的文本,因为读时给予多个可变参数。 如果您需要这样做,可以使用上面的命令replace将输出读取到variables中,并使用bash模式删除扩展操作符截断variables,或使用以下方法的一些变体。

说/ usr / local / bin / ipaddr是下面的shell脚本:

 #! /bin/sh host `hostname` | awk '/address/ {print $NF}' 

而不是使用

 /usr/local/bin/ipaddr | read ABCD 

把本地机器的IP地址分解成单独的八位字节,使用

 OIFS="$IFS" IFS=. set -- $(/usr/local/bin/ipaddr) IFS="$OIFS" A="$1" B="$2" C="$3" D="$4" 

但是要小心,这将改变shell的位置参数。 如果你需要他们,那么在做这件事之前应该保存它们。

这是一般的方法 – 在大多数情况下,您不需要将$ IFS设置为不同的值。

一些其他用户提供的替代品包括:

 read ABCD << HERE $(IFS=.; echo $(/usr/local/bin/ipaddr)) HERE 

而且,在可以进行stream程替代的地方,

 read ABCD < <(IFS=.; echo $(/usr/local/bin/ipaddr)) 

嗯…我几乎会发誓,这对原始的Bourne shell有效,但是现在不能访问正在运行的副本来检查。

然而,这个问题有一个非常小的解决办法。

将脚本的第一行更改为:

 #!/bin/bash 

 #!/bin/ksh 

Et瞧! 假如你已经安装了Korn shell,那么在pipe道末端的读取就可以正常工作。

如何一个非常简单的方法

  +call your while loop in a function - set your value inside (nonsense, but shows the example) - return your value inside +capture your value outside +set outside +display outside #!/bin/bash # set -e # set -u # No idea why you need this, not using here foo=0 bar="hello" if [[ "$bar" == "hello" ]] then foo=1 echo "Setting \$foo to $foo" fi echo "Variable \$foo after if statement: $foo" lines="first line\nsecond line\nthird line" function my_while_loop { echo -e $lines | while read line do if [[ "$line" == "second line" ]] then foo=2; return 2; echo "Variable \$foo updated to $foo inside if inside while loop" fi echo -e $lines | while read line do if [[ "$line" == "second line" ]] then foo=2; echo "Variable \$foo updated to $foo inside if inside while loop" return 2; fi # Code below won't be executed since we returned from function in 'if' statement # We aready reported the $foo var beint set to 2 anyway echo "Value of \$foo in while loop body: $foo" done } my_while_loop; foo="$?" echo "Variable \$foo after while loop: $foo" Output: Setting $foo 1 Variable $foo after if statement: 1 Value of $foo in while loop body: 1 Variable $foo after while loop: 2 bash --version GNU bash, version 3.2.51(1)-release (x86_64-apple-darwin13) Copyright (C) 2007 Free Software Foundation, Inc.