Bash 脚本编写教程 / 04 - 运算符
04 - 运算符
4.1 算术运算符
Bash 支持整数运算,不支持浮点数(需要 bc 或 awk)。
算术展开 $(( ))
a=10
b=3
echo "$((a + b))" # 加法: 13
echo "$((a - b))" # 减法: 7
echo "$((a * b))" # 乘法: 30
echo "$((a / b))" # 除法(整数): 3
echo "$((a % b))" # 取余: 1
echo "$((a ** 2))" # 幂运算: 100
# 自增自减
((a++))
echo "$a" # 11
((a--))
echo "$a" # 10
# 复合赋值
((a += 5)) # a = a + 5
((a -= 2)) # a = a - 2
((a *= 3)) # a = a * 3
((a /= 4)) # a = a / 4
((a %= 7)) # a = a % 7
算术运算的多种形式
# 方式一:$(( )) (推荐)
result=$((10 + 20))
# 方式二:(( )) 作为命令
((result = 10 + 20))
# 方式三:let 命令
let "result = 10 + 20"
let result=10+20 # 不含空格时可省略引号
# 方式四:expr 命令(旧语法)
result=$(expr 10 + 20) # 运算符两边必须有空格
# 方式五:declare -i
declare -i result
result=10+20
# 方式六:bc(支持浮点数)
result=$(echo "scale=2; 10 / 3" | bc)
echo "$result" # 3.33
浮点数运算
# Bash 不原生支持浮点数
# echo "$((10 / 3))" # 输出: 3(不是 3.33)
# 使用 bc
pi=$(echo "scale=10; 4 * a(1)" | bc -l)
echo "$pi" # 3.1415926535
# 使用 awk
result=$(awk "BEGIN {printf \"%.2f\", 10/3}")
echo "$result" # 3.33
# 使用 printf
printf "%.2f\n" "$(echo "10 / 3" | bc -l)" # 3.33
4.2 比较运算符
整数比较
| 运算符 | 含义 | 等价写法 |
|---|
-eq | 等于 (equal) | ==(在 (( )) 中) |
-ne | 不等于 (not equal) | != |
-gt | 大于 (greater than) | > |
-ge | 大于等于 (greater or equal) | >= |
-lt | 小于 (less than) | < |
-le | 小于等于 (less or equal) | <= |
a=10
b=20
# [ ] 传统写法
if [ "$a" -eq "$b" ]; then
echo "相等"
fi
# [[ ]] 推荐写法
if [[ $a -eq $b ]]; then
echo "相等"
fi
# (( )) 算术比较(最直观)
if ((a == b)); then
echo "相等"
fi
if ((a < b)); then
echo "$a 小于 $b"
fi
# 三元运算(在 (( )) 中)
max=$(( a > b ? a : b ))
echo "较大值: $max" # 较大值: 20
字符串比较
| 运算符 | 含义 | 示例 |
|---|
= / == | 相等 | [[ "$a" = "$b" ]] |
!= | 不相等 | [[ "$a" != "$b" ]] |
< | 字典序小于 | [[ "$a" < "$b" ]] |
> | 字典序大于 | [[ "$a" > "$b" ]] |
-z | 为空(长度为 0) | [[ -z "$a" ]] |
-n | 非空(长度大于 0) | [[ -n "$a" ]] |
=~ | 正则匹配 | [[ "$a" =~ ^[0-9]+$ ]] |
== 通配符 | 通配符匹配 | [[ "$a" == *.txt ]] |
name="hello world"
# 字符串比较(必须加引号)
[[ "$name" = "hello world" ]] && echo "相等"
[[ "$name" != "goodbye" ]] && echo "不相等"
# 空字符串检查
str=""
[[ -z "$str" ]] && echo "字符串为空"
[[ -n "$name" ]] && echo "字符串非空"
# 字典序比较
[[ "apple" < "banana" ]] && echo "apple 在 banana 前"
# 通配符匹配(不支持正则)
file="document.pdf"
[[ "$file" == *.pdf ]] && echo "是 PDF 文件"
# 正则匹配
email="[email protected]"
if [[ "$email" =~ ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$ ]]; then
echo "有效的邮箱地址"
fi
# 捕获组
version="v1.2.3"
if [[ "$version" =~ ^v([0-9]+)\.([0-9]+)\.([0-9]+)$ ]]; then
major="${BASH_REMATCH[1]}"
minor="${BASH_REMATCH[2]}"
patch="${BASH_REMATCH[3]}"
echo "版本: 主版本=$major 次版本=$minor 补丁=$patch"
fi
⚠️ 注意:在 [ ] 中使用 < > 需要转义 \< \>,或使用 -lt -gt。在 [[ ]] 中可以直接使用。
4.3 逻辑运算符
# && 逻辑与
[[ -f "/etc/hosts" ]] && echo "文件存在"
# || 逻辑或
[[ -d "/nonexistent" ]] || echo "目录不存在"
# ! 逻辑非
[[ ! -f "/nonexistent" ]] && echo "文件不存在"
# 组合条件
age=25
role="admin"
if [[ $age -ge 18 && "$role" = "admin" ]]; then
echo "成年管理员"
fi
if [[ $age -lt 18 || "$role" = "guest" ]]; then
echo "未成年或访客"
fi
# 短路求值
# &&:第一个为真才执行第二个
# ||:第一个为假才执行第二个
# 实用模式:默认值
name="${1:-default_name}" # 参数展开(推荐)
# 条件执行链
cd /tmp && mkdir -p test && touch test/file.txt && echo "创建成功"
4.4 文件测试运算符
| 运算符 | 含义 | 示例 |
|---|
-e | 文件存在 | [[ -e "$file" ]] |
-f | 是普通文件 | [[ -f "$file" ]] |
-d | 是目录 | [[ -d "$dir" ]] |
-L / -h | 是符号链接 | [[ -L "$link" ]] |
-r | 可读 | [[ -r "$file" ]] |
-w | 可写 | [[ -w "$file" ]] |
-x | 可执行 | [[ -x "$file" ]] |
-s | 文件非空 | [[ -s "$file" ]] |
-b | 是块设备 | [[ -b "/dev/sda" ]] |
-c | 是字符设备 | [[ -c "/dev/null" ]] |
-S | 是 Socket | [[ -S "$sock" ]] |
-p | 是管道 | [[ -p "$pipe" ]] |
-nt | 更新于 (newer than) | [[ "$a" -nt "$b" ]] |
-ot | 旧于 (older than) | [[ "$a" -ot "$b" ]] |
-ef | 同一文件 (same file) | [[ "$a" -ef "$b" ]] |
file="/etc/hosts"
dir="/tmp"
# 基础文件测试
if [[ -e "$file" ]]; then
echo "$file 存在"
fi
if [[ -f "$file" ]]; then
echo "$file 是普通文件"
fi
if [[ -d "$dir" ]]; then
echo "$dir 是目录"
fi
# 复合检查
config_file="/etc/myapp/config.toml"
if [[ ! -f "$config_file" ]]; then
echo "配置文件不存在,创建默认配置..."
mkdir -p "$(dirname "$config_file")"
cat > "$config_file" << 'EOF'
[server]
host = "0.0.0.0"
port = 8080
EOF
fi
# 权限检查
if [[ -r "$config_file" && -w "$config_file" ]]; then
echo "配置文件可读写"
elif [[ -r "$config_file" ]]; then
echo "配置文件只读"
else
echo "无法访问配置文件"
exit 1
fi
# 文件新旧比较
if [[ "/tmp/new.txt" -nt "/tmp/old.txt" ]]; then
echo "new.txt 比 old.txt 新"
fi
4.5 位运算符
a=12 # 二进制: 1100
b=10 # 二进制: 1010
echo "$((a & b))" # 按位与: 8 (1000)
echo "$((a | b))" # 按位或: 14 (1110)
echo "$((a ^ b))" # 按位异或: 6 (0110)
echo "$((~a))" # 按位取反: -13
echo "$((a << 2))" # 左移: 48
echo "$((a >> 2))" # 右移: 3
# 权限位操作(实际应用)
# 设置文件权限位
EXECUTE=1 # 001
WRITE=2 # 010
READ=4 # 100
# 组合权限
perms=$((READ | WRITE)) # rw-: 6
echo "权限值: $perms"
# 检查权限位
if ((perms & READ)); then
echo "有读权限"
fi
if ((perms & EXECUTE)); then
echo "有执行权限"
else
echo "无执行权限"
fi
4.6 赋值运算符
# 简单赋值
x=10
# 复合赋值
((x += 5)) # x = x + 5 = 15
((x -= 3)) # x = x - 3 = 12
((x *= 2)) # x = x * 2 = 24
((x /= 4)) # x = x / 4 = 6
((x %= 5)) # x = x % 5 = 1
# 位赋值
y=12
((y &= 10)) # y = y & 10
((y |= 5)) # y = y | 5
((y ^= 3)) # y = y ^ 3
((y <<= 2)) # y = y << 2
((y >>= 1)) # y = y >> 1
4.7 运算符优先级
| 优先级 | 运算符 | 说明 |
|---|
| 1 | () | 括号 |
| 2 | ++ -- | 自增自减 |
| 3 | ** | 幂 |
| 4 | ~ ! - | 取反、逻辑非、负号 |
| 5 | * / % | 乘、除、取余 |
| 6 | + - | 加、减 |
| 7 | << >> | 位移 |
| 8 | < <= > >= | 比较 |
| 9 | == != | 相等比较 |
| 10 | & | 按位与 |
| 11 | ^ | 按位异或 |
| 12 | | | 按位或 |
| 13 | && | 逻辑与 |
| 14 | || | 逻辑或 |
| 15 | ?: | 三元条件 |
| 16 | = += -= 等 | 赋值 |
| 17 | , | 逗号 |
4.8 业务场景:服务器健康检查
#!/bin/bash
# health_check.sh —— 服务器健康检查脚本
set -euo pipefail
readonly WARN_DISK_PERCENT=80
readonly CRIT_DISK_PERCENT=90
readonly WARN_MEM_PERCENT=80
readonly CRIT_MEM_PERCENT=95
readonly WARN_LOAD_RATIO=2.0
# 读取 CPU 核心数
readonly CPU_COUNT=$(nproc 2>/dev/null || echo 1)
check_disk() {
local mount_point="$1"
local usage
usage=$(df "$mount_point" | awk 'NR==2 {gsub(/%/,""); print $5}')
if ((usage >= CRIT_DISK_PERCENT)); then
echo " 🔴 磁盘 $mount_point: ${usage}% (严重!)"
return 2
elif ((usage >= WARN_DISK_PERCENT)); then
echo " 🟡 磁盘 $mount_point: ${usage}% (警告)"
return 1
else
echo " 🟢 磁盘 $mount_point: ${usage}% (正常)"
return 0
fi
}
check_memory() {
local total used percent
read -r total used _ <<< "$(free | awk '/^Mem:/ {print $2, $3}')"
percent=$((used * 100 / total))
if ((percent >= CRIT_MEM_PERCENT)); then
echo " 🔴 内存: ${percent}% (严重!)"
return 2
elif ((percent >= WARN_MEM_PERCENT)); then
echo " 🟡 内存: ${percent}% (警告)"
return 1
else
echo " 🟢 内存: ${percent}% (正常)"
return 0
fi
}
check_load() {
local load_1m
load_1m=$(awk '{print $1}' /proc/loadavg)
local threshold
threshold=$(awk "BEGIN {printf \"%.1f\", $CPU_COUNT * $WARN_LOAD_RATIO}")
if awk "BEGIN {exit !($load_1m > $threshold)}"; then
echo " 🔴 负载: $load_1m (阈值: $threshold,严重!)"
return 2
elif awk "BEGIN {exit !($load_1m > $CPU_COUNT)}"; then
echo " 🟡 负载: $load_1m (CPU核心数: $CPU_COUNT,警告)"
return 1
else
echo " 🟢 负载: $load_1m (正常)"
return 0
fi
}
# 主检查
echo "================================"
echo " 服务器健康检查 — $(date '+%Y-%m-%d %H:%M:%S')"
echo "================================"
exit_code=0
echo ""
echo "[磁盘使用]"
check_disk "/" || exit_code=$?
echo ""
echo "[内存使用]"
check_memory || exit_code=$?
echo ""
echo "[系统负载]"
check_load || exit_code=$?
echo ""
case $exit_code in
0) echo "✅ 所有检查通过" ;;
1) echo "⚠️ 存在警告,请关注" ;;
2) echo "🔴 存在严重问题,请立即处理!" ;;
esac
exit "$exit_code"
4.9 注意事项
| 陷阱 | 说明 | 解决方案 |
|---|
| 字符串与整数混用 | [[ "abc" -gt 5 ]] 报错 | 先验证是否为数字 |
[ ] 中未加引号 | [ $var = "x" ] 当 $var 为空时报错 | [[ "$var" == "x" ]] |
| 浮点比较 | -gt 不支持浮点数 | 使用 bc 或 awk |
< > 在 [ ] 中 | 被解释为重定向 | 使用 [[ ]] 或 -lt -gt |
== 在 [ ] 中 | 部分系统不支持 | 使用 = 或 [[ ]] |
4.10 扩展阅读