Bash 循环中的计数器增量不起作用

我有以下简单的脚本,其中我正在运行一个循环,并希望维护一个 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.example"$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
393830 次浏览

尝试使用

COUNTER=$((COUNTER+1))

而不是

COUNTER=$((COUNTER))

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

首先,不要增加计数器。将 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

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

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

顺便说一下,你应该能够转换所有的 grep, grep, awk, awk, awk到一个单一的 awk

从 Bash 4.2开始,有一个 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

我认为这个单独的 awk 呼叫相当于你的 grep|grep|awk|awk管道: 请测试它。您的最后一个 awk 命令似乎什么也没有改变。

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

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
}
COUNTER=1
while [ Your != "done" ]
do
echo " $COUNTER "
COUNTER=$[$COUNTER +1]
done

森托斯,苏珊,RH

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

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

(( COUNTER++ ))

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

let COUNTER++

如果你认为这样可以提高可读性的话。有时候,Bash 给出了太多的方法——我想是 Perl 哲学——而 Python“只有一种正确的方法可以做到这一点”可能更合适。这是一个有争议的声明,如果曾经有一个!无论如何,我建议(在这种情况下)的目标不仅是增加一个变量,而且(一般规则)还要编写其他人能够理解和支持的代码。从众对实现这一目标有很大的帮助。

高温

极简主义者

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

这就是你需要做的:

$((COUNTER++))

以下节选自 学习 bash Shell,第3版,147,148页:

Bash 算术表达式等效于 优先级和结合性是相同的 表6-2显示了支持的算术运算符。 尽管其中一些字符是(或包含)特殊字符,但是 不需要反斜杠-转义它们,因为它们在 $(...)中 语法。

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

+ + 和-运算符在需要增量或 将一个值减1。[11]它们的工作原理与 Java 和 C 中相同, 例如,价值 + + 将值增加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/

这是一个简单的例子

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

导致 ((var++))表达式失败的原因有两个:

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

  2. 从一(1)开始是可以的,但是在计算“ + +”时,零会导致增量返回“1”,这在严格模式下是一个非零返回代码失败。

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

源脚本中的 subshell 有一些问题。 第一个例子,你可能不需要 subshell,但是我们不知道“ Some more action”下面隐藏了什么。 最流行的解决方案有隐藏的 bug,这会增加 I/O,并且不能使用 subshell,因为它还原了 Couter 的内部循环。

不要忘记添加“符号”,它会通知 bash 解释器有关行继续的信息。我希望这对你或者任何人都有帮助。但是在我看来,这个脚本应该完全转换为 AWK 脚本,或者使用 regexp 或 perl 重写为 python,但是 perl 多年来的流行程度会降低。最好用蟒蛇。

更正版本:

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

如果确实需要使用 subshell 的版本

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