在while循环内修改的变量不会被记住

Eri*_*lja 168 bash scope sh while-loop

在下面的程序中,如果我$foo在第一个if语句中将变量设置为值1 ,那么它的作用就是在if语句之后记住它的值.但是,当我将同一个变量设置ifwhile语句内部的值2时,它在while循环之后就被遗忘了.它的行为就像我$foowhile循环中使用某种变量的副本而我只修改那个特定的副本.这是一个完整的测试程序:

#!/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)
Run Code Online (Sandbox Code Playgroud)

P.P*_*.P. 221

echo -e $lines | while read line 
    ...
done
Run Code Online (Sandbox Code Playgroud)

while回路在子Shell执行.因此,一旦子shell退出,您对该变量所做的任何更改都将无法使用.

相反,您可以使用here字符串重写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")"
Run Code Online (Sandbox Code Playgroud)

echo通过在分配时立即扩展反斜杠序列,你可以摆脱上面here-string 中的相当丑陋lines.$'...'引用的形式可以在那里使用:

lines=$'first line\nsecond line\nthird line'
while read line; do
    ...
done <<< "$lines"
Run Code Online (Sandbox Code Playgroud)

  • 更好地改变`<<<"$(echo -e"$ lines")"`简单`<<<"$ lines"` (17认同)
  • @AvinashYadav 这个问题实际上与“while”循环或“for”循环无关;而是使用子shell,即在`cmd1 | 中 cmd2`、`cmd2` 位于子 shell 中。因此,如果在子 shell 中执行“for”循环,则会出现意外/有问题的行为。 (4认同)
  • @mteee您可以在读取-r行时使用`; 回显“ LINE:$ line”;done &lt;&lt;(tail -f file)`(显然循环不会终止,因为它继续等待来自`tail`的输入)。 (2认同)

Tru*_*ueY 46

更新:#2

Blue Moons的答案是解释.

替代方案:

消除 echo

while read line; do
...
done <<EOT
first line
second line
third line
EOT
Run Code Online (Sandbox Code Playgroud)

在here-is-the-document中添加echo

while read line; do
...
done <<EOT
$(echo -e $lines)
EOT
Run Code Online (Sandbox Code Playgroud)

echo在后台运行:

coproc echo -e $lines
while read -u ${COPROC[0]} line; do 
...
done
Run Code Online (Sandbox Code Playgroud)

显式重定向到文件句柄(注意空格< <!):

exec 3< <(echo -e  $lines)
while read -u 3 line; do
...
done
Run Code Online (Sandbox Code Playgroud)

或者只是重定向到stdin:

while read line; do
...
done < <(echo -e  $lines)
Run Code Online (Sandbox Code Playgroud)

一个chepner(消除echo):

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

$lines可以将变量转换为数组而无需启动新的子shell.字符\并且n必须转换为某个字符(例如,一个真正的新行字符)并使用IFS(内部字段分隔符)变量将字符串拆分为数组元素.这可以这样做:

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
Run Code Online (Sandbox Code Playgroud)

结果是

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


Jen*_*ens 8

您是第742342位用户,请求此bash常见问题解答.答案还描述了由管道创建的子壳中设置的变量的一般情况:

E4)如果我输出一个命令的输出read variable,为什么$variable在read命令结束时输出不显示?

这与Unix进程之间的父子关系有关.它会影响在管道中运行的所有命令,而不仅仅是简单的调用read.例如,将命令的输出while传递到重复调用的循环read将导致相同的行为.

管道的每个元素,甚至是内置函数或shell函数,都在一个单独的进程中运行,这是运行管道的shell的子进程.子流程不会影响其父级环境.当read命令将变量设置为输入时,该变量仅在子shell中设置,而不是在父shell中设置.当子shell退出时,变量的值将丢失.

许多以(以)结尾的管道read variable可以转换为命令替换,这将捕获指定命令的输出.然后可以将输出分配给变量:

grep ^gnu /usr/lib/news/active | wc -l | read ngroup
Run Code Online (Sandbox Code Playgroud)

可以转换成

ngroup=$(grep ^gnu /usr/lib/news/active | wc -l)
Run Code Online (Sandbox Code Playgroud)

遗憾的是,这并不能在多个变量之间拆分文本,因为在给定多个变量参数时读取了这些变量.如果需要这样做,可以使用上面的命令替换将输出读入变量并使用bash模式删除扩展运算符来切断变量,或者使用以下方法的某种变体.

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

#! /bin/sh
host `hostname` | awk '/address/ {print $NF}'
Run Code Online (Sandbox Code Playgroud)

而不是使用

/usr/local/bin/ipaddr | read A B C D
Run Code Online (Sandbox Code Playgroud)

将本地计算机的IP地址分解为单独的八位字节,使用

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

但请注意,这将改变shell的位置参数.如果您需要它们,您应该在执行此操作之前保存它们.

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

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

read A B C D << HERE
    $(IFS=.; echo $(/usr/local/bin/ipaddr))
HERE
Run Code Online (Sandbox Code Playgroud)

并且,如果有过程替换,

read A B C D < <(IFS=.; echo $(/usr/local/bin/ipaddr))
Run Code Online (Sandbox Code Playgroud)

  • 你忘记了*Family Feud*问题.有时很难找到与撰写答案的人相同的单词组合,这样你就不会被错误的结果所淹没,也不会过滤掉所说的答案. (6认同)