Warning: file_get_contents(/data/phpspider/zhask/data//catemap/5/bash/18.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Bash 在while循环中修改的变量不会被记住_Bash_While Loop_Scope_Sh - Fatal编程技术网

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

Bash 在while循环中修改的变量不会被记住,bash,while-loop,scope,sh,Bash,While Loop,Scope,Sh,在下面的程序中,如果我将变量$foo设置为第一个if语句中的值1,它的工作原理是在if语句之后记住它的值。但是,当我在while语句中的if中将同一变量设置为值2时,它会在while循环后被遗忘。它的行为就像我在循环中使用变量$foo的某种副本,而我只修改该特定副本。下面是一个完整的测试程序: #!/bin/bash set -e set -u foo=0 bar="hello" if [[ "$bar" == "hello" ]] then foo=1 echo "Se

在下面的程序中,如果我将变量
$foo
设置为第一个
if
语句中的值1,它的工作原理是在if语句之后记住它的值。但是,当我在
while
语句中的
if
中将同一变量设置为值2时,它会在
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 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)
while
循环在子shell中执行。因此,一旦子shell退出,您对变量所做的任何更改都将不可用

相反,您可以使用重新写入while循环,使其位于主shell进程中;只有
echo-e$行
将在子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

解释就在蓝月亮的回答中

替代解决方案:

消除回声

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

显式重定向到文件句柄(注意
中的空格,您是742342位询问此问题的用户。答案还描述了在管道创建的子shell中设置变量的一般情况:

E4)如果我将命令的输出导入
读取变量
,为什么 读取命令完成时,
$variable
中是否显示输出

这与Unix之间的父子关系有关 过程。它影响管道中运行的所有命令,而不仅仅是 对
read
的简单调用。例如,管道化命令的输出 进入一个
while
循环,重复调用
read
将导致 同样的行为

管道的每个元素,甚至是内置或外壳函数, 在单独的进程中运行,shell的子进程运行 管道子进程不能影响其父进程的环境。 当
read
命令将变量设置为输入时 变量仅在子shell中设置,而不是在父shell中设置。什么时候 子shell退出时,变量的值将丢失

许多以
读取变量
结尾的管道都可以转换 转换为命令替换,这将捕获 指定的命令。然后可以将输出分配给 变量:

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}'
#!/bin/sh

.....

