Bash循环中的计数器增量无效

时间:2012-05-09 12:20:50

标签: linux bash shell scripting counter

我有以下简单的脚本,我正在运行循环并希望维护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

13 个答案:

答案 0 :(得分:151)

首先,你没有增加柜台。将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

答案 1 :(得分:83)

COUNTER=1
while [ Your != "done" ]
do
     echo " $COUNTER "
     COUNTER=$[$COUNTER +1]
done

TESTED BASH:Centos,SuSE,RH

答案 2 :(得分:36)

COUNTER=$((COUNTER+1)) 

在现代编程中是一个非常笨拙的构造。

(( COUNTER++ ))

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

let COUNTER++

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

HTH

答案 3 :(得分:13)

count=0   
base=1
(( count += base ))

答案 4 :(得分:12)

尝试使用

COUNTER=$((COUNTER+1))

而不是

COUNTER=$((COUNTER))

答案 5 :(得分:10)

我认为这个单一的awk调用等同于你的grep|grep|awk|awk管道:请测试一下。你的上一个awk命令似乎什么都没改变。

COUNTER的问题是while循环在子shell中运行,因此当subshel​​l退出时,对变量的任何更改都会消失。您需要在相同的子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
}

答案 6 :(得分:8)

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

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

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

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

  

运行a的最后一个命令       当前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

答案 7 :(得分:5)

极简

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

答案 8 :(得分:4)

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

$((COUNTER++))

以下摘录自学习bash Shell ,第3版,第147,148页:

  

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

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

  

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

$ 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/

答案 9 :(得分:2)

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

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

  2. 从一(1)开始很好,但是零会导致在评估“ ++”时增量返回“ 1”,这是严格模式下的非零返回码失败。

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

答案 10 :(得分:1)

这是一个简单的示例

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

答案 11 :(得分:0)

您似乎没有更新counter脚本,请使用counter++

答案 12 :(得分:0)

源脚本的subshel​​l有问题。 第一个示例,您可能不需要subshel​​l。但是我们不知道“更多动作”下隐藏了什么。 最受欢迎的答案是隐藏的错误,它将增加I / O,并且不能与subshel​​l一起使用,因为它可以恢复内部循环。

不要强行添加'\'号,它将通知bash解释器有关行继续的信息。希望对您或任何人有帮助。但是在我看来,该脚本应该完全转换为AWK脚本,或者使用regexp或perl重写为python,但是多年以来perl的普及度下降了。最好使用python。

没有子外壳的正确版本:

#!/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

如果真的需要带子外壳的版本

#!/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