Bash 零基础教程

Bash 数字处理

在 Bash 脚本编程中,虽然变量默认主要被视为字符串,但对数值执行算术运算的能力,对于创建健壮且动态的自动化脚本来说是不可或缺的。无论你是处理系统性能指标、计算服务器资源使用率,还是仅仅管理一个简单的循环计数器,理解如何处理整数并执行数学计算都是一项核心技能。本章将为你提供在 Bash 脚本中高效执行整数算术的工具和技巧,为未来课程中更复杂的逻辑操作和数据清洗打下坚实的基础。

1. 认清 Bash 中“数字”的本质

在默认情况下,Bash 变量将所有值存储为字符串,即使这些值看起来完全是纯数字。

例如,如果你执行 COUNT=5INCREMENT=3,Bash 最初会将 COUNTINCREMENT 视为纯文本的 "5" 和 "3"。如果你尝试简单地用 echo "$COUNT + $INCREMENT" 将它们加起来,Bash 会在终端输出 5 + 3,因为它执行的是字符串拼接,而不是数学加法。

为了执行真正的数学运算,你需要显式地告诉 Bash 将这些字符串值“解释”为整数。Bash 提供了几种机制来实现这一点。

2. 算术扩展 $((expression)):现代首选方案

在 Bash 中执行整数算术最常见、最灵活且最现代的方法是使用算术扩展 (Arithmetic Expansion),其语法结构为 $((expression))。这个结构会将括号内的 expression 作为数学算式进行计算,并将最终结果替换回命令行中。

关键便利性:((...)) 结构内部,引用变量时不需要在变量名前加 $ 符号;Bash 会自动将它们识别为整数变量。

2.1 基本运算符:加、减、乘、除、取模

算术扩展支持所有标准的数学运算符:

  • + : 加法
  • - : 减法
  • * : 乘法
  • / : 除法 (注意:Bash 仅支持整数除法,小数部分会被直接舍弃/截断)
  • % : 取模 (除法后的余数)
#!/bin/bash

num1=10
num2=3
result="" 

echo "--- 基础算术运算 ---"

# 加法
result=$((num1 + num2))
echo "加法: $num1 + $num2 = $result" # 输出: 13

# 减法
result=$((num1 - num2))
echo "减法: $num1 - $num2 = $result" # 输出: 7

# 乘法
result=$((num1 * num2))
echo "乘法: $num1 * $num2 = $result" # 输出: 30

# 除法 (整数除法)
result=$((num1 / num2))
echo "除法 (整数): $num1 / $num2 = $result" # 输出: 3 (真实结果 3.333... 的小数部分被截断)

# 取模 (求余)
result=$((num1 % num2))
echo "取模: $num1 % $num2 = $result" # 输出: 1 (10 除以 3 商 3 余 1)

# 在结构内部不需要 '$' 符号
count=5
increment=2
new_count=$((count + increment))
echo "直接使用变量: count ($count) + increment ($increment) = $new_count" # 输出: 7

# 复合赋值 (将计算结果累加回原变量)
initial_value=15
initial_value=$((initial_value + 5))
echo "增加 5 之后: $initial_value" # 输出: 20

2.2 指数运算 (乘方)

Bash 的算术扩展还支持使用 ** 运算符进行指数运算。

#!/bin/bash
base=2
power=3
result=$((base ** power))
echo "$base 的 $power 次方是: $result" # 输出: 8 (即 2*2*2)

2.3 运算优先级 (Order of Operations)

Bash 遵循标准的数学运算优先级(先乘除后加减)。你可以使用小括号 () 来改变默认的优先级。

#!/bin/bash
a=2
b=3
c=4

# 无小括号: 先乘法后加法
result=$((a + b * c))
echo "$a + $b * $c = $result" # 输出: 14 (3*4=12, 然后 2+12=14)

# 使用小括号: 强制先加法后乘法
result=$(((a + b) * c))
echo "($a + $b) * $c = $result" # 输出: 20 (2+3=5, 然后 5*4=20)

2.4 处理“除以零”错误

在数学中,除以零是未定义的操作。在 Bash 的算术扩展中,尝试除以零通常会导致运行时的严重报错,并可能导致脚本直接终止运行。

dividend=10
divisor=0

# 此操作将引发严重错误
result=$((dividend / divisor)) 
# 终端会报错:bash: 10 / 0: division by 0 (error token is "0")

在编写脚本时,尤其当除数(divisor)来源于用户输入或动态抓取的系统数据时,必须极其小心。你需要在执行除法之前添加检查机制(这将在模块 3 的条件语句中学习),以防止这种致命崩溃。

