在Bash循环中递增一个变量

62
我正在尝试编写一个小脚本,用于计算日志文件中的条目数,并且我正在增加一个变量(USCOUNTER),希望在循环结束后使用它。但此时USCOUNTER看起来是0而不是实际值。有任何想法我做错了什么吗?谢谢!
FILE=$1

tail -n10 mylog > $FILE

USCOUNTER=0

cat $FILE | while read line; do
  country=$(echo "$line" | cut -d' ' -f1)
  if [ "US" = "$country" ]; then
        USCOUNTER=`expr $USCOUNTER + 1`
        echo "US counter $USCOUNTER"
  fi
done
echo "final $USCOUNTER"

输出结果为:

US counter 1
US counter 2
US counter 3
..
final 0

可能是A variable modified inside a while loop is not remembered的重复问题。 - codeforester
8个回答

65

您正在子Shell中使用 USCOUNTER,这就是为什么变量不在主Shell中显示的原因。

不要使用 cat FILE | while ...,而是使用 while ... done < $FILE。这样,您可以避免常见问题:我在管道中的循环中设置变量。为什么循环结束后它们消失了?或者为什么无法将数据传输到read命令中?

while read country _; do
  if [ "US" = "$country" ]; then
        USCOUNTER=$(expr $USCOUNTER + 1)
        echo "US counter $USCOUNTER"
  fi
done < "$FILE"

请注意,我还用 $() 替换了 `` 表达式。

我还用 while read country _ 替换了 while read line; do country=$(echo "$line" | cut -d' ' -f1)。这样可以让你使用 while read var1 var2 ... varN,其中 var1 包含行中的第一个单词,$var2 等等,直到 $varN 包含剩余内容。


请注意,您可以使用$(($USCOUNTER+1))来计算表达式。 - geert3
1
感谢@geert3,好的建议。还有anubhava所提供的 ((USCOUNTER++)) 作为解决方案。我不打算将其添加到我的答案中以防它与OP的shell不兼容。 - fedorqui

28

1
为什么不在if中使用 echo "US counter $((USCOUNTER++))" - Krzysztof Jabłoński
@KrzysztofJabłoński 如果你想让它与上面的代码等效,那么你应该使用"US counter $((++USCOUNTER))"。这是一个非常好的观点,你是正确的! - Aleks-Daniel Jakimenko-A.

26

极简主义

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

2
希望能够提供一些解释。 - rocketspacer
3
你的示例代码中想要的 while 循环在哪里? - tdaget
回显 $((++counter)) - zzapper

16

你得到了 final 0 是因为你的 while loop 正在一个子进程中执行,而在那里所做的任何更改都不会反映在当前(父)进程中。

正确的脚本:

while read -r country _; do
  if [ "US" = "$country" ]; then
        ((USCOUNTER++))
        echo "US counter $USCOUNTER"
  fi
done < "$FILE"

8
我在while循环中遇到了同样的$count变量丢失问题。@fedorqui的答案(以及其他一些答案)是对实际问题的准确回答:子shell确实是问题所在。
但这让我遇到了另一个问题:我并没有使用文件内容的管道处理...而是一系列管道和grep的输出...
我的错误示例代码:
count=0
cat /etc/hosts | head | while read line; do
  ((count++))
  echo $count $line
done
echo $count

感谢本帖和进程替换的帮助,我成功解决了问题:

count=0
while IFS= read -r line; do
  ((count++))
  echo "$count $line"
done < <(cat /etc/hosts | head)
echo "$count"

1
很高兴看到我的回答有所帮助 :) 还要注意的是,cat file | head 可以简化为 head file - fedorqui

1
USCOUNTER=$(grep -c "^US " "$FILE")

0

变量的递增可以像这样完成:

  _my_counter=$[$_my_counter + 1]

使用grep可以计算列中模式出现的次数

 grep -cE "^([^ ]* ){2}US"

-c count

([^ ]* ) 用于检测冒号

{2} 冒号的数量

US 您的模式


1
尽管这段代码可能回答了问题,但是提供关于它为什么及如何回答问题的额外背景信息将会显著提高它的长期价值。请编辑您的答案以添加一些解释。 - Toby Speight
1
标题是:“Bash循环中的变量递增” 我的代码行为是:“在bash中递增一个变量” 因此,我认为它回答了问题的一部分(至少与geekzspot的示例一样)。 - trax
1
算术扩展的 $[ ... ] 语法已经被弃用。请改用 $(( ... ))。你和 geekzspot 的回答都涉及了 OP 问题中不解决问题的 次要 方面(OP 代码中变量递增部分虽然效率低下但是仍在工作)。如果你想提出 次要 改进而不回答问题,请明确说明。 - mklement0

0

使用以下一行命令在Linux中更改多个文件名,使用短语特定性:

find -type f -name '*.jpg' | rename 's/holiday/honeymoon/'

对于所有扩展名为“.jpg”的文件,如果它们包含字符串“holiday”,则将其替换为“honeymoon”。例如,此命令将重命名文件“ourholiday001.jpg”为“ourhoneymoon001.jpg”。

此示例还说明了如何使用find命令发送带有扩展名为.jpg(-name '*.jpg')的文件列表(-type f)通过管道(|)进行重命名。然后,rename从标准输入读取其文件列表。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接