Shell 运算符详解

Shell运算符简介

运算符是Shell脚本编程中执行各种操作的基础工具。Shell提供了丰富的运算符类型,可以用于算术计算、条件判断、字符串操作和文件测试等。

算术运算符

用于执行数学运算,如加减乘除、取模等。

关系运算符

用于比较数值或字符串的大小、相等等关系。

逻辑运算符

用于组合多个条件,实现复杂的逻辑判断。

字符串运算符

用于字符串的比较、连接、长度计算等操作。

文件测试运算符

用于检查文件的属性,如是否存在、可读、可写等。

其他运算符

包括位运算符、赋值运算符、特殊运算符等。

运算符使用注意事项
  • [ ][[ ]]中使用不同的运算符语法
  • 算术运算需要使用(( ))let命令
  • 字符串比较时要注意引号的使用
  • 文件测试运算符使用单字符选项

算术运算符

用于执行数学运算的运算符。

运算符 说明 示例 结果
+ 加法 expr 10 + 3 13
- 减法 expr 10 - 3 7
* 乘法 expr 10 \* 3 30
/ 除法 expr 10 / 3 3
% 取余 expr 10 % 3 1
** 幂运算 echo $((2 ** 3)) 8
++ 自增 ((a++)) a的值加1
-- 自减 ((a--)) a的值减1

算术运算符使用示例

arithmetic_operators.sh
#!/bin/bash

# 算术运算符示例

# 方法1: 使用 expr (注意: 运算符前后要有空格,*需要转义)
echo "=== 使用 expr ==="
a=10
b=3
echo "a + b = `expr $a + $b`"    # 13
echo "a - b = `expr $a - $b`"    # 7
echo "a * b = `expr $a \* $b`"   # 30
echo "a / b = `expr $a / $b`"    # 3
echo "a % b = `expr $a % $b`"    # 1

# 方法2: 使用 $(( ))
echo -e "\n=== 使用 \$(( )) ==="
echo "a + b = $((a + b))"        # 13
echo "a - b = $((a - b))"        # 7
echo "a * b = $((a * b))"        # 30
echo "a / b = $((a / b))"        # 3
echo "a % b = $((a % b))"        # 1
echo "a ** b = $((a ** b))"      # 1000

# 方法3: 使用 let
echo -e "\n=== 使用 let ==="
let "c = a + b"
echo "c = $c"                    # 13
let "c += 5"
echo "c += 5 → $c"               # 18
let "c++"
echo "c++ → $c"                  # 19

# 方法4: 使用 bc (浮点运算)
echo -e "\n=== 使用 bc (浮点运算) ==="
echo "scale=2; $a / $b" | bc     # 3.33
echo "scale=4; $a / $b" | bc     # 3.3333

# 复合运算
echo -e "\n=== 复合运算 ==="
x=5
y=2
((x += y))      # x = x + y
echo "x += y → $x"  # 7

((x -= y))      # x = x - y
echo "x -= y → $x"  # 5

((x *= y))      # x = x * y
echo "x *= y → $x"  # 10

((x /= y))      # x = x / y
echo "x /= y → $x"  # 5

((x %= y))      # x = x % y
echo "x %= y → $x"  # 1

# 自增自减
echo -e "\n=== 自增自减 ==="
counter=5
((counter++))
echo "counter++ → $counter"  # 6

((counter--))
echo "counter-- → $counter"  # 5

((++counter))
echo "++counter → $counter"  # 6

((--counter))
echo "--counter → $counter"  # 5
算术运算方法比较:
  • expr: 老式方法,需要转义和空格
  • $(( )): 推荐方法,简洁高效
  • let: 另一种算术运算方式
  • bc: 支持浮点运算,需要安装

关系运算符

用于比较数值或字符串的关系运算符。

