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

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

#!/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 linensecond linenthird 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退出,对变量所做的任何更改都将不可用。

相反,您可以使用here字符串将while循环重写为主壳程序; 只有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的答案就是解释。

替代方案:

消除echo

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

在这里是文档中添加回显

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

在后台运行echo

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

明确重定向到文件句柄(注意< < !中的空格):

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

或者只是重定向到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]}; 
...
}

变量$lines可以转换为数组而不必启动新的子shell。 字符n必须转换为某个字符(例如一个真实的新行字符)并使用IFS(内部字段分隔符)变量将字符串拆分为数组元素。 这可以像这样完成:

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

结果是

first linensecond linenthird line
first line second line third line, Length: 3
arr=([0]="first line" [1]="second line" [2]="third line")

您是742342nd用户,请问这个bash FAQ。 答案还描述了在由管道创建的子壳体中设置的变量的一般情况:

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

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

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

许多以read variable结尾的流水线可以转换为命令替换,它将捕获指定命令的输出。 输出可以被分配给一个变量:

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

可以转换成

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

不幸的是,这并不能在多个变量之间分割文本,就像在给定多个可变参数时所做的那样。 如果您需要这样做,您可以使用上面的命令替换将输出读取到变量中,并使用bash模式删除扩展操作符截断变量,或者使用以下方法的一些变体。

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

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

而不是使用

/usr/local/bin/ipaddr | read A B C D

将本地机器的IP地址分解成不同的八位字节,使用

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

但是要小心,这会改变shell的位置参数。 如果你需要它们,你应该在做这些之前先保存它们。

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

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

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

并且在可以进行过程替代的地方,

read A B C D < <(IFS=.; echo $(/usr/local/bin/ipaddr))
链接地址: http://www.djcxy.com/p/25571.html

上一篇: A variable modified inside a while loop is not remembered

下一篇: Correct Bash and shell script variable capitalization