在 Shell 脚本开发中,表达式是实现逻辑判断、数据计算、文件操作的核心工具。
无论是简单的数值比较,还是复杂的文件状态校验,都依赖于 Shell 表达式的灵活运用。
本文将系统梳理 Shell 四大类核心表达式(算术、字符串、布尔、文件测试),结合语法规则与实战示例,协助开发者掌握其应用技巧。
01
Shell 表达式概述
在 Shell 脚本编程中,表达式是构建逻辑和处理数据的核心。
无论是进行数学计算、操作文本、执行条件判断,还是检查文件状态,都离不开各式各样的表达式。
掌握这些表达式的用法,是提升 Shell 脚本编写能力的关键。
Shell 表达式并非单一语法,而是根据处理对象(数值、字符串、文件)衍生的多类操作语法集合,核心作用包括:
1)实现数值计算与比较(如统计脚本执行次数、判断内存使用率);
2)处理字符串操作(如校验输入格式、截取日志关键字);
3)构建逻辑判断(如 “满足条件 A 且不满足条件 B 时执行命令”);
4)校验文件状态(如判断配置文件是否存在、是否可读写)。
本文将全面解析 Shell 中最常用的四种表达式:算术表达式、字符串表达式、布尔表达式和文件测试表达式,并通过丰富的实战代码示例,助你轻松驾驭 Shell 编程。
02
算术表达式:数值计算与比较
在 Bash Shell 中,默认情况下所有变量都以字符串形式存储,直接进行数学运算一般不会得到预期的结果。
因此,需要借助特定的命令和语法来进行算术运算。
1
常用算术运算符

