在下面的程序中,如果我在第一个$foo
语句中将变量if
设置为值1,那么它的作用就是在if语句之后记住它的值。但是,当我在if
语句中的while
中将相同的变量设置为值2时,它在while
循环之后被遗忘了。它的行为就像我在$foo
循环中使用变量while
的某种副本而我只修改那个特定的副本。这是一个完整的测试程序:
#!/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中执行。因此,一旦子shell退出,您对该变量所做的任何更改都将无法使用。
相反,您可以使用here 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")"
你可以通过在分配echo
时立即扩展反斜杠序列来摆脱上面这里字符串中相当难看的lines
。可以在那里使用$'...'
引用形式:
lines=$'first line\nsecond line\nthird line'
while read line; do
...
done <<< "$lines"
更新:#2
Blue Moons的答案是解释。
替代方案:
消除echo
while read line; do
...
done <<EOT
first line
second line
third line
EOT
在here-is-the-document中添加echo
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 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")
您是第742342位用户询问此bash FAQ.答案还描述了由管道创建的子壳中设置的变量的一般情况:
E4)如果我将一个命令的输出传送到
read variable
,为什么当read命令结束时输出不会出现在$variable
中?这与Unix进程之间的父子关系有关。它会影响在管道中运行的所有命令,而不仅仅是对
read
的简单调用。例如,将命令的输出传递到重复调用while
的read
循环将导致相同的行为。管道的每个元素,甚至是内置函数或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))
嗯......我几乎发誓,这适用于原始的Bourne shell,但是现在无法访问正在运行的副本来检查。
但是,这个问题有一个非常简单的解决方法。
从以下位置更改脚本的第一行:
#!/bin/bash
至
#!/bin/ksh
瞧瞧!假设您安装了Korn shell,那么管道末端的读取工作正常。
怎么样一个非常简单的方法
+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和subshell中的一个非常基本的概念。在这里,我通过某种过滤提供了与以前的解决方案不同的解决方案。我将举一个可能在现实生活中有用的例子。这是用于检查下载的文件是否符合已知校验和的片段。校验和文件如下所示(仅显示3行):
49174 36326 dna_align_feature.txt.gz
54757 1 dna.txt.gz
55409 9971 exon_transcript.txt.gz
shell脚本:
#!/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
parent和subshell通过echo命令进行通信。您可以为父shell选择一些易于解析的文本。这种方法不会破坏你的正常思维方式,只是你必须做一些后期处理。您可以使用grep,sed,awk等来执行此操作。