3. let 命令:用于赋值与自增减

let 命令提供了另一种执行整数算术和变量赋值的方法。与算术扩展类似,let 也会将其后面的参数作为算术表达式进行求值。

3.1 基本用法

使用 let 时,表达式内部的变量也无需加 $. 但是,如果表达式中包含空格或特殊字符,你必须用双引号将整个表达式括起来。

x=10
y=5

# 基本加法
let sum=x+y
echo "和: $sum" # 输出: 15

# 包含空格的乘法,必须加引号
let "product = x * y" 
echo "积: $product" # 输出: 50

3.2 变量递增与递减 (自增/自减)

let 在执行简单的递增和递减操作时特别顺手,它完美支持 C 语言风格的快捷操作符,如 ++--。当然,不带 $((...)) 结构也可以做到这一点。

counter=0

# 使用 let 递增
let counter++
echo "let counter++ 之后: $counter" # 输出: 1

# 使用 let 复合累加
let counter+=5 # 等同于 counter = counter + 5
echo "let counter+=5 之后: $counter" # 输出: 6

# 使用 ((...)) 结构直接操作 (推荐用法,更简洁)
num=10
((num++)) # num 自增 1
echo "((num++)) 之后: $num" # 输出: 11

((num-=2)) # num 自减 2
echo "((num-=2)) 之后: $num" # 输出: 9

提示: 不带前导 $((...)) 是一个独立的“算术求值命令”。它不返回文本结果,而是直接在后台修改变量的值。它经常被用作 if 语句中的条件判断。

4. expr 命令 (传统/遗留方案)

expr 命令是一个外部工具(而不是 Bash 的内置功能),用于计算表达式。这是一种较老的在 Bash 及其他 Shell 中执行数学运算的方法。虽然它仍然可用,但由于其冗长的语法和较低的性能(因为它需要调用外部程序),现代脚本中基本已使用 $((...)) 替代它

expr 的一个显著痛点是:它要求每个运算符和操作数都必须用空格隔开,并且像 *(乘号,同时也是通配符)这样的特殊字符必须被转义

val1=20
val2=4

# 加法 (注意加号两边的空格)
sum=$(expr $val1 + $val2) 

# 乘法 (必须用反斜杠转义星号)
product=$(expr $val1 \* $val2) 
echo "expr 乘法: $product" # 输出: 80

了解 expr 有助于你看懂一些上古时期流传下来的旧脚本,但在你自己的新项目中,请果断使用 $((...))

5. 实战演练:算术运算在系统管理中的应用

让我们将整数算术融入到系统管理任务中,结合我们之前学过的变量知识。

5.1 计算并格式化系统运行时间 (Uptime)

系统的原始运行时间通常是以纯“秒”为单位提供的。我们可以使用整数算术(特别是除法和取模)将其转换为人类易读的“小时、分钟、秒”格式。

#!/bin/bash
# 假设我们从系统中获取到的总运行时间是 3665 秒
total_uptime_seconds=3665 
echo "总运行秒数: $total_uptime_seconds"

# 1. 计算小时数 (一小时 3600 秒)
hours=$((total_uptime_seconds / 3600)) 

# 2. 计算去掉小时后剩下的秒数
remainder_seconds=$((total_uptime_seconds % 3600))

# 3. 从剩余秒数中计算分钟数 (一分钟 60 秒)
minutes=$((remainder_seconds / 60))

# 4. 最后剩下的就是零头秒数
final_seconds=$((remainder_seconds % 60))

echo "格式化后的系统运行时间: $hours 小时, $minutes 分钟, $final_seconds 秒"
# 输出: 格式化后的系统运行时间: 1 小时, 1 分钟, 5 秒

5.2 模拟基础的服务器资源分配

想象一个管理有限“连接槽位”或“资源”的脚本。我们可以使用算术来实时跟踪分配情况。

#!/bin/bash
total_slots=100
allocated_slots=35
user_request=15

echo "总槽位: $total_slots"
echo "已分配槽位: $allocated_slots"

# 计算剩余槽位
remaining_slots=$((total_slots - allocated_slots))
echo "剩余可用: $remaining_slots" # 输出: 65

# 模拟用户请求新的槽位
echo "用户请求分配 $user_request 个槽位..."
new_allocated=$((allocated_slots + user_request))
new_remaining=$((total_slots - new_allocated))

echo "分配后状态: 已使用 = $new_allocated, 剩余 = $new_remaining"
# 输出: 分配后状态: 已使用 = 50, 剩余 = 50