
编写 Bash 脚本时最常见的算术操作之一就是对变量进行递增和递减。这最常用作循环中的计数器,但也可能出现在脚本的其他位置。
递增和递减分别表示对数值变量的当前值加上或减去一个值(通常是 1)。算术运算在双括号内运行,既可作为 ((...)) 命令或 $((...)) 展开,也可使用 let 内建命令。
本指南讲解在 Bash 中对变量进行递增和递减的最常用方法。
使用 + 和 - 运算符
递增或递减变量最简单的方法是使用 + 和 - 运算符。
i
=
$((
i+1
))
((
i
=
i+1
))
let
"i=i+1"
i
=
$((
i-1
))
((
i
=
i-1
))
let
"i=i-1"
这种方法允许你按任意想要的值对变量进行递增或递减。
下面是一个在 until 循环中递增计数器变量的示例:
i
=
0
until
[
$i
-gt
3
]
do
echo
i:
$i
((
i
=
i+1
))
done
i: 0
i: 1
i: 2
i: 3
+= 和 -= 运算符
除了上面讲解的基础运算符外,Bash 还提供了赋值运算符 += 和 -=。这些运算符用于将左操作数的值按运算符后面指定的值进行递增或递减。
((
i
+=
1
))
let
"i+=1"
((
i-
=
1
))
let
"i-=1"
在下面的 while 循环中,我们将 i 变量的值递减 5:
i
=
20
while
[
$i
-ge
5
]
do
echo
Number:
$i
let
"i-=5"
done
Number: 20
Number: 15
Number: 10
Number: 5
用 declare -i 声明整数变量
除非你另行说明,Bash 会把变量当作字符串处理,而 += 就让人栽在这里。在非算术上下文中,+= 会追加文本而不是做加法:
i
=
5
i
+=
3
echo
"i:
$i
"
i: 53
Bash 把 5 和 3 拼接成了 53。用 declare -i 把变量声明为整数,会改变该变量之后每次赋值的规则,使 += 变成加法,并让裸写的 i=i+1 无需双括号也能工作:
declare
-i
i
=
5
i
+=
3
echo
"i:
$i
"
i
=
i+1
echo
"i:
$i
"
i: 8
i: 9
整数属性随变量保留,因此当一个变量只保存计数时适合使用它。对于一次性的递增,((...)) 形式比脚本上方一行 declare 对读者来说更清晰。
使用 ++ 和 -- 运算符
++ 和 -- 运算符分别将其操作数递增或递减 1 并返回该值。
((
i++
))
((
++i
))
let
"i++"
let
"++i"
((
i--
))
((
--i
))
let
"i--"
let
"--i"
这些运算符可以用在操作数之前或之后。它们也被称为:
• 前缀递增:++i • 前缀递减:--i • 后缀递增:i++ • 后缀递减:i--
前缀运算符先将操作数递增或递减 1,然后返回操作数的新值。后缀运算符返回操作数在被递增或递减之前的值。
如果你只想递增或递减变量而不检查命令的退出状态,两种形式对变量的改变方式相同。不过它们的退出状态可能不同,因为算术命令在其表达式求值结果为 0 时返回状态 1。例如,当 i 从 0 开始时,((i++)) 返回状态 1,而 ((++i)) 返回状态 0。
这种差异在使用了 set -e 的脚本中很重要,独立的 ((i++)) 会在第一次递增时就让脚本停止。当你需要一个不会把算术结果作为退出状态的递增命令时,使用 i=$((i+1))。
以下示例演示了 ++ 运算符在操作数之前和之后使用时的工作方式:
x
=
5
y
=
$((
x++
))
echo
x:
$x
echo
y:
$y
x: 6
y: 5
x
=
5
y
=
$((
++
x
))
echo
x:
$x
echo
y:
$y
x: 6
y: 6
下面是一个在 Bash 脚本中如何使用后缀递增运算符的示例:
#!/bin/bash
i
=
0
while
true
;
do
if
[[
"
$i
"
-gt
3
]]
;
then
break
fi
echo
"i:
$i
"
((
i++
))
done
同样的运算符驱动 C 风格 for 循环的头部,其中第三部分在每次迭代之后运行,并且可以步进任意值:
for
((
i
=
0
;
i < 6
;
i
+=
2
))
;
do
echo
"i:
$i
"
done</code></pre>
<pre style="margin:0 0 10px;padding:9.5px;line-height:18.5667px;overflow-wrap:break-word;background-color:rgb(245,245,245);border:1px solid rgb(204,204,204);border-radius:4px;color:rgb(51,51,51);font-size:16px;word-break:break-all;white-space:pre-wrap;"><code spellcheck="false">i: 0
i: 2
i: 4</code></pre>
<p>循环在 6 处停止且不打印它,因为测试在循环体之前运行,而 6 < 6 为假。</p>
<p>使用 ++ 和 -- 的缺点是变量只能按 1 递增或递减。要按更大的值步进,请改用 += 或 -=。</p>
<h4 style="margin: 20px 0;">快速参考</h4>
<table style="border-collapse:collapse;width:100%;margin:15px 0;font-size:16px;">
<tr><th style="border:1px solid #ddd;padding:8px;background:#f5f5f5;">Method</th><th style="border:1px solid #ddd;padding:8px;background:#f5f5f5;">Increment</th><th style="border:1px solid #ddd;padding:8px;background:#f5f5f5;">Decrement</th></tr>
<tr><td style="border:1px solid #ddd;padding:8px;">基础运算符</td><td style="border:1px solid #ddd;padding:8px;"><code>i=$((i+1))</code></td><td style="border:1px solid #ddd;padding:8px;"><code>i=$((i-1))</code></td></tr>
<tr><td style="border:1px solid #ddd;padding:8px;">算术上下文</td><td style="border:1px solid #ddd;padding:8px;"><code>((i=i+1))</code></td><td style="border:1px solid #ddd;padding:8px;"><code>((i=i-1))</code></td></tr>
<tr><td style="border:1px solid #ddd;padding:8px;">let 内建命令</td><td style="border:1px solid #ddd;padding:8px;"><code>let "i=i+1"</code></td><td style="border:1px solid #ddd;padding:8px;"><code>let "i=i-1"</code></td></tr>
<tr><td style="border:1px solid #ddd;padding:8px;">赋值运算符</td><td style="border:1px solid #ddd;padding:8px;"><code>((i+=1))</code></td><td style="border:1px solid #ddd;padding:8px;"><code>((i-=1))</code></td></tr>
<tr><td style="border:1px solid #ddd;padding:8px;">let 赋值</td><td style="border:1px solid #ddd;padding:8px;"><code>let "i+=1"</code></td><td style="border:1px solid #ddd;padding:8px;"><code>let "i-=1"</code></td></tr>
<tr><td style="border:1px solid #ddd;padding:8px;">后缀简写</td><td style="border:1px solid #ddd;padding:8px;"><code>((i++))</code></td><td style="border:1px solid #ddd;padding:8px;"><code>((i--))</code></td></tr>
<tr><td style="border:1px solid #ddd;padding:8px;">前缀简写</td><td style="border:1px solid #ddd;padding:8px;"><code>((++i))</code></td><td style="border:1px solid #ddd;padding:8px;"><code>((--i))</code></td></tr>
</table>
<p>如需可打印的快速参考,请参阅 bash 速查表。</p>
<h4 style="margin: 20px 0;">故障排查</h4>
<p>integer expression expected 错误——这通常意味着变量为空或包含非数字文本。在算术运算之前初始化计数器,例如 i=0。</p>
<p>i++ fails with command not found——只在算术上下文(如 ((i++)) 或 $((i+1)))中使用递增和递减运算符。把 i++ 当作独立命令书写不是有效的 shell 语法。</p>
<p>Script works in Bash but fails in sh——某些算术形式是 Bash 特有的。如果脚本使用了 (( )) 等 Bash 特性,请用 Bash(#!/bin/bash)而不是 sh 来运行它。</p>
<p>Counter stays at 0 after a loop——默认情况下,把数据通过管道传入循环会在子 shell 中运行循环体,因此递增落在变量的一个副本上,并在循环退出时消失:</p>
<pre class="command" style="margin:10px 0px;padding:1em;line-height:1.1em;overflow-wrap:break-word;background-color:#e5f6fe;border:1px dashed rgb(47,111,171);border-radius:4px;color:#333333;font-size:16px;word-break:break-all;white-space:pre-wrap;font-style:italic;"><code spellcheck="false" style="color:#333333;">count
=
0
printf
'%s\n'
one two three
|
while
read
-r line
;
do
((
count++
))
done
echo
"count:
$count
"</code></pre>
<pre style="margin:0 0 10px;padding:9.5px;line-height:18.5667px;overflow-wrap:break-word;background-color:rgb(245,245,245);border:1px solid rgb(204,204,204);border-radius:4px;color:rgb(51,51,51);font-size:16px;word-break:break-all;white-space:pre-wrap;"><code spellcheck="false">count: 0</code></pre>
<p>改用进程替换通过输入重定向为循环提供数据,计数器就能保留:</p>
<pre class="command" style="margin:10px 0px;padding:1em;line-height:1.1em;overflow-wrap:break-word;background-color:#e5f6fe;border:1px dashed rgb(47,111,171);border-radius:4px;color:#333333;font-size:16px;word-break:break-all;white-space:pre-wrap;font-style:italic;"><code spellcheck="false" style="color:#333333;">count
=
0
while
read
-r line
;
do
((
count++
))
done
< <
(
printf
'%s\n'
one two three
)
echo
"count:
$count
"</code></pre>
<pre style="margin:0 0 10px;padding:9.5px;line-height:18.5667px;overflow-wrap:break-word;background-color:rgb(245,245,245);border:1px solid rgb(204,204,204);border-radius:4px;color:rgb(51,51,51);font-size:16px;word-break:break-all;white-space:pre-wrap;"><code spellcheck="false">count: 3</code></pre>
<p>value too great for base 错误——从文件、时间戳或命令输出获取的计数器可能带有前导零,而 Bash 会把前导零读作八进制数。08 和 09 不是有效的八进制数,因此递增失败。用 10# 前缀强制以十进制处理:</p>
<pre class="command" style="margin:10px 0px;padding:1em;line-height:1.1em;overflow-wrap:break-word;background-color:#e5f6fe;border:1px dashed rgb(47,111,171);border-radius:4px;color:#333333;font-size:16px;word-break:break-all;white-space:pre-wrap;font-style:italic;"><code spellcheck="false" style="color:#333333;">i
=
08
i
=
$((
10#
$i
+
1
))
echo
"i:
$i
"</code></pre>
<pre style="margin:0 0 10px;padding:9.5px;line-height:18.5667px;overflow-wrap:break-word;background-color:rgb(245,245,245);border:1px solid rgb(204,204,204);border-radius:4px;color:rgb(51,51,51);font-size:16px;word-break:break-all;white-space:pre-wrap;"><code spellcheck="false">i: 9</code></pre>
<h4 style="margin: 20px 0;">常见问题(FAQ)</h4>
<p>前缀递增和后缀递增有什么区别?两者都把变量改变 1,但当结果用于表达式中时有所不同。y=$((x++)) 先把原始值赋给 y,然后递增 x。y=$((++x)) 先递增 x,再把新值赋给 y。当它们作为算术命令使用时,退出状态也可能不同,这在使用 set -e 或其他基于状态的控制流时很重要。</p>
<p>我可以按 1 以外的值递增吗?可以。使用 ((i+=5)) 或 let "i+=5" 按任意值递增。++ 和 -- 简写只能步进 1。</p>
<p>计数器最常用在哪里?计数器最常用于 for、while 和 until 循环内部,以跟踪迭代次数或构建基于步长的序列。</p>
<p>我可以递减到零以下吗?可以。Bash 算术把变量当作有符号整数处理,因此计数器无需任何特殊处理即可变为负数。</p>
<h4 style="margin: 20px 0;">结论</h4>
<p>Bash 提供了基础算术、复合赋值以及前缀或后缀简写来改变计数器变量。对于更大的步进使用 += 或 -=,在使用 set -e 的脚本中留意 ((i++)) 的退出状态,下一步请参阅我们关于如何运行 Bash 脚本的指南。</p>