【问题标题】:Counter increment in Bash loop not workingBash循环中的计数器增量不起作用
【发布时间】:2012-05-17 23:03:44
【问题描述】:

我有以下简单的脚本,我正在运行一个循环并希望维护一个COUNTER。我无法弄清楚为什么计数器没有更新。是因为创建了子shell吗?我该如何解决这个问题?

#!/bin/bash

WFY_PATH=/var/log/nginx
WFY_FILE=error.log
COUNTER=0
grep 'GET /log_' $WFY_PATH/$WFY_FILE | grep 'upstream timed out' | awk -F ', ' '{print $2,$4,$0}' | awk '{print "http://domain.com"$5"&ip="$2"&date="$7"&time="$8"&end=1"}' | awk -F '&end=1' '{print $1"&end=1"}' |
(
while read WFY_URL
do
    echo $WFY_URL #Some more action
    COUNTER=$((COUNTER+1))
done
)

echo $COUNTER # output = 0

【问题讨论】:

  • 您不需要将while循环放入子shell。只需删除 while 循环周围的括号,就足够了。或者如果你必须把它循环到子shell中,那么在完成后,将计数器转储到临时文件中,然后在子shell之外恢复这个文件。我会准备最后的程序给你答复。

标签: linux bash shell scripting counter


【解决方案1】:

源脚本的子shell 有一些问题。 第一个例子,你可能不需要 subshel​​l。但我们不知道“Some more action”下隐藏了什么。 最流行的答案有隐藏的错误,这会增加 I/O,并且不适用于 subshel​​l,因为它会在循环中恢复 couter。

不要加 '\' 符号,它会通知 bash 解释器关于行继续。我希望它会帮助你或任何人。但在我看来,这个脚本应该完全转换为 AWK 脚本,或者使用 regexp 或 perl 重写为 python,但是 perl 多年来的流行度下降了。最好用 python 来做。

没有子shell的更正版本:

#!/bin/bash
WFY_PATH=/var/log/nginx
WFY_FILE=error.log
COUNTER=0
grep 'GET /log_' $WFY_PATH/$WFY_FILE | grep 'upstream timed out' |\
awk -F ', ' '{print $2,$4,$0}' |\
awk '{print "http://example.com"$5"&ip="$2"&date="$7"&time="$8"&end=1"}' |\
awk -F '&end=1' '{print $1"&end=1"}' |\
#(  #unneeded bracket
while read WFY_URL
do
    echo $WFY_URL #Some more action
    COUNTER=$((COUNTER+1))
done
# ) unneeded bracket

echo $COUNTER # output = 0

如果确实需要,则带有 subshel​​l 的版本

#!/bin/bash

TEMPFILE=/tmp/$$.tmp  #I've got it from the most popular answer
WFY_PATH=/var/log/nginx
WFY_FILE=error.log
COUNTER=0
grep 'GET /log_' $WFY_PATH/$WFY_FILE | grep 'upstream timed out' |\
awk -F ', ' '{print $2,$4,$0}' |\
awk '{print "http://example.com"$5"&ip="$2"&date="$7"&time="$8"&end=1"}' |\
awk -F '&end=1' '{print $1"&end=1"}' |\
(
while read WFY_URL
do
    echo $WFY_URL #Some more action
    COUNTER=$((COUNTER+1))
done
echo $COUNTER > $TEMPFILE  #store counter only once, do it after loop, you will save I/O
)

COUNTER=$(cat $TEMPFILE)  #restore counter
unlink $TEMPFILE
echo $COUNTER # output = 0