数值比较运算符
运算符 说明 示例
-eq 等于 [ $a -eq $b ]
-ne 不等于 [ $a -ne $b ]
-gt 大于 [ $a -gt $b ]
-lt 小于 [ $a -lt $b ]
-ge 大于等于 [ $a -ge $b ]
-le 小于等于 [ $a -le $b ]
字符串比较运算符
运算符 说明 示例
= 等于 [ "$a" = "$b" ]
== 等于 [ "$a" == "$b" ]
!= 不等于 [ "$a" != "$b" ]
\< 小于 [[ "$a" < "$b" ]]
\> 大于 [[ "$a" > "$b" ]]
-z 长度为0 [ -z "$a" ]
-n 长度不为0 [ -n "$a" ]

关系运算符使用示例

relational_operators.sh
#!/bin/bash

# 关系运算符示例

# 数值比较
echo "=== 数值比较 ==="
a=10
b=20

if [ $a -eq $b ]; then
echo "$a -eq $b : a 等于 b"
else
echo "$a -eq $b : a 不等于 b"
fi

if [ $a -ne $b ]; then
echo "$a -ne $b : a 不等于 b"
else
echo "$a -ne $b : a 等于 b"
fi

if [ $a -gt $b ]; then
echo "$a -gt $b : a 大于 b"
else
echo "$a -gt $b : a 不大于 b"
fi

if [ $a -lt $b ]; then
echo "$a -lt $b : a 小于 b"
else
echo "$a -lt $b : a 不小于 b"
fi

if [ $a -ge $b ]; then
echo "$a -ge $b : a 大于或等于 b"
else
echo "$a -ge $b : a 小于 b"
fi

if [ $a -le $b ]; then
echo "$a -le $b : a 小于或等于 b"
else
echo "$a -le $b : a 大于 b"
fi

# 字符串比较
echo -e "\n=== 字符串比较 ==="
str1="apple"
str2="banana"
str3=""

if [ "$str1" = "$str2" ]; then
echo "$str1 = $str2 : str1 等于 str2"
else
echo "$str1 = $str2 : str1 不等于 str2"
fi

if [ "$str1" != "$str2" ]; then
echo "$str1 != $str2 : str1 不等于 str2"
else
echo "$str1 != $str2 : str1 等于 str2"
fi

if [[ "$str1" < "$str2" ]]; then
echo "$str1 < $str2 : str1 在字典序上小于 str2"
else
echo "$str1 < $str2 : str1 在字典序上不小于 str2"
fi

if [[ "$str1" > "$str2" ]]; then
echo "$str1 > $str2 : str1 在字典序上大于 str2"
else
echo "$str1 > $str2 : str1 在字典序上不大于 str2"
fi

if [ -z "$str3" ]; then
echo "str3 是空字符串"
else
echo "str3 不是空字符串"
fi

if [ -n "$str1" ]; then
echo "str1 不是空字符串"
else
echo "str1 是空字符串"
fi

# 使用双括号进行数值比较
echo -e "\n=== 使用双括号进行数值比较 ==="
if (( a == b )); then
echo "((a == b)) : a 等于 b"
else
echo "((a == b)) : a 不等于 b"
fi

if (( a < b )); then
echo "((a < b)) : a 小于 b"
else
echo "((a < b)) : a 不小于 b"
fi

if (( a > b )); then
echo "((a > b)) : a 大于 b"
else
echo "((a > b)) : a 不大于 b"
fi
比较运算符使用场景:
数值比较: 使用 -eq, -ne, -gt, -lt, -ge, -le
字符串比较: 使用 =, ==, !=, \<, \>, -z, -n
双括号数值比较: 使用 ==, !=, \<, \>, \<=, >=

逻辑运算符

用于组合多个条件的逻辑运算符。

运算符 说明 示例 说明
&& 逻辑与 [ $a -gt 0 ] && [ $b -gt 0 ] 两个条件都为真
|| 逻辑或 [ $a -gt 0 ] || [ $b -gt 0 ] 至少一个条件为真
! 逻辑非 [ ! $a -gt 0 ] 条件取反
-a 逻辑与 [ $a -gt 0 -a $b -gt 0 ] 两个条件都为真
-o 逻辑或 [ $a -gt 0 -o $b -gt 0 ] 至少一个条件为真

逻辑运算符使用示例

logical_operators.sh
#!/bin/bash

# 逻辑运算符示例

echo "=== 逻辑与 (&&) ==="
age=25
score=85

