Bash循环,如何打印当前迭代次数?

40

假设你有一个简单的循环:

while read line
do
  printf "${line#*//}\n"
done < text.txt
有没有一种优雅的方法可以将当前迭代次数与输出一起打印出来?就像这样:

0 The
1 quick
2 brown
3 fox

我希望避免在每个循环中设置一个变量并对其进行递增。

5个回答

66
为了实现这一点,您需要在每次迭代中增加一个计数器(就像您试图避免的那样)。
count=0
while read -r line; do
   printf '%d %s\n' "$count" "${line*//}"
   (( count++ ))
done < test.txt

编辑:经过进一步思考,如果你使用的是bash 4或更高版本,则可以在不使用计数器的情况下完成:

mapfile -t arr < test.txt
for i in "${!arr[@]}"; do
   printf '%d %s' "$i" "${arr[i]}"
done

mapfile内置函数将文件的全部内容读入数组中。然后,您可以遍历该数组的索引,这些索引将是行号,并访问该元素。


3
不错的解决方案,但如果你要处理大文件,mapfile可能会非常耗费内存。不知道原帖中的文件大小,我建议使用通过管道运行的方法,这样它对文件大小是不敏感的。 - ghoti

29

虽然不常见,但是你可以在 while 循环的条件语句中包含多个命令。以下示例仍需要显式计数器变量,但是对于某些用途来说,这种安排可能更合适或有吸引力。

while ((i++)); read -r line
do
    echo "$i $line"
done < inputfile

while条件由最后一个命令返回的内容决定(在这种情况下为read)。

有些人更喜欢将do与同一行上。这是它看起来的样子:

while ((i++)); read -r line; do
    echo "$i $line"
done < inputfile

6
@jordanm说:for 做的事情类似: for ((i=0, j=14; i<=20; i++, j+=11))。另外,你可以一次读取文件中的两行(或更多行):while read -r oddline; read -r evenline; do echo "odd: [$oddline] even: [$evenline]"; done < filename - Dennis Williamson

7

您可以使用范围进行遍历,它可以是数组、字符串、输入行或列表。

在此示例中,我使用数字列表 [0..10] 并增量为2。

#!/bin/bash
for i in {0..10..2}; do 
   echo " $i times"
done

输出结果如下:
 0 times
 2 times
 4 times
 6 times
 8 times
 10 times

要打印出索引而不考虑循环范围,你需要使用一个变量"COUNTER=0"并在每次迭代中增加它"COUNTER+1"。 我的解决方案打印出每个迭代结果,FOR遍历输入行并每次迭代递增一次,同时显示输入行中的每个单词:
#!/bin/bash 

COUNTER=0
line="this is a sample input line"

for word in $line; do        
    echo "This i a word number $COUNTER: $word"
    COUNTER=$((COUNTER+1))
done

输出结果为:
This i a word number 0: this
This i a word number 1: is
This i a word number 2: a
This i a word number 3: sample
This i a word number 4: input
This i a word number 5: line

了解更多关于循环的内容,请访问:此链接

测试你的脚本,请访问:此链接


虽然这段代码可能回答了问题,但提供有关它如何以及/或为什么解决问题的附加上下文将改善答案的长期价值。 - Nic3500

5
n=0
cat test.txt | while read line; do
  printf "%7s %s\n" "$n" "${line#*//}"
  n=$((n+1))
done

当然,在 Bourne Shell 中也可以使用这个方法。

如果您真的想避免递增变量,那么可以通过 grep 或 awk 管道输出:

cat test.txt | while read line; do
  printf " %s\n" "${line#*//}"
done | grep -n .

或者

awk '{sub(/.*\/\//, ""); print NR,$0}' test.txt

编辑以包括您的最终标准选项。 - Graham
我更喜欢 awk 的解决方案,尽管 sub(/.*\/\//, ""); 并不等同于 ${line#*//} - ghoti
1
好的...你回答的第一部分基本上和jordanm的一样,而另外两个似乎也很好用。归功于StackOverflow的随机性吧。 :-) - ghoti
cat 命令无用,而且没有使用 -r 选项的 read 命令会破坏输入中的反斜杠。这是重新实现 nl 的一种非常低效的方式。 - tripleee

1

更新:这里发布的其他答案更好,特别是@Graham和@DennisWilliamson的答案。

类似于这样的东西应该适合:

tr -s ' ' '\n' <test.txt | nl -ba

如果您想从0开始索引,可以在nl命令中添加-v0标志。


在FreeBSD、OSX或Illumos中,我没有“n1”命令。它是从哪里来的? - Graham
1
啊,可恶的等宽字体。@thb,我认为你仍然需要一些东西来剥离到//的行,就像${line#*//}那样。但是你肯定可以通过nl将while循环管道化,就像我在我的答案中使用grep -n .一样。 - Graham

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