【讨论】:

    【解决方案2】:

    有两种情况导致表达式 ((var++)) 对我来说失败:

    1. 如果我将 bash 设置为严格模式 (set -euo pipefail) 并且如果我从零开始增量 (0)。

      李>
    2. 从一 (1) 开始很好,但在计算“++”时,零会导致增量返回“1”,这是严格模式下的非零返回代码故障。

    我可以使用((var+=1))var=$((var+1)) 来逃避这种行为

    【讨论】:

    • 我遇到了同样的问题。互联网搜索让我找到了这个答案。 “++”运算符的令人费解和意外的行为。 set -e; x=-2; while [ "$x" -le 2 ]; do (( x++ )) || echo "error incrementing x to $x"; done; 导致将 x 增加到 1 时出错
    • 请注意,递减运算符-- 也包含此“功能”。运算符返回成功​​,直到从零递减。将循环更改为向后运行,您将得到error decrementing x to -1
    • 正如 Ilkka Virta 向我解释的那样(他回答了 bug-bash@gnu.org 的问题),这不是 inc/dec 运算符,而是 ((...)) 构造。如果表达式的结果为零,((...)) 返回状态 1,这使其对条件表达式很有用:if (( 100-100 )); then echo true; else echo false; fi 如果您的计数器从零开始,您可以使用 ((++x)),它返回值 after 表达式被计算,而((x++)) 返回 x before 的值,它增加,导致“假/错误”返回值。
    【解决方案3】:

    这是一个简单的例子

    COUNTER=1
    for i in {1..5}
    do   
       echo $COUNTER;
       //echo "Welcome $i times"
       ((COUNTER++));    
    done
    

    【讨论】:

    • 简单例子,但不适用于提问。
    【解决方案4】:

    您可以通过使用进程替换来避免在while 循环周围创建子shell,而不是使用临时文件。

    while ...
    do
       ...
    done < <(grep ...)
    

    顺便说一句,您应该能够将所有grep, grep, awk, awk, awk 转换为单个awk

    从 Bash 4.2 开始,有一个 lastpipe 选项

    运行最后一个命令 当前 shell 上下文中的管道。 lastpipe 选项没有 启用作业控制后的效果。

    bash -c 'echo foo | while read -r s; do c=3; done; echo "$c"'
    
    bash -c 'shopt -s lastpipe; echo foo | while read -r s; do c=3; done; echo "$c"'
    3
    

    【讨论】:

    • 如果您想在循环内增加一个计数器并在完成后在外部使用它,那么进程替换非常好,进程替换的问题是我发现无法同时获取已执行命令的状态代码,在使用管道时可以使用 ${PIPESTATUS[*]}
    • @chrisweb:我添加了有关lastpipe 的信息。顺便说一句,您可能应该使用"${PIPESTATUS[@]}"(而不是星号)。
    • 勘误表。在 bash 中(不是在 perl 中,因为我之前错误地写过)退出代码是一个表格,然后您可以单独检查管道链中的所有退出代码。在首先测试之前,您的步骤必须复制此表,否则在第一个命令之后您将丢失所有值。
    • 这是对我有用的解决方案,并且不使用外部文件来存储变量的值,这在我看来太行人了。
    【解决方案5】:

    这就是你需要做的所有事情:

    $((COUNTER++))
    

    这是Learning the bash Shell,第 3 版,第 147、148 页的摘录:

    bash 算术表达式等价于它们在 Java 和 C 语言。[9]优先级和关联性相同 与 C 中一样。表 6-2 显示了支持的算术运算符。 虽然其中一些是(或包含)特殊字符,但有 无需反斜杠转义它们,因为它们在 $((...)) 内 语法。

    ......................

    ++ 和 - 运算符在您想要递增或 将值减一。 [11]它们的工作方式与 Java 和 C 相同, 例如,value++ 将 value 增加 1。这称为 post-increment; 还有一个预增量:++。差异变得明显 举个例子:

    $ i=0
    $ echo $i
    0
    $ echo $((i++))
    0
    $ echo $i
    1
    $ echo $((++i))
    2
    $ echo $i
    2
    

    http://www.safaribooksonline.com/a/learning-the-bash/7572399/

    【讨论】:

    • 这是我需要的这个版本,因为我在if语句的条件下使用它:if [[ $((needsComma++)) -gt 0 ]]; then printf ',\n'; fi对或错,这是唯一可靠工作的版本。跨度>
    • 此表单的重要之处在于您可以在单个步骤中使用增量。 i=1; while true; do echo $((i++)); sleep .1; done
    • @LS: if (( needsComma++ &gt; 0 )); thenif (( needsComma++ )); then
    • 在 bash 中使用 "echo $((i++))" 我总是得到 "/opt/xyz/init.sh: line 29: i: command not found" 我做错了什么?跨度>
    • 这并没有解决关于在循环之外获取计数器值的问题。
    【解决方案6】:
    count=0   
    base=1
    (( count += base ))
    

    【讨论】:

      【解决方案7】:

      极简主义

      counter=0
      ((counter++))
      echo $counter
      

      【讨论】:

      • 简单的一个:-)。谢谢@geekzspot
      • 在问题中不起作用,因为有子shell
      【解决方案8】:
      COUNTER=$((COUNTER+1)) 
      

      在现代编程中是一个相当笨拙的结构。

      (( COUNTER++ ))
      

      看起来更“现代”。你也可以使用

      let COUNTER++
      

      如果您认为这样可以提高可读性。有时,Bash 提供了太多的做事方式——我想是 Perl 哲学——而 Python “只有一种正确的方式去做”可能更合适。如果有的话,这是一个值得商榷的声明!无论如何,我建议目标(在这种情况下)不仅仅是增加一个变量,而是(一般规则)还要编写其他人可以理解和支持的代码。一致性对于实现这一目标大有帮助。

      HTH

      【讨论】:

      • 这并没有解决最初的问题,即如何在结束(子流程)循环后获取计数器中的更新值
      【解决方案9】:
      COUNTER=1
      while [ Your != "done" ]
      do
           echo " $COUNTER "
           COUNTER=$[$COUNTER +1]
      done
      

      经过测试的 BASH:Centos、SuSE、RH

      【讨论】:

      • @kroonwijk 方括号前需要有一个空格(正式来说,“分隔单词”)。否则 Bash 无法看到前一个表达式的结尾。
      • 问题是关于管道的一段时间,所以在创建子外壳的地方,你的答案是正确的,但你不使用管道,所以它没有回答问题
      • 根据 chepner 对另一个答案的评论,$[ ] 语法已被弃用。 stackoverflow.com/questions/10515964/…
      • 这不能解决主要问题,主循环放在子shell下
      • 无限循环?还有一个 for() 循环?
      【解决方案10】:

      我认为这个单一的 awk 调用相当于你的 grep|grep|awk|awk 管道:请测试它。您的最后一个 awk 命令似乎没有任何改变。

      COUNTER 的问题在于 while 循环在子 shell 中运行,因此当子 shell 退出时,对变量的任何更改都会消失。您需要在同一个子 shell 中访问 COUNTER 的值。或者采纳@DennisWilliamson 的建议,使用进程替换,完全避免使用子shell。

      awk '
        /GET \/log_/ && /upstream timed out/ {
          split($0, a, ", ")
          split(a[2] FS a[4] FS $0, b)
          print "http://example.com" b[5] "&ip=" b[2] "&date=" b[7] "&time=" b[8] "&end=1"
        }
      ' | {
          while read WFY_URL
          do
              echo $WFY_URL #Some more action
              (( COUNTER++ ))
          done
          echo $COUNTER
      }
      

      【讨论】:

      • 谢谢,最后一个 awk 基本上会删除 end=1 之后的所有内容,并在末尾添加一个新的 end=1 (以便下次我们可以删除后面附加的所有内容)。
      • @SparshGupta,前一个 awk 在 "end=1" 之后不打印任何内容。
      • 这对问题脚本有很好的改进,但不能解决子shell内增加计数器的问题
      【解决方案11】:

      首先,您没有增加计数器。将COUNTER=$((COUNTER)) 更改为COUNTER=$((COUNTER + 1))COUNTER=$[COUNTER + 1] 会增加它。

      其次,根据您的推测,将子shell 变量反向传播给被调用者会比较棘手。子shell 中的变量在子shell 之外不可用。这些是子进程的本地变量。

      解决它的一种方法是使用临时文件来存储中间值:

      TEMPFILE=/tmp/$$.tmp
      echo 0 > $TEMPFILE
      
      # Loop goes here
        # Fetch the value and increase it
        COUNTER=$[$(cat $TEMPFILE) + 1]
      
        # Store the new value
        echo $COUNTER > $TEMPFILE
      
      # Loop done, script done, delete the file
      unlink $TEMPFILE
      

      【讨论】:

      • @chepner 你有没有说$[...] 已被弃用的参考资料?有其他解决方案吗?
      • $[...]$((...)) 被 POSIX shell 采用之前被 bash 使用。我不确定它是否曾经被正式弃用,但我在 bash 手册页中找不到它的提及,而且它似乎只支持向后兼容。
      • 另外,$(...) 优于 ...
      • @blong 这是一个关于 $[...] vs $((...)) 的 SO 问题,讨论并引用了弃用:stackoverflow.com/questions/2415724/…
      • 这个模板不好。它为每个循环、每个匹配的源转储临时文件。我们为什么要创建额外的 I/O ????只需在子外壳内循环后转储一次计数器。做一次。在子外壳之外,您可以将其恢复一次,而无需不必要的 I/O。这很重要,因为并非所有文件系统都有有效的写入取消。
      【解决方案12】:

      看来你没有更新counter是脚本,使用counter++

      【讨论】:

      • 抱歉错字,我实际上在脚本中使用了 ((COUNTER+1)),但它不起作用
      • 无论是按 value+1 递增,还是按 value++ 递增。子shell结束后,计数器值丢失,并恢复到此脚本开始时设置的初始0值。
      【解决方案13】:

      尝试使用

      COUNTER=$((COUNTER+1))
      

      而不是

      COUNTER=$((COUNTER))
      

      【讨论】:

      • 对不起,这是一个错字。它实际上是 ((COUNTER+1))
      • @AaronDigulla: (( COUNTER++ ))(没有美元符号)
      • 我不知道为什么,但我看到我的脚本在使用 (( COUNTER++ )) 时反复失败,但当我切换到 COUNTER=$((COUNTER + 1)) 时它起作用了。 GNU bash, version 4.1.2(1)-release (x86_64-redhat-linux-gnu)
      • 也许你的 hash bang 行将 bash 作为 /bin/sh 而不是 /bin/bash 运行?
      • 请删除你的回答,因为它只做信息噪音:) 主要问题,子shell内增加了密码
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-06-19
      • 2018-09-05
      • 1970-01-01
      • 2012-03-30
      • 2014-09-02
      • 1970-01-01
      相关资源
      最近更新 更多