failcnt=0 # this variable is only valid in the parent shell
#variable xx captures all the outputs from the while loop
xx=$(cat ${checkfile} | while read -r line; do
    num1=$(echo $line | awk '{print $1}')
    num2=$(echo $line | awk '{print $2}')
    fname=$(echo $line | awk '{print $3}')
    if [ -f "$fname" ]; then
        res=$(sum $fname)
        filegood=$(sum $fname | awk -v na=$num1 -v nb=$num2 -v fn=$fname '{ if (na == $1 && nb == $2) { print "TRUE"; } else { print "FALSE"; }}')
        if [ "$filegood" = "FALSE" ]; then
            failcnt=$(expr $failcnt + 1) # only in subshell
            echo "$fname BAD $failcnt"
        fi
    fi
done | tail -1) # I am only interested in the final result
# you can capture a whole bunch of texts and do further filtering
failcnt=${xx#* BAD } # I am only interested in the number
# this variable is in the parent shell
echo failcnt $failcnt
if [ $failcnt -gt 0 ]; then
    echo $failcnt files failed
else
    echo download successful
fi
而不是使用

/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"
但是,请注意,这将更改外壳的位置 参数。如果你需要它们,你应该在做之前保存它们 这个

这是一般的方法——在大多数情况下,您不需要这样做 将$IFS设置为其他值

其他一些用户提供的备选方案包括:

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

readabcd一个非常简单的方法怎么样

    +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.

嗯。。。我几乎发誓这对原始的Bourne shell有效,但现在还没有访问运行副本进行检查

然而,这个问题有一个非常简单的解决方法

将脚本的第一行更改为:

#!/bin/bash


瞧!假设您已经安装了Korn shell,那么在管道末尾进行阅读就可以了。

这是一个有趣的问题,涉及到Bourne shell和subshell中的一个非常基本的概念。在这里,我提供了一个不同于以前的解决方案的解决方案,通过进行某种过滤。我将举一个在现实生活中可能有用的例子。这是一个用于检查下载文件是否符合已知校验和的片段。校验和文件如下所示(仅显示3行):

shell脚本:

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

.....

failcnt=0 # this variable is only valid in the parent shell
#variable xx captures all the outputs from the while loop
xx=$(cat ${checkfile} | while read -r line; do
    num1=$(echo $line | awk '{print $1}')
    num2=$(echo $line | awk '{print $2}')
    fname=$(echo $line | awk '{print $3}')
    if [ -f "$fname" ]; then
        res=$(sum $fname)
        filegood=$(sum $fname | awk -v na=$num1 -v nb=$num2 -v fn=$fname '{ if (na == $1 && nb == $2) { print "TRUE"; } else { print "FALSE"; }}')
        if [ "$filegood" = "FALSE" ]; then
            failcnt=$(expr $failcnt + 1) # only in subshell
            echo "$fname BAD $failcnt"
        fi
    fi
done | tail -1) # I am only interested in the final result
# you can capture a whole bunch of texts and do further filtering
failcnt=${xx#* BAD } # I am only interested in the number
# this variable is in the parent shell
echo failcnt $failcnt
if [ $failcnt -gt 0 ]; then
    echo $failcnt files failed
else
    echo download successful
fi

父shell和子shell通过echo命令进行通信。您可以为父shell选择一些易于解析的文本。这种方法不会打破你正常的思维方式,只是你必须做一些后处理。您可以使用grep、sed、awk和更多工具来完成此操作。

我使用stderr在循环中存储数据,并从循环外部读取数据。 在这里,变量i最初设置为1,并在循环内部读取为1

# reading lines of content from 2 files concatenated
# inside loop: write value of var i to stderr (before iteration)
# outside: read var i from stderr, has last iterative value

f=/tmp/file1
g=/tmp/file2
i=1
cat $f $g | \
while read -r s;
do
  echo $s > /dev/null;  # some work
  echo $i > 2
  let i++
done;
read -r i < 2
echo $i
#从连接的两个文件中读取内容行
#内部循环:将var i的值写入stderr(在迭代之前)
#外部:从stderr读取var i,具有最后一个迭代值
f=/tmp/file1
g=/tmp/file2
i=1
猫$f$g|\
而read-rs;
做
echo$s>/dev/null;#一些工作
echo$i>2
让我++
完成;
read-ri<2
回声$i
或者使用herdoc方法减少子shell中的代码量。 注意迭代i值可以在while循环之外读取

i=1
while read -r s;
do
  echo $s > /dev/null
  let i++
done <<EOT
$(cat $f $g)
EOT
let i--
echo $i
i=1
而read-rs;
做
echo$s>/dev/null
让我++

完成虽然这是一个老问题,问了好几次,但下面是我在几个小时后处理
这里的
字符串所做的,唯一对我有效的选择是在while循环子shell期间将值存储在文件中,然后检索它。简单

使用
echo
语句存储,使用
cat
语句检索。bash用户必须
chown
目录或具有读写
chmod
访问权限

#write to file
echo "1" > foo.txt

while condition; do 
    if (condition); then
        #write again to file
        echo "2" > foo.txt      
    fi
done

#read from file
echo "Value of \$foo in while loop body: $(cat foo.txt)"

+1对于here doc,因为
变量的唯一用途似乎是在
循环时为
提供数据。@chepner:Thx!我又加了一个,献给你!给出了另一种解决方案:
用于$中的行(echo-e$行);做完成
@dma_k感谢您的评论!此解决方案将产生包含单个单词的6行。OP的请求w
read A B C D << HERE
    $(IFS=.; echo $(/usr/local/bin/ipaddr))
HERE
read A B C D < <(IFS=.; echo $(/usr/local/bin/ipaddr))
    +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.
#!/bin/bash
#!/bin/ksh
49174 36326 dna_align_feature.txt.gz
54757     1 dna.txt.gz
55409  9971 exon_transcript.txt.gz
#!/bin/sh

.....

failcnt=0 # this variable is only valid in the parent shell
#variable xx captures all the outputs from the while loop
xx=$(cat ${checkfile} | while read -r line; do
    num1=$(echo $line | awk '{print $1}')
    num2=$(echo $line | awk '{print $2}')
    fname=$(echo $line | awk '{print $3}')
    if [ -f "$fname" ]; then
        res=$(sum $fname)
        filegood=$(sum $fname | awk -v na=$num1 -v nb=$num2 -v fn=$fname '{ if (na == $1 && nb == $2) { print "TRUE"; } else { print "FALSE"; }}')
        if [ "$filegood" = "FALSE" ]; then
            failcnt=$(expr $failcnt + 1) # only in subshell
            echo "$fname BAD $failcnt"
        fi
    fi
done | tail -1) # I am only interested in the final result
# you can capture a whole bunch of texts and do further filtering
failcnt=${xx#* BAD } # I am only interested in the number
# this variable is in the parent shell
echo failcnt $failcnt
if [ $failcnt -gt 0 ]; then
    echo $failcnt files failed
else
    echo download successful
fi
# reading lines of content from 2 files concatenated
# inside loop: write value of var i to stderr (before iteration)
# outside: read var i from stderr, has last iterative value

f=/tmp/file1
g=/tmp/file2
i=1
cat $f $g | \
while read -r s;
do
  echo $s > /dev/null;  # some work
  echo $i > 2
  let i++
done;
read -r i < 2
echo $i
i=1
while read -r s;
do
  echo $s > /dev/null
  let i++
done <<EOT
$(cat $f $g)
EOT
let i--
echo $i
#write to file
echo "1" > foo.txt

while condition; do 
    if (condition); then
        #write again to file
        echo "2" > foo.txt      
    fi
done

#read from file
echo "Value of \$foo in while loop body: $(cat foo.txt)"