在Bash脚本中实现计数器
1. 概述
实现计数器是几乎所有编程语言中的常用技术。在本教程中,我们将了解如何在 Bash 脚本中实现计数器。此外,我们将讨论一些常见的陷阱以及如何正确解决问题。
2. 在 Bash 中递增一个整数
通常,当我们需要一个计数器时,我们希望在循环中递增它的值。因此,如果我们知道如何在 Bash 中递增一个整数,那么实现一个计数器就不成问题了。
2.1. 使用let命令
** let 命令是一个内置的 Bash 命令,用于计算算术表达式。**
让我们创建一个简单的 shell 脚本let_cmd.sh来展示我们如何使用let命令递增整数变量:
$ cat let_cmd.sh
#!/bin/bash
COUNTER=0
printf "Initial value of COUNTER=%d\n" $COUNTER
let COUNTER=COUNTER+1
printf "After 'let COUNTER=COUNTER+1', COUNTER=%d\n" $COUNTER
let COUNTER++
printf "After 'let COUNTER++', COUNTER=%d\n" $COUNTER
如果我们运行 let_cmd.sh脚本,我们将得到输出:
$ ./let_cmd.sh
Initial value of COUNTER=0
After 'let COUNTER=COUNTER+1', COUNTER=1
After 'let COUNTER++', COUNTER=2
代码非常简单,我们看到let命令递增了整数变量COUNT。
2.2. 使用 Bash 算术扩展
我们可以使用*(( math expression ))*在 Bash 中计算数学表达式。
同样,我们创建一个简短的脚本 math_exp.sh来展示如何使用算术展开来计算数学表达式:
$ cat math_exp.sh
#!/bin/bash
COUNTER=0
printf "Initial value of COUNTER=%d\n" $COUNTER
COUNTER=$(( COUNTER + 1 ))
printf "After 'COUNTER=\$(( COUNTER + 1 ))', COUNTER=%d\n" $COUNTER
(( COUNTER++ ))
printf "After '(( COUNTER++ ))', COUNTER=%d\n" $COUNTER
如果我们执行脚本,我们将得到输出:
$ ./math_exp.sh
Initial value of COUNTER=0
After 'COUNTER=$(( COUNTER + 1 ))', COUNTER=1
After '(( COUNTER++ ))', COUNTER=2
如代码所示,如果我们想获得计算结果,我们可以在*(( … ))*前加上一个美元符号 “ $ ”。
3. 实现计数器
现在,是时候在 shell 脚本中实现一个计数器了。
假设我们想创建一个 shell 脚本counter.sh来计算命令输出中的行数。
为了更容易验证,我们将在测试中使用*“seq 5 ”*:
$ cat counter.sh
#!/bin/bash
COUNTER=0
for OUTPUT in $(seq 5)
do
let COUNTER++
done
printf "The value of the counter is COUNTER=%d\n" $COUNTER
如果我们执行脚本,计数器的值为 5:
$ ./counter.sh
The value of the counter is COUNTER=5
太好了,我们的柜台工作了!
4. 子shell陷阱
我们已经看到了如何创建一个计数器并在for循环中增加它的值。到目前为止,一切都很好。当我们读取命令的输出并进行计数时,我们经常使用while循环。
让我们用while循环做同样的计数:
$ cat pipe_count.sh
#!/bin/bash
COUNTER=0
seq 5 | while read OUTPUT
do
let COUNTER++
done
printf "The value of the counter is COUNTER=%d\n" $COUNTER
在上面的脚本中,我们只是将for循环更改为while循环,并将“ seq 5 ”命令的输出通过管道传递给while循环。
让我们看看计数器是否也能正常工作:
$ ./pipe_count.sh
The value of the counter is COUNTER=0
哎呀!为什么会这样?let命令 在while循环中不起作用 ?让我们在每个循环步骤后打印一些调试消息:
#!/bin/bash
COUNTER=0
seq 5 | while read OUTPUT
do
let COUNTER++
printf "[DEBUG] After a loop step COUNTER=%d\n" $COUNTER
done
printf "The value of the counter is COUNTER=%d\n" $COUNTERbbvg
现在,让我们重新运行脚本:
$ ./pipe_count.sh
[DEBUG] After a loop step COUNTER=1
[DEBUG] After a loop step COUNTER=2
[DEBUG] After a loop step COUNTER=3
[DEBUG] After a loop step COUNTER=4
[DEBUG] After a loop step COUNTER=5
The value of the counter is COUNTER=0
调试输出显示let命令在while循环中正常工作 。然而,计数器在循环后以某种方式“重置”为0 。
这个奇怪问题的原因是管道。当我们使用管道时,如command1 | command2,command2将在子shell中执行。子 shell 中发生的更改不会影响当前 shell,即使它是同一个变量。
回到我们的 pipe_count.sh脚本,因为我们将seq 5的输出通过管道 传递给while循环,while循环在子 shell 中运行。因此,当我们在循环后检查变量COUNTER时,它的值仍然是0。
接下来,让我们看看如何解决这个问题。
4.1. 使用流程替换
为了解决子shell问题,我们可以将 seq命令的 进程替换 重定向到 while循环:
$ cat ps_count.sh
#!/bin/bash
COUNTER=0
while read OUTPUT
do
let COUNTER++
done < <(seq 5)
printf "The value of the counter is COUNTER=%d\n" $COUNTER
这样, while循环不在子 shell 中运行,计数器最终会报告正确的值:
$ ./ps_count.sh
The value of the counter is COUNTER=5
4.2. 使用命名管道
或者,我们也可以使用命名管道 来解决这个问题:
$ cat ./fifo_count.sh
#!/bin/bash
COUNTER=0
NAMED_PIPE="./myFifo"
if [[ ! -p "$NAMED_PIPE" ]]; then
mkfifo $NAMED_PIPE
fi
seq 5 > "$NAMED_PIPE" &
while read OUTPUT
do
let COUNTER++
done < "$NAMED_PIPE"
rm "$NAMED_PIPE"
printf "The value of the counter is COUNTER=%d\n" $COUNTER
在上面的脚本中,我们创建了一个命名管道并使用seq 5命令为其提供数据。随后, while循环读取命名管道以获取seq命令的输出。
变量COUNTER将在while循环后具有预期值:
$ ./fifo_count.sh
The value of the counter is COUNTER=5