在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中执行。 所以,一旦subshell退出,对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道输送到一个反复调用read
的while
循环中将会产生相同的行为。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.