# 使用 && 连接多个命令
if [ $age -ge 18 ] && [ $score -ge 60 ]; then
echo "年龄合格且成绩及格"
else
echo "年龄或成绩不合格"
fi

# 在 [[ ]] 中使用 &&
if [[ $age -ge 18 && $score -ge 60 ]]; then
echo "年龄合格且成绩及格"
else
echo "年龄或成绩不合格"
fi

# 使用 -a (单括号内)
if [ $age -ge 18 -a $score -ge 60 ]; then
echo "年龄合格且成绩及格"
else
echo "年龄或成绩不合格"
fi

echo -e "\n=== 逻辑或 (||) ==="
file1="test.txt"
file2="backup.txt"

# 使用 || 连接多个命令
if [ -f "$file1" ] || [ -f "$file2" ]; then
echo "至少有一个文件存在"
else
echo "两个文件都不存在"
fi

# 在 [[ ]] 中使用 ||
if [[ -f "$file1" || -f "$file2" ]]; then
echo "至少有一个文件存在"
else
echo "两个文件都不存在"
fi

# 使用 -o (单括号内)
if [ -f "$file1" -o -f "$file2" ]; then
echo "至少有一个文件存在"
else
echo "两个文件都不存在"
fi

echo -e "\n=== 逻辑非 (!) ==="
file="nonexistent.txt"

# 使用 ! 取反
if [ ! -f "$file" ]; then
echo "文件不存在"
else
echo "文件存在"
fi

# 在 [[ ]] 中使用 !
if [[ ! -f "$file" ]]; then
echo "文件不存在"
else
echo "文件存在"
fi

echo -e "\n=== 复杂逻辑组合 ==="
username="admin"
password="123456"
is_admin=true

# 复杂的逻辑组合
if [[ ($username == "admin" && $password == "123456") || $is_admin == true ]]; then
echo "登录成功或用户是管理员"
else
echo "登录失败且用户不是管理员"
fi

# 使用括号分组
if [ \( "$username" = "admin" -a "$password" = "123456" \) -o "$is_admin" = true ]; then
echo "登录成功或用户是管理员"
else
echo "登录失败且用户不是管理员"
fi

echo -e "\n=== 短路求值 ==="
# && 短路求值:第一个命令成功才执行第二个
[ -f "$file1" ] && echo "文件存在,执行操作"

# || 短路求值:第一个命令失败才执行第二个
[ -f "$file1" ] || echo "文件不存在,执行备用操作"

# 组合使用
[ -f "$file1" ] && echo "文件存在" || echo "文件不存在"
逻辑运算符最佳实践:
  • 推荐使用 &&|| 而不是 -a-o
  • [[ ]] 中使用逻辑运算符更安全
  • 使用括号明确优先级顺序
  • 利用短路求值简化代码

字符串运算符

用于字符串操作和处理的运算符。