2
三种实现方式对比
Shell 提供了多种整数运算的方法,主要包括 expr、let、(( )) 和 $[]。
expr 命令
作为一个外部程序,expr 是一个功能强劲的表达式计算工具,可以处理整数计算和字符串操作。
它的优点是跨平台兼容性好。
使用时需要注意,运算符和操作数之间必须有空格,并且像乘法 * 这类特殊字符需要转义。
# 加法
result=$(expr 10 + 5)
echo "10 + 5 = $result"
# 乘法(注意需要转义 *)
result=$(expr 10 * 5)
echo "10 * 5 = $result"
let 命令
let 是一个内置命令,专门用于执行整数运算。
与 expr 不同,let 命令中的变量名可以直接使用,无需加 $ 符号。
x=10
y=5
let "result = x + y"
echo "10 + 5 = $result"
let x+=1 # 自增
echo "x is now $x"
(( )) 结构
这是 Shell 中专门用于整数运算的推荐方式,效率高且语法灵活。
它支持 C 语言风格的运算符,并且在括号内的变量可以省略 $ 符号。
x=10
y=5
result=$((x + y * 2)) # 支持运算符优先级
echo "10 + 5 * 2 = $result"
# 可以在 if 语句中直接使用
if (( x > y )); then
echo "x is greater than y"
fi
$[] 结构
这种方式与 let 命令和 (( )) 类似,也用于整数运算,但在灵活性上稍逊于 (( ))。
x=10
y=5
result=$[x + y]
echo "10 + 5 = $result"
注意
以上方法主要用于整数运算。
若要进行浮点数运算,需要借助外部计算器工具 bc。
03
字符串表达式:处理文本与输入校验
字符串处理是 Shell 脚本中最常见的任务之一。
Shell 提供了丰富的内置功能来操作字符串,无需调用 awk 或 sed 等外部工具。
1
常用操作
获取字符串长度
使用 ${#string} 可以获取字符串的字符数。
my_str="Hello, Shell!"
length=${#my_str}
echo "The length of the string is: $length" # 输出 13
字符串截取
可以使用 ${string:position:length} 的形式从指定位置开始截取特定长度的子串。
my_str="Learn Shell from scratch"
sub_str1=${my_str:6:5} # 从第6个字符开始,截取5个字符
echo $sub_str1 # 输出 "Shell"
sub_str2=${my_str:12} # 从第12个字符开始,截取到末尾
echo $sub_str2 # 输出 "from scratch"
字符串替换
可以替换字符串中首次出现或所有出现的模式。
- ${string/pattern/replacement}
替换第一个匹配的 pattern
- ${string//pattern/replacement}
替换所有匹配的 pattern
my_str="apple banana apple orange"
new_str1=${my_str/apple/grape}
echo $new_str1 # 输出 "grape banana apple orange"
new_str2=${my_str//apple/grape}
echo $new_str2 # 输出 "grape banana grape orange"
字符串删除(模式匹配)
通过模式匹配删除字符串的头部或尾部内容。
- # 和 ##
从左向右匹配,删除最短或最长的匹配部分。
- % 和 %%
从右向左匹配,删除最短或最长的匹配部分。
file_path="/home/user/document/report.pdf"
# 从左边删除最短匹配 */ 的部分
echo ${file_path#*/} # 输出 "home/user/document/report.pdf"
# 从左边删除最长匹配 */ 的部分
echo ${file_path##*/} # 输出 "report.pdf" (获取文件名)
# 从右边删除最短匹配 .* 的部分
echo ${file_path%.*} # 输出 "/home/user/document/report"
# 从右边删除最长匹配 .* 的部分
echo ${file_path%%.*} # 输出 "/home/user/document/report"
04
布尔表达式:构建逻辑判断
布尔表达式用于条件判断,其结果为真或假。虽然 Bash 本身没有严格的布尔类型,但它通过整数(0 代表真,非 0 代表假)或字符串(”true” / “false”)来模拟。布尔和逻辑运算一般在 [] 和 [[]] 结构中使用。
1
运算符
Shell 中的逻辑判断分为两种:test 或 [ ] 使用的布尔运算符,以及 [[ ]] 或 (( )) 使用的逻辑运算符。
布尔运算符(用于 [ ])
- ! expression
逻辑非,当表达式为假时,结果为真。
- expr1 -a expr2
逻辑与,当两个表达式都为真时,结果为真。
- expr1 -o expr2
逻辑或,当任意一个表达式为真时,结果为真。
逻辑运算符(用于 [[ ]] 或(( )))

2
字符串比较

**1)**字符串比较操作符
这些操作符专门用于判断字符串之间的关系或状态。
- string1 = string2 (或 ==)
含义:判断两个字符串是否完全相等。
说明:在 [ ] 结构中,一般使用单个 =,这是 POSIX 标准,兼容性最好。在 [[ ]] 结构中,= 和 == 的作用完全一样,但 == 更接近其他编程语言的习惯,可读性更强。
实战示例:
read -p "请输入用户名: " username
# 使用 [ ] 进行判断 (推荐使用单个 =)
if [ "$username" = "root" ]; then
echo "警告:不提议以 root 用户操作!"
fi
# 使用 [[ ]] 进行判断 (== 更常见)
if [[ "$username" == "admin" ]]; then
echo "欢迎管理员!"
fi
重大
在进行字符串比较时,强烈提议将变量用双引号 ” 包裹起来,例如 “$username”。
这可以防止当变量为空或包含空格时,Shell 解释器发生语法错误。
- string1 != string2
含义:判断两个字符串是否不相等。
说明:此操作符在 [ ] 和 [[ ]] 中通用。
实战示例:
DB_STATUS="running"
if [[ "$DB_STATUS" != "shutdown" ]]; then
echo "数据库正在运行中..."
fi
- -n string
含义:判断字符串的长度是否非零 (Not-Zero)。如果字符串不为空,则表达式为真。
实战示例:检查一个变量是否已经被赋值。
API_KEY="" # 假设 API_KEY 可能未被设置
# ... 经过一些操作后 ...
API_KEY="xyz-12345"
if [ -n "$API_KEY" ]; then
echo "API 密钥已设置,可以继续执行。"
else
echo "错误:API 密钥为空,请先配置!"
exit 1
fi
- -z string
含义:判断字符串的长度是否为零 (Zero)。如果字符串为空,则表达式为真。
说明:这与 -n 的作用完全相反,超级适合用于验证用户输入或变量是否为空。
实战示例:要求用户必须输入内容。
read -p "请输入您的邮箱地址: " email
if [ -z "$email" ]; then
echo "错误:邮箱地址不能为空!"
else
echo "您输入的邮箱是: $email"
fi
2
字典序比较操作符 (> 和 <)
和 < 在 Shell 中有双重含义,这取决于它们所在的上下文。
- 在 [[ ]] 结构中:用于字符串的字典序比较
含义:按照当前系统区域设置(locale)的字典顺序比较字符串。
例如,”b” 在字典序上大于 “a”。
说明:在 [[]] 中,可以直接使用 > 和 <。
# 字符串字典序比较
if [[ "banana" > "apple" ]]; then
echo "在字典序中, 'banana' 排在 'apple' 之后。"
fi
if [[ "cat" < "dog" ]]; then
echo "在字典序中, 'cat' 排在 'dog' 之前。"
fi
- 在 [ ] 结构中:需要转义
说明:在 [] 结构中,> 和 < 默认被解释为重定向符号。
如果要进行字符串的字典序比较,必须使用反斜杠 进行转义,写成 > 和 <。
if [ "banana" > "apple" ]; then
echo "在 [ ] 结构中,'banana' > 'apple' 为真。"
fi
注意
由于这种用法容易混淆且易错,强烈推荐使用 [[ ]] 来进行字符串的字典序比较。
2
数值比较操作符 (> 和 <)
当 > 和 < 出目前 (( )) 算术表达式中时,它们执行的是我们最熟悉的数值大小比较。
- 在 (( )) 结构中:用于数值比较
- 含义:比较两个整数的大小。
- 说明:这是进行数值比较最直观、最推荐的方式,语法也更接近 C 语言等主流编程语言。
score=95
if (( score > 90 )); then
echo "优秀!"
fi
stock=45
if (( stock < 50 )); then
echo "库存不足,请及时补货!"
fi
3
数值比较
在 Shell 的 [ ] 和 [[ ]] 环境中,进行数值比较是一个有特定规则的任务。
如前文所述,像 > 和 == 这样的符号有其特殊含义(文件重定向和字符串比较),直接用于数值比较会导致逻辑错误或意外行为。
为了解决这个问题,Shell 提供了一套专门的、基于文本的操作符来安全、明确地执行整数比较。
- -eq:等于 (equal)
- -ne:不等于 (not equal)
- -gt:大于 (greater than)
- -ge:大于或等于 (greater or equal)
- -lt:小于 (less than)
- -le:小于或等于 (less or equal)
(一)详细解析与实战
- -eq:等于
**含义:**判断两个数值是否相等 (equal)。
**说明:**这是进行数值相等性判断的标准方法。它能正确处理像 01 和 1 这样在数值上相等但在字符串上不等的边缘情况。
**实战示例:**检查一个命令的退出码是否为 0,以判断其是否成功执行。
# 执行一个命令,并捕获其退出码
ping -c 1 "example.com" > /dev/null 2>&1
exit_code=$?
if [ "$exit_code" -eq 0 ]; then
echo "主机 'example.com' 在线。"
else
echo "无法访问主机 'example.com'。"
fi
- -gt:大于
含义:判断左边的数值是否大于 (greater than) 右边的数值。
说明:用于执行严格大于的比较。
实战示例:监控系统中的进程数量,当超过某个阈值时发出警告。
# 获取当前系统总进程数
process_count=$(ps -e | wc -l)
# 设定阈值为 500
if [ "$process_count" -gt 500 ]; then
echo "警告:系统进程数 ($process_count) 过高,已超过 500!"
fi
- -ne:不等于
**含义:**判断两个数值是否不相等 (not equal)。
**说明:**与 -eq 的逻辑完全相反。
**实战示例:**验证用户输入的端口号是否不是默认的 80 端口。
read -p "请输入要使用的端口号 [默认 80]: " port
port=${port:-80} # 如果用户未输入,则默认为 80
if [ "$port" -ne 80 ]; then
echo "您选择了一个非标准 HTTP 端口: $port"
else
echo "将使用标准 HTTP 端口 80。"
fi
- -le:小于或等于
含义:判断左边的数值是否小于或等于 (less or equal) 右边的数值。
说明:适用于检查某个值是否达到了某个下限或未超过某个上限。
实战示例:检查剩余磁盘空间百分比,如果低于或等于 10%,则触发清理脚本。
# 假设我们通过命令获取了根目录的剩余空间百分比
remaining_space=8
if [ "$remaining_space" -le 10 ]; then
echo "磁盘空间严重不足 (${remaining_space}%),正在启动清理程序..."
# ./run_cleanup_script.sh
fi
(二)再次强调:最佳实践 (( ))
虽然 -eq, -gt 等操作符功能强劲且兼容性好,但在支持 Bash、Zsh 等现代 Shell 的环境中,进行纯数值比较时,使用双括号算术表达式 (( )) 是更优的选择。
- 语法更直观
可以直接使用 ==, !=, >, <, >=, <=。
- 性能可能更优
作为 Shell 的内建算术引擎,一般比 test 命令 ([ ]) 更快。
- 不易出错
变量前的 $ 是可选的,且不会发生词法切分或文件名扩展。
对比示例:
# 传统方式
if [ "$process_count" -gt 500 ]; then ...; fi
# 现代、推荐的方式
if (( process_count > 500 )); then ...; fi
05
文件测试表达式:校验文件状态
在 Shell 脚本中,绝大多数任务都离不开与文件系统的交互。
在读取配置文件、写入日志或执行另一个脚本之前,检查文件或目录的状态(是否存在、类型、权限等)是保证脚本健壮性的基本要求。
Shell 提供了一套丰富的、以单个字母为核心的测试操作符,专门用于此目的。
这些操作符在 [ ] 和 [[ ]] 结构中均可使用。
1
常用文件测试运算符

2
测试运算符示例
存在性与类型判断
这是最基础的一类判断,用于确定一个路径指向的是什么。
- -e file
含义:判断文件或目录是否存在 (exists)。只要路径有效,无论它是文件、目录、符号链接还是设备文件,结果都为真。
说明:这是最通用的存在性检查。
实战示例:在执行操作前,检查一个关键的系统文件是否存在。
if [ -e "/etc/hosts" ]; then
echo "文件 /etc/hosts 存在。"
else
echo "警告:关键文件 /etc/hosts 不存在!"
fi
- -f file
含义:判断目标是否为一个普通文件 (regular file)。
说明:比 -e 更具体,它会排除目录、设备文件等其他类型。在需要读取或写入文件内容时,使用它比 -e 更准确。
实战示例:确认配置文件是一个文件(而不是目录)后,再进行读取。
config_file="/etc/myapp.conf"
if [ -f "$config_file" ]; then
echo "正在读取配置文件..."
# source "$config_file"
else
echo "错误:配置文件 $config_file 不是一个有效的文件。"
fi
- -d file
含义:判断目标是否为一个目录 (directory)。
说明:与 -f 对应,专门用于检查目录。
实战示例:在写入日志前,确保日志目录存在,如果不存在则创建它。这是一个超级经典的用法。
log_dir="/var/log/myapp"
if [ ! -d "$log_dir" ]; then
echo "日志目录 $log_dir 不存在,正在创建..."
mkdir -p "$log_dir"
fi
echo "日志目录已就绪。"
- -L file
含义:判断目标是否为一个符号链接 (symbolic link)。
-h 是它的同义词。
说明:用于识别链接文件本身,而不是它所指向的目标。
实战示例:检查某个命令是否是一个符号链接,以判断其真实路径。
python_path="/usr/bin/python"
if [ -L "$python_path" ]; then
real_path=$(readlink -f "$python_path")
echo "$python_path 是一个符号链接,它指向: $real_path"
else
echo "$python_path 不是一个符号链接。"
fi
文件权限判断
在进行文件操作前,检查当前用户是否拥有足够的权限至关重大。
- -r file
含义:判断文件是否可读 (readable)。
实战示例:在尝试读取文件内容前,先检查可读权限。
if [ -r "$config_file" ]; then
echo "文件可读,继续操作。"
else
echo "错误:没有权限读取文件 $config_file!"
exit 1
fi
- -w file
含义:判断文件是否可写 (writable)。
实战示例:在尝试向日志文件追加内容前,检查写入权限。
log_file="/var/log/myapp/app.log"
if [ -w "$log_file" ]; then
echo "写入日志..." >> "$log_file"
else
echo "错误:没有权限写入日志文件 $log_file!"
fi
- -x file
含义:判断文件是否可执行 (executable)。
实战示例:在调用一个脚本前,检查它是否被赋予了执行权限。
helper_script="./scripts/run_task.sh"
if [ -x "$helper_script" ]; then
"$helper_script"
else
echo "错误:脚本 $helper_script 不可执行,请先 chmod +x"
fi
3
文件大小与比较
除了类型和权限,文件的大小和修改时间也是重大的判断依据。
- -s file
含义:判断文件大小是否非零 (size is not zero),即文件是否非空。
说明:这是一种比 [ $(wc -c < “$file”) -gt 0 ] 更高效的判空方式。
实战示例:检查错误日志文件是否包含内容,如果有,则发送通知。
error_log="errors.log"
if [ -s "$error_log" ]; then
echo "错误日志不为空,正在发送警报邮件..."
# mail -s "发现错误" admin@example.com < "$error_log"
fi
- file1 -nt file2
含义:判断 file1 是否比 file2 更新 (newer than),基于文件的修改时间。
说明:在编译和构建场景中超级有用,用于判断源文件是否在目标文件生成后被修改过。
实战示例:一个简单的增量编译逻辑。
source_file="main.c"
executable="main"
if [ "$source_file" -nt "$executable" ]; then
echo "源文件已被修改,需要重新编译..."
# gcc "$source_file" -o "$executable"
else
echo "无需重新编译。"
fi
- file1 -ot file2
含义:判断 file1 是否比 file2 更旧 (older than)。与 -nt 逻辑相反。
总结与最佳实践
1)始终引用变量
在进行文件测试时,务必用双引号将包含文件路径的变量括起来(如 [ -f “$filepath” ])。
这可以防止当文件名包含空格或特殊字符时,脚本出现解析错误。
**2)**优先使用 [[ ]]:与 [ ] 相比
[[ ]] 在处理变量时能避免词法切分和路径名扩展问题,使其在处理不确定的文件名时更加安全和健壮。
3)组合测试
使用逻辑运算符 && (与) 和 || (或) 可以将多个测试条件组合起来,构建更复杂的判断逻辑。
# 检查配置文件是否存在并且可读
if [[ -f "$config_file" && -r "$config_file" ]]; then
echo "配置文件有效,正在加载..."
source "$config_file"
else
echo "错误:配置文件不存在或不可读。"
fi
06
综合实战:Shell 表达式组合应用
下面通过一个 “系统资源监控脚本”,整合四类表达式的用法:
#!/bin/bash
# 功能:监控CPU使用率、内存使用率,检查关键配置文件
set -e # 脚本出错即退出
# 1. 定义关键参数(算术表达式基础)
cpu_threshold=80 # CPU使用率阈值
mem_threshold=85 # 内存使用率阈值
conf_file="/etc/nginx/nginx.conf" # 关键配置文件
# 2. 获取CPU使用率(字符串截取+算术转换)
cpu_usage=$(top -bn1 | grep "Cpu(s)" | awk '{print $2}' | cut -d. -f1)
# 转换为整数(避免浮点数问题)
cpu_usage=$((cpu_usage))
# 3. 获取内存使用率(字符串处理+算术计算)
mem_total=$(free -m | grep "Mem" | awk '{print $2}')
mem_used=$(free -m | grep "Mem" | awk '{print $3}')
mem_usage=$((mem_used * 100 / mem_total)) # 计算百分比
# 4. 监控逻辑(布尔表达式+文件测试)
echo "=== 系统资源监控报告 ==="
echo "当前CPU使用率:${cpu_usage}%"
echo "当前内存使用率:${mem_usage}%"
# CPU超限判断
if ((cpu_usage > cpu_threshold)); then
echo "警告:CPU使用率超过${cpu_threshold}%!"
# 记录告警日志(文件测试+写权限判断)
if [[ -d /var/log/monitor/ ]] && [[ -w /var/log/monitor/ ]]; then
echo "$(date +'%Y-%m-%d %H:%M:%S') CPU告警:使用率${cpu_usage}%" >> /var/log/monitor/system_alarm.log
fi
fi
# 内存超限判断
if ((mem_usage > mem_threshold)); then
echo "警告:内存使用率超过${mem_threshold}%!"
fi
# 关键配置文件检查(文件测试表达式)
echo -e "
=== 关键文件检查 ==="
if [[ -f $conf_file ]] && [[ -r $conf_file ]]; then
echo "配置文件 $conf_file 存在且可读取"
# 截取配置文件关键参数(字符串截取)
worker_processes=$(grep "worker_processes" $conf_file | awk '{print $2}' | sed 's/;//')
echo "Nginx worker_processes 配置:$worker_processes"
else
echo "错误:配置文件 $conf_file 不存在或不可读!"
exit 1
fi
以本地环境为例,输出如下
=== 系统资源监控报告 ===
当前CPU使用率:0%
当前内存使用率:73%
=== 关键文件检查 ===
错误:配置文件 /etc/nginx/nginx.conf 不存在或不可读!
07
关键注意事项
1
语法规范
- 算术表达式中,$((…))无需空格,expr必须加空格;
- 字符串 / 文件测试需用[ ]或[[ ]]包裹,[[ ]]支持模式匹配(如[[ $str == a* ]]),兼容性优于[ ]。
2
变量引用
- 字符串比较时,变量提议加双引号(如[[ “$str” == “test” ]]),避免空变量导致语法错误;
- 算术表达式中,变量可直接引用(如((a + b))),无需$。
3
运算符优先级
- 算术运算:先乘除后加减,括号优先(如$((a + b * c)));
- 逻辑运算:! > && > ||,复杂逻辑提议加括号明确优先级。
4
兼容性
$((…))、[[ ]]为 Bash 特有语法,若脚本需兼容sh(如#!/bin/sh),需改用expr和[ ]。
写在最后
Shell 表达式是构建强劲而灵活脚本的基石。
从执行简单数学运算的算术表达式,到灵活处理文本的字符串操作,再到控制脚本流程的布尔判断和检查系统状态的文件测试,每一种都有其独特的应用场景和语法规则。
通过熟练掌握 (( ))、[[ ]] 等现代 Shell 提供的便捷语法,并结合具体场景选择合适的表达式,你将能够编写出更健壮、更高效的 Shell 脚本。
作者介绍

大家好,我是刘峰,安丫科技创始人 & 数据库技术高级讲师,专注于 PostgreSQL、国产数据库运维与迁移、数据库性能优化 等方向。
作为 PG中国分会官方授权讲师、PostgreSQL ACE 讲师认证专家,我长期活跃在一线项目实战中,拥有 10年以上大型数据库管理与优化经验,曾深度参与电信、金融、政务等多个行业的数据库性能调优与迁移项目。
欢迎关注我,一起深入探索数据库的无限可能,技术交流不设限!
觉得有收获的话,记得点赞、收藏、转发支持一下哦,别忘了关注我获取更多数据库干货~
安呀智数据坊|我们能做什么
无论你是业务系统的技术负责人,还是数据部门的第一响应人,我们都能为你提供可靠的支持:
- 数据库类型支持
Oracle / MySQL / PostgreSQL / PG / SQL Server 等主流数据库
- 核心服务内容
性能优化 / 故障处理 / 数据迁移 / 备份恢复 / 版本升级 / 补丁管理
- 系统性支持
深度巡检 / 高可用架构设计 / 应用层兼容评估 / 运维工具集成
- 专项能力补充
定制课程培训 / 甲方团队辅导 / 复杂问题协作排查 / 紧急救援支持
如果你有一张删不掉的表、一个跑不动的查询,或者一场说不清的升级风险,欢迎来找我们聊聊。


