bash:变量在读取循环结束时丢失值


36

我的一个shell脚本有问题。问了几个同事,但他们都只是摇了摇头(经过一番抓挠),所以我来这里就是一个答案。

根据我的理解,以下shell脚本应在最后一行打印“ Count is 5”。除非不是。打印“ Count is 0”。如果将“ while读取”替换为任何其他类型的循环,则可以正常工作。这是脚本:

回声“ 1”> input.data
回声“ 2” >> input.data
回声“ 3” >> input.data
回声“ 4” >> input.data
回声“ 5” >> input.data

碳纳米管= 0 

cat input.data | 阅读时;
做
  让CNT ++;
  回声“计数到$ CNT”
完成 
回声“计数是$ CNT”

为什么会发生这种情况,如何预防呢?我已经在Debian Lenny和Squeeze中尝试过,结果相同(即bash 3.2.39和bash 4.1.5。我完全承认自己不是shell脚本向导,因此希望使用任何指针。

Answers:


30

请参阅参数@ Bash FAQ条目#24:“我在循环中设置了变量。为什么在循环终止后它们突然消失?或者,为什么我不能通过管道传输要读取的数据?” (最近在此处存档)。

简介:仅bash 4.2及更高版本支持此功能。如果您使用的是bash,则需要使用其他方式(例如命令替换)代替管道。


由于您的回答为我提供了最广泛的选择,因此您将获得奖励。
wolfgangsz

5
链接已死。这就是纯链接答案不好的原因。至少在这里总结答案。
rudolfbyker '17

天哪,又是ksh好得多的那一次……为什么,为什么每个人都蜂拥而至。
Florian Heigl

@FlorianHeigl:您是否声称ksh是One True Shell?
伊格纳西奥·巴斯克斯

@ IgnacioVazquez-Abrams不,但是我声称bash中的while循环处理是一个非常糟糕的PITA。循环处理长期以来一直使它无法赶上1993年的功能。其他事情是getopt处理,其中(也是1993年)内置的处理程序简单而功能强大,除非使用docopt,否则您仍然无法获得。我声称bash坚持不懈地走了20多年,并且在这里花费的时间或数百万不佳的getopts使用所花费的时间是无法估量的-仅被接受是因为大多数人永远不会知道。
Florian Heigl

30

这是一个“常见”错误。管道会创建SubShell,因此,它们while read在与脚本不同的shell上运行,这使您的CNT变量永不更改(仅管道子shell中的变量)。

将最后一个echo与子外壳while进行分组以对其进行修复(有许多其他方法可以对其进行修复,这是一种。Iain和Ignacio的答案还有其他方法。)

CNT=0

 cat input.data | ( while read 
do
  let CNT++;
  echo "Counting to $CNT"
done 
echo "Count is $CNT" )

详细说明:

  1. CNT在脚本中声明值为0;
  2. 子shell启动的|while read;
  3. 您的$CNT变量将导出到值为0的SubShell;
  4. SubShell进行计数并将其CNT值增加到5;
  5. SubShell结束,变量和值被破坏(它们不返回到调用进程/脚本)。
  6. echo的原始CNT值为0。

2
我曾经写过的第一个shell脚本给了我同样的问题,将我的头撞在墙上一会儿,然后才发现管道产生了更多的外壳。您在管道中遇到的任何变量都会在管道结束时超出范围-这意味着,如果您确实要对使用该变量的管道之外的变量进行某些操作,则必须通过诸如临时文件之类的时髦内容来保持状态。
光电离

好的答案,很遗憾,我只能给一个接受奖金。抱歉。
wolfgangsz

10

这有效

CNT=0 

while read ;
do
  let CNT++;
  echo "Counting to $CNT"
done <input.data
echo "Count is $CNT"

我喜欢这种聪明的方法,因为您知道所需的数据在哪里,只需要取回即可。如果您不知道高技能的解决方案,则可以随时“读取文件”哈哈哈哈。为您+1。
erm3nda 2015年

1
任何阅读此书的人都请注意,只有当您的脚本显式调用bash时,Iain提供的解决方案才有效,因为第一行是#!/ bin / bash,而#!/ bin / sh将不起作用。
Roadowl

1
有趣的是,我见过的第一个示例实际上是对Cat无用使用导致代码无法正常工作的地方。顺便说一下,@ Roadowl,这里唯一的bashism是let CNT++应该改为CNT="$((CNT+1))"使用符合POSIX的算术扩展的行。其余的已经可以移植了。
通配符

6

尝试将数据传递到子外壳中,就像在while循环之前将其作为文件一样。这类似于lain的解决方案,但是假设您不需要一些间歇文件:

total=0
while read var
do
  echo "variable: $var"
  ((total+=var))
done < <(echo 45) #output from a command, script, or function
echo "total: $total"
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.