运算符 说明 示例 结果
${#str} 字符串长度 ${#name} 字符串长度
${str:pos} 子字符串提取 ${str:2} 从位置2开始
${str:pos:len} 子字符串提取 ${str:2:3} 从位置2开始取3个字符
${str/sub/repl} 替换第一个匹配 ${str/old/new} 替换第一个old为new
${str//sub/repl} 替换所有匹配 ${str//old/new} 替换所有old为new
${str/#sub/repl} 替换开头匹配 ${str/#old/new} 如果开头是old则替换
${str/%sub/repl} 替换结尾匹配 ${str/%old/new} 如果结尾是old则替换
${str#pattern} 删除前缀匹配 ${str#prefix} 删除最短匹配前缀
${str##pattern} 删除前缀匹配 ${str##prefix} 删除最长匹配前缀
${str%pattern} 删除后缀匹配 ${str%suffix} 删除最短匹配后缀
${str%%pattern} 删除后缀匹配 ${str%%suffix} 删除最长匹配后缀

字符串运算符使用示例

string_operators.sh
#!/bin/bash

# 字符串运算符示例

echo "=== 字符串长度 ==="
str="Hello, World!"
echo "字符串: $str"
echo "长度: ${#str}"  # 13

echo -e "\n=== 子字符串提取 ==="
echo "从位置7开始: ${str:7}"        # World!
echo "从位置0开始取5个字符: ${str:0:5}"  # Hello
echo "从位置7开始取5个字符: ${str:7:5}"  # World

echo -e "\n=== 字符串替换 ==="
text="apple orange apple banana"
echo "原始文本: $text"
echo "替换第一个apple: ${text/apple/fruit}"     # fruit orange apple banana
echo "替换所有apple: ${text//apple/fruit}"      # fruit orange fruit banana
echo "替换开头的apple: ${text/#apple/fruit}"    # fruit orange apple banana
echo "替换结尾的banana: ${text/%banana/fruit}"  # apple orange apple fruit

echo -e "\n=== 删除匹配模式 ==="
filename="backup.tar.gz"
echo "文件名: $filename"
echo "删除 .tar.gz: ${filename%.tar.gz}"        # backup
echo "删除 backup.: ${filename#backup.}"        # tar.gz

path="/home/user/documents/file.txt"
echo -e "\n文件路径: $path"
echo "删除最短前缀匹配: ${path#/*/}"           # user/documents/file.txt
echo "删除最长前缀匹配: ${path##/*/}"          # file.txt
echo "删除最短后缀匹配: ${path%.*}"            # /home/user/documents/file
echo "删除最长后缀匹配: ${path%%.*}"           # /home/user/documents/file

echo -e "\n=== 大小写转换 ==="
mixed="Hello World"
echo "原始: $mixed"
echo "大写: ${mixed^^}"              # HELLO WORLD
echo "小写: ${mixed,,}"              # hello world
echo "首字母大写: ${mixed^}"          # Hello World

echo -e "\n=== 默认值处理 ==="
unset undefined_var
echo "变量值: ${undefined_var:-默认值}"        # 默认值
echo "变量值: ${undefined_var:=默认值}"        # 默认值 (并赋值)
echo "设置后的值: $undefined_var"              # 默认值

defined_var="有值"
echo "有值的变量: ${defined_var:-默认值}"      # 有值

echo -e "\n=== 检查变量设置 ==="
unset check_var
echo "变量是否设置: ${check_var+是}"          # 空 (未设置)
echo "变量是否设置: ${defined_var+是}"        # 是 (已设置)

echo -e "\n=== 字符串分割 ==="
csv="apple,orange,banana,grape"
IFS=',' read -ra fruits <<< "$csv"
echo "CSV字符串: $csv"
echo "分割后的数组:"
for fruit in "${fruits[@]}"; do
echo "  - $fruit"
done

echo -e "\n=== 字符串连接 ==="
part1="Hello"
part2="World"
combined="$part1, $part2!"
echo "连接结果: $combined"  # Hello, World!

# 使用 += 连接字符串
greeting="Hello"
greeting+=", World!"
echo "使用+=连接: $greeting"  # Hello, World!

文件测试运算符

用于测试文件属性和类型的运算符。

运算符 说明 示例
-e 文件/目录是否存在 [ -e file ]
-f 是否是普通文件 [ -f file ]
-d 是否是目录 [ -d dir ]
-r 文件是否可读 [ -r file ]
-w 文件是否可写 [ -w file ]
-x 文件是否可执行 [ -x file ]
-s 文件大小是否大于0 [ -s file ]
-L 是否是符号链接 [ -L file ]
-O 文件是否属于当前用户 [ -O file ]
-G 文件是否属于当前用户组 [ -G file ]
-nt 文件1是否比文件2新 [ file1 -nt file2 ]
-ot 文件1是否比文件2旧 [ file1 -ot file2 ]
-ef 文件1和文件2是否是同一文件 [ file1 -ef file2 ]

文件测试运算符使用示例

file_operators.sh
#!/bin/bash

# 文件测试运算符示例

echo "=== 基本文件测试 ==="
file="test.txt"
dir="/tmp"

# 创建测试文件
echo "Hello World" > "$file"

if [ -e "$file" ]; then
echo "$file 存在"
else
echo "$file 不存在"
fi

if [ -f "$file" ]; then
echo "$file 是普通文件"
else
echo "$file 不是普通文件"
fi

if [ -d "$dir" ]; then
echo "$dir 是目录"
else
echo "$dir 不是目录"
fi

echo -e "\n=== 权限测试 ==="
if [ -r "$file" ]; then
echo "$file 可读"
else
echo "$file 不可读"
fi

if [ -w "$file" ]; then
echo "$file 可写"
else
echo "$file 不可写"
fi

if [ -x "$file" ]; then
echo "$file 可执行"
else
echo "$file 不可执行"
fi

echo -e "\n=== 文件属性测试 ==="
if [ -s "$file" ]; then
echo "$file 不为空"
else
echo "$file 为空"
fi

# 创建符号链接测试
ln -sf "$file" "link_to_test.txt"

if [ -L "link_to_test.txt" ]; then
echo "link_to_test.txt 是符号链接"
else
echo "link_to_test.txt 不是符号链接"
fi

if [ -O "$file" ]; then
echo "$file 属于当前用户"
else
echo "$file 不属于当前用户"
fi

if [ -G "$file" ]; then
echo "$file 属于当前用户组"
else
echo "$file 不属于当前用户组"
fi

echo -e "\n=== 文件比较 ==="
file1="file1.txt"
file2="file2.txt"

# 创建测试文件
echo "content1" > "$file1"
sleep 1  # 确保时间戳不同
echo "content2" > "$file2"

if [ "$file1" -nt "$file2" ]; then
echo "$file1 比 $file2 新"
else
echo "$file1 不比 $file2 新"
fi

if [ "$file1" -ot "$file2" ]; then
echo "$file1 比 $file2 旧"
else
echo "$file1 不比 $file2 旧"
fi

# 创建硬链接
ln "$file1" "hard_link.txt"

if [ "$file1" -ef "hard_link.txt" ]; then
echo "$file1 和 hard_link.txt 是同一文件"
else
echo "$file1 和 hard_link.txt 不是同一文件"
fi

echo -e "\n=== 综合文件检查函数 ==="
check_file() {
local file="$1"

echo "检查文件: $file"

if [ ! -e "$file" ]; then
    echo "  ❌ 文件不存在"
    return 1
fi

[ -f "$file" ] && echo "  ✅ 是普通文件"
[ -d "$file" ] && echo "  ✅ 是目录"
[ -r "$file" ] && echo "  ✅ 可读"
[ -w "$file" ] && echo "  ✅ 可写"
[ -x "$file" ] && echo "  ✅ 可执行"
[ -s "$file" ] && echo "  ✅ 不为空"
[ -L "$file" ] && echo "  ✅ 是符号链接"
[ -O "$file" ] && echo "  ✅ 属于当前用户"

return 0
}

# 测试文件检查函数
check_file "$file"
check_file "$dir"

echo -e "\n=== 批量文件检查 ==="
files=("$file" "$file1" "$file2" "nonexistent.txt")

for f in "${files[@]}"; do
if [ -e "$f" ]; then
    echo "✅ $f 存在"
    if [ -s "$f" ]; then
        size=$(wc -c < "$f")
        echo "   大小: $size 字节"
    fi
else
    echo "❌ $f 不存在"
fi
done

# 清理测试文件
rm -f "$file" "$file1" "$file2" "link_to_test.txt" "hard_link.txt"

综合示例

通过实际脚本示例展示Shell运算符的综合应用。

计算器脚本

使用各种运算符实现一个简单的计算器。

calculator.sh
#!/bin/bash

# 简单计算器脚本

# 颜色定义
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color

# 显示菜单
show_menu() {
echo -e "${BLUE}=== 简单计算器 ===${NC}"
echo "1. 加法"
echo "2. 减法"
echo "3. 乘法"
echo "4. 除法"
echo "5. 取余"
echo "6. 幂运算"
echo "7. 比较两个数"
echo "8. 退出"
}

# 获取数字输入
get_number() {
local prompt="$1"
local number

while true; do
    read -p "$prompt" number
    if [[ "$number" =~ ^-?[0-9]+\.?[0-9]*$ ]]; then
        echo "$number"
        break
    else
        echo -e "${RED}错误: 请输入有效的数字${NC}"
    fi
done
}

# 加法
add() {
local num1=$(get_number "请输入第一个数: ")
local num2=$(get_number "请输入第二个数: ")
local result=$(echo "$num1 + $num2" | bc)
echo -e "${GREEN}结果: $num1 + $num2 = $result${NC}"
}

# 减法
subtract() {
local num1=$(get_number "请输入第一个数: ")
local num2=$(get_number "请输入第二个数: ")
local result=$(echo "$num1 - $num2" | bc)
echo -e "${GREEN}结果: $num1 - $num2 = $result${NC}"
}

# 乘法
multiply() {
local num1=$(get_number "请输入第一个数: ")
local num2=$(get_number "请输入第二个数: ")
local result=$(echo "$num1 * $num2" | bc)
echo -e "${GREEN}结果: $num1 * $num2 = $result${NC}"
}

# 除法
divide() {
local num1=$(get_number "请输入第一个数: ")
local num2=$(get_number "请输入第二个数: ")

if (( $(echo "$num2 == 0" | bc) )); then
    echo -e "${RED}错误: 除数不能为0${NC}"
    return
fi

local result=$(echo "scale=4; $num1 / $num2" | bc)
echo -e "${GREEN}结果: $num1 / $num2 = $result${NC}"
}

# 取余
modulus() {
local num1=$(get_number "请输入第一个数(整数): ")
local num2=$(get_number "请输入第二个数(整数): ")

if (( $(echo "$num2 == 0" | bc) )); then
    echo -e "${RED}错误: 除数不能为0${NC}"
    return
fi

local result=$((num1 % num2))
echo -e "${GREEN}结果: $num1 % $num2 = $result${NC}"
}

# 幂运算
power() {
local num1=$(get_number "请输入底数: ")
local num2=$(get_number "请输入指数: ")
local result=$(echo "$num1 ^ $num2" | bc)
echo -e "${GREEN}结果: $num1 ^ $num2 = $result${NC}"
}

# 比较两个数
compare() {
local num1=$(get_number "请输入第一个数: ")
local num2=$(get_number "请输入第二个数: ")

echo -e "${BLUE}比较结果:${NC}"

if (( $(echo "$num1 == $num2" | bc) )); then
    echo "  $num1 = $num2"
elif (( $(echo "$num1 > $num2" | bc) )); then
    echo "  $num1 > $num2"
else
    echo "  $num1 < $num2"
fi

# 使用字符串比较显示差异
local diff=$(echo "$num1 - $num2" | bc)
if (( $(echo "$diff > 0" | bc) )); then
    echo -e "  相差: +$diff"
else
    echo -e "  相差: $diff"
fi
}

# 主循环
main() {
while true; do
    show_menu
    read -p "请选择操作 [1-8]: " choice

    case $choice in
        1) add ;;
        2) subtract ;;
        3) multiply ;;
        4) divide ;;
        5) modulus ;;
        6) power ;;
        7) compare ;;
        8)
            echo -e "${GREEN}感谢使用计算器,再见!${NC}"
            break
            ;;
        *)
            echo -e "${RED}无效选择,请重新输入${NC}"
            ;;
    esac

    echo
    read -p "按回车键继续..."
    echo
done
}

# 脚本入口
echo -e "${GREEN}欢迎使用Shell计算器${NC}"
main
使用示例:
./calculator.sh
然后按照菜单提示选择操作。

运算符使用最佳实践

推荐做法
  • 使用 [[ ]] 而不是 [ ] 进行条件测试
  • 对字符串变量使用引号
  • 使用 $(( )) 进行算术运算
  • 检查文件存在性后再进行操作
  • 使用括号明确运算优先级
  • 为复杂的条件添加注释
避免的做法
  • 不要忘记字符串比较中的引号
  • 避免在 [ ] 中使用 <>
  • 不要混用数值和字符串比较运算符
  • 避免使用过时的 expr 命令
  • 不要假设文件一定存在
  • 避免过度复杂的条件表达式
调试技巧

使用 set -x 开启调试模式,可以查看运算符的执行过程。使用 echo 输出中间结果进行调试。