Shell 脚本编程是通过**编写一系列 Shell 命令的集合(文本文件),实现自动化任务的技术。**它广泛用于系统管理、批量处理、自动化部署等场景。以下是 Shell 脚本编程的核心知识点和入门指南:
一、基础概念
- 什么是 Shell 脚本 :由 Shell 命令组成的文本文件,可直接被 Shell 解释器(如
bash、sh)执行,无需编译。 - 常用 Shell :
bash(最流行,Linux 默认)、sh(基础版)、zsh(增强版)等,本文以bash为例。
二、脚本基本结构与运行
1. 脚本格式
脚本第一行必须指定解释器("shebang"),后续为命令序列:
#!/bin/bash # 指定用bash解释器执行
# 这是注释(#开头的行)
echo "Hello, Shell Script!" # 输出内容
2. 运行脚本的步骤
-
创建脚本文件 :用
vim、nano等编辑器创建,例如test.sh。 -
赋予执行权限 :
chmod +x test.sh # 允许脚本执行 -
执行脚本 :
./test.sh # 当前目录执行(需权限) # 或直接指定解释器:bash test.sh(无需执行权限)
三、核心语法
1. 变量
变量用于存储数据,分为系统变量 (预定义)和自定义变量。
-
系统变量 :如
$HOME(用户主目录)、$PATH(命令搜索路径)、$USER(当前用户)等。示例:echo "当前用户:$USER" -
自定义变量:
- 赋值:
变量名=值(等号前后无空格 ),例如name="Alice"。 - 使用:
$变量名或${变量名}(推荐,避免歧义),例如echo "Name: $name"。 - 只读变量:
readonly age=18(赋值后不可修改)。 - 删除变量:
unset name(只读变量不可删除)。
- 赋值:
-
特殊变量(处理脚本参数):
$0:脚本文件名(如test.sh)。$1~$9:第 1 到第 9 个参数(超过 9 个用${10})。$#:参数总个数。$*/$@:所有参数($*将参数视为整体,$@视为独立个体)。$?:上一条命令的退出状态(0 表示成功,非 0 表示失败)。
示例(
param.sh):#!/bin/bash echo "脚本名:$0" echo "参数1:$1" echo "参数总数:$#" echo "所有参数:$@"运行:
./param.sh a b c,输出:脚本名:./param.sh 参数1:a 参数总数:3 所有参数:a b c
2. 输入输出
-
输出 :
echo命令,支持转义字符(需加-e)。echo "Hello" # 输出Hello(默认换行) echo -n "Hello" # 不换行输出 echo -e "Line1\nLine2" # 换行输出两行 -
输入 :
read命令读取用户输入。#!/bin/bash read -p "请输入姓名:" name # -p显示提示信息 echo "你好,$name!"运行后输入姓名,会输出对应问候。
3.逻辑与和逻辑或
在 Shell 脚本中,逻辑与(AND)和 逻辑或(OR)用于组合多个条件或命令,实现更复杂的逻辑判断。它们在条件判断 和命令执行流程中有着不同的语法和特性,以下是详细说明:
一、逻辑与(AND)的用法
逻辑与表示 "多个条件同时成立" 或 "前一个命令成功后执行下一个命令"。
1. 在test/[ ]中:用-a
在test命令或[ ]结构中,逻辑与用-a表示,格式为:
test 条件1 -a 条件2
[ 条件1 -a 条件2 ]
示例:判断文件存在且可写
if [ -e "file.txt" -a -w "file.txt" ]; then
echo "文件存在且可写"
fi
2. 在[[ ]]中:用&&
[[ ]]是 Bash 的增强语法,逻辑与用&&表示,更直观:
[[ 条件1 && 条件2 ]]
示例:判断字符串非空且数值大于 10
str="hello"
num=20
if [[ -n "$str" && $num -gt 10 ]]; then
echo "条件成立"
fi
3. 在命令执行中:用&&
多个命令用&&连接时,** 前一个命令成功(退出状态 0)** 才会执行后一个命令(短路逻辑)。示例:创建目录并进入(确保 mkdir 成功后再 cd)
mkdir "new_dir" && cd "new_dir"
二、逻辑或(OR)的用法
逻辑或表示 "多个条件至少一个成立" 或 "前一个命令失败后执行下一个命令"。
1. 在test/[ ]中:用-o
在test或[ ]中,逻辑或用-o表示:
test 条件1 -o 条件2
[ 条件1 -o 条件2 ]
示例:判断文件是 txt 或 sh 格式
if [ "$file" = "*.txt" -o "$file" = "*.sh" ]; then
echo "是文本或脚本文件"
fi
2. 在[[ ]]中:用||
[[ ]]中逻辑或用||表示:
[[ 条件1 || 条件2 ]]
示例:判断数值小于 0 或大于 100
num=150
if [[ $num -lt 0 || $num -gt 100 ]]; then
echo "数值超出范围"
fi
3. 在命令执行中:用||
多个命令用||连接时,** 前一个命令失败(退出状态非 0)** 才会执行后一个命令(短路逻辑)。示例:如果文件不存在则创建
ls "file.txt" || touch "file.txt"
三、短路逻辑的特性
- 逻辑与(
&&):若第一个条件 / 命令失败,直接跳过后续判断 / 命令("短路")。 - 逻辑或(
||):若第一个条件 / 命令成功,直接跳过后续判断 / 命令("短路")。
示例:结合逻辑与或实现复杂判断
# 判断文件存在且(是txt或sh格式)
if [ -e "file" ] && ( [ "$file" = "*.txt" ] || [ "$file" = "*.sh" ] ); then
echo "符合条件"
fi
四、注意事项
-
[ ]与[[ ]]的语法差异:[ ]中使用-a/-o,且条件内的变量建议加双引号(避免空值报错)。[[ ]]中使用&&/||,支持更直观的逻辑,且变量引用可不加引号(但建议加)。
-
命令间的逻辑与或:
&&和||可用于连接多个命令,实现 "依赖执行" 或 "兜底执行"。
-
优先级 :逻辑非(
!)> 逻辑与(-a/&&)> 逻辑或(-o/||),复杂逻辑建议用括号明确优先级。
4.1 条件判断(if 语句)
格式:
if [ 条件表达式 ]; then
# 条件为真时执行
elif [ 条件表达式 ]; then
# 否则若该条件为真时执行
else
# 所有条件为假时执行
fi
条件表达式规则:
- 中括号
[ ]前后必须有空格。 - 数值比较:
-eq(等于)、-ne(不等于)、-gt(大于)、-lt(小于)、-ge(大于等于)、-le(小于等于)。例:[ $a -gt 10 ](a 大于 10)。 - 字符串比较:
=(等于)、!=(不等于)、-z(空字符串)、-n(非空字符串)。例:[ "$str" = "hello" ](字符串相等,变量加引号避免空值报错)。 - 文件判断:
-f(普通文件)、-d(目录)、-r(可读)、-w(可写)、-x(可执行)、-e(存在)。例:[ -f "file.txt" ](file.txt 是普通文件)。
示例(判断文件是否存在):
#!/bin/bash
read -p "输入文件名:" file
if [ -e "$file" ]; then
echo "$file 存在"
if [ -f "$file" ]; then
echo "是普通文件"
elif [ -d "$file" ]; then
echo "是目录"
fi
else
echo "$file 不存在"
fi
4.2 多分支条件判断(case in 语句)
在 Shell 脚本中,case in语句用于多分支条件判断 ,功能类似于其他编程语言的switch-case语句,适合处理变量与多个固定值 / 模式匹配的场景,语法简洁且可读性强。
一、基本语法结构
case $变量 in
模式1)
命令序列1
;; # 双分号表示分支结束(无需break,自动退出case)
模式2)
命令序列2
;;
模式N)
命令序列N
;;
*) # 通配符*表示"默认分支"(所有未匹配的情况)
命令序列默认
;;
esac # case的结束标记
二、模式匹配规则
case in的模式支持通配符,规则与 Shell 文件名匹配一致:
*:匹配任意字符(包括空字符)。?:匹配单个字符。[字符范围]:匹配指定范围 / 集合的单个字符 (如[a-z]匹配小写字母)。- 多个模式可通过
|分隔(表示 "或"),如模式1|模式2)。
三、使用示例
示例 1:菜单选择
根据用户输入的数字执行对应操作:
#!/bin/bash
read -p "请选择操作(1-3):" choice
case $choice in
1)
echo "执行操作1:创建文件"
touch new_file.txt
;;
2)
echo "执行操作2:删除文件"
rm -f old_file.txt
;;
3)
echo "执行操作3:查看文件"
ls -l
;;
*)
echo "输入无效,请选择1-3!"
;;
esac
示例 2:文件类型判断
根据文件扩展名执行不同处理:
#!/bin/bash
file="report.pdf"
ext=${file##*.} # 提取文件扩展名(如pdf、txt)
case $ext in
txt|md) # 匹配txt或md格式
echo "文本文件,使用cat查看"
cat "$file"
;;
pdf|jpg|png) # 匹配pdf、图片格式
echo "多媒体文件,无法直接查看"
;;
*)
echo "未知格式:$ext"
;;
esac
示例 3:通配符匹配
处理带通配符的输入:
#!/bin/bash
read -p "输入一个字符:" char
case $char in
[a-z]) # 匹配小写字母
echo "你输入了小写字母:$char"
;;
[A-Z]) # 匹配大写字母
echo "你输入了大写字母:$char"
;;
[0-9]) # 匹配数字
echo "你输入了数字:$char"
;;
?) # 匹配单个非空字符(如符号)
echo "你输入了符号:$char"
;;
"") # 匹配空输入(无内容)
echo "你没有输入任何内容"
;;
esac
四、关键特性与注意事项
- 自动退出分支 :每个分支的
;;会让case执行完命令后直接退出,无需像其他语言一样写break。 - 变量引用 :
case后的变量直接写$变量,模式中无需加 $ 符号。 - 默认分支的必要性 :
*)分支建议保留,确保所有未匹配的情况都有处理逻辑,避免脚本异常。 - 模式顺序 :若多个模式存在包含关系(如
*和具体值),需将具体模式放在前面 ,否则会被*提前匹配。
五、与if-elif-else的对比
| 场景 | case in |
if-elif-else |
|---|---|---|
| 多固定值匹配 | 语法简洁,可读性高 | 分支多时有冗余,可读性差 |
| 复杂条件(如范围) | 依赖通配符,灵活性有限 | 支持数值 / 逻辑运算,灵活性高 |
| 执行效率 | 内部是哈希表匹配,效率略高 | 逐分支判断,效率随分支数降低 |
5.test语句
在 Shell 脚本中,test命令是用于条件检测的基础工具,[ ](中括号)本质上是test命令的语法糖(符号链接) ,二者功能几乎完全一致。补充test命令的用法,能更全面理解 Shell 的条件判断机制。
一、test命令的基本用法
test命令用于检测条件是否成立,语法为:
test 条件表达式
- 若条件成立,
test返回退出状态码 0(表示 "真"); - 若条件不成立,返回非 0 状态码(表示 "假")。
通常结合if、while等流程控制语句使用,例如:
# 判断文件是否存在
if test -f "file.txt"; then
echo "file.txt存在"
fi
二、test与[ ]的等价性
[ ]是test的简写形式,二者完全等价。例如:
# 以下两种写法效果一致
test -f "file.txt"
[ -f "file.txt" ] # 注意:[ 后和 ] 前必须有空格(因为[是命令,]是它的最后一个参数)
因此,之前提到的文件测试、字符串测试、数值测试 等条件表达式,既可以用test,也可以用[ ],语法规则完全相同。
三、test支持的条件表达式分类
1. 文件测试(检测文件 / 目录属性)
| 表达式 | 含义 | test写法示例 |
|---|---|---|
-e 文件 |
文件 / 目录是否存在 | test -e "test.sh" |
-f 文件 |
是否为普通文件(非目录 / 设备文件) | test -f "data.txt" |
-d 目录 |
是否为目录 | test -d "backup/" |
-r 文件 |
是否有读权限 | test -r "config.ini" |
-w 文件 |
是否有写权限 | test -w "log.txt" |
-x 文件 |
是否有执行权限(或目录是否可进入) | test -x "script.sh" |
-s 文件 |
文件是否存在且非空(大小 > 0) | test -s "result.log" |
文件1 -nt 文件2 |
文件 1 是否比文件 2 新(修改时间更新) | test "a.txt" -nt "b.txt" |
文件1 -ot 文件2 |
文件 1 是否比文件 2 旧(修改时间更早) | test "a.txt" -ot "b.txt" |
2. 字符串测试(检测字符串属性)
| 表达式 | 含义 | test写法示例 |
|---|---|---|
字符串 |
字符串是否非空(非空为真) | test "hello"(真)、test ""(假) |
-z 字符串000 |
字符串是否为空(空为真) | test -z ""(真)、test -z "a"(假) |
-n 字符串 |
字符串是否非空(非空为真,同直接写字符串) | test -n "hello"(真) |
字符串1 = 字符串2 |
两字符串是否相等(=可换为==,但=更兼容) |
test "abc" = "abc"(真) |
字符串1 != 字符串2 |
两字符串是否不相等 | test "abc" != "def"(真) |
3. 数值测试(比较整数)
| 表达式 | 含义 | test写法示例 |
|---|---|---|
数值1 -eq 数值2 |
等于(equal) | test 5 -eq 5(真) |
数值1 -ne 数值2 |
不等于(not equal) | test 5 -ne 3(真) |
数值1 -gt 数值2 |
大于(greater than) | test 10 -gt 5(真) |
数值1 -lt 数值2 |
小于(less than) | test 3 -lt 7(真) |
数值1 -ge 数值2 |
大于等于(>=) | test 5 -ge 5(真) |
数值1 -le 数值2 |
小于等于(<=) | test 4 -le 6(真) |
4. 逻辑组合(多条件判断)
test支持通过逻辑运算符组合多个条件,常用运算符:
!:逻辑非(取反);-a:逻辑与(两个条件都成立才为真);-o:逻辑或(至少一个条件成立为真)。
示例:
# 判断文件存在且有写权限(逻辑与)
test -e "file.txt" -a -w "file.txt"
# 判断字符串非空或数值大于10(逻辑或)
test -n "$str" -o 20 -gt 10
# 判断文件不存在(逻辑非)
test ! -e "nonexist.txt"
四、test与[[ ]]的区别(扩展)
Bash 支持更强大的[[ ]](双中括号),它是test/[ ]的增强版,语法更灵活,支持:
- 直接使用
&&(与)、||(或)代替-a、-o,逻辑更清晰; - 字符串比较支持模式匹配(如
*、?通配符); - 不需要对特殊字符(如
<、>)转义; - 变量引用时,即使为空也不易报错(无需强制加引号,但建议加)。
示例:
# [[ ]]中使用&&代替-a
[[ -f "file.txt" && -r "file.txt" ]]
# 模式匹配:判断字符串是否以".sh"结尾
str="script.sh"
[[ $str == *.sh ]] # 成立(真)
# 数值比较可直接用>、<(无需-gt/-lt)
[[ 10 > 5 ]] # 成立(真)
注意:
[[ ]]是 Bash 的扩展语法,兼容性不如test/[ ](如在sh解释器中可能不支持),脚本需兼容多种 Shell 时建议用test/[ ]。
五、实战示例:用test完善脚本
基于之前的 "文件备份脚本",用test改写条件判断部分:
#!/bin/bash
# 用test命令完善的备份脚本
if test $# -ne 1; then # 判断参数个数是否为1
echo "用法:$0 <要备份的目录>"
exit 1
fi
src_dir=$1
if test ! -d "$src_dir"; then # 判断目录是否不存在
echo "错误:$src_dir 不是目录或不存在"
exit 1
fi
# 后续逻辑不变...
test命令总结
test是 Shell 条件检测的基础命令,[ ]是其简写,二者完全等价;- 支持文件、字符串、数值三类测试,以及逻辑组合(
!、-a、-o); [[ ]]是 Bash 增强版,语法更灵活,但兼容性稍弱;- 实际使用中,
[ ]因写法简洁更常用,test多用于强调可读性的场景。
6. 循环结构
(1)for 循环
遍历列表或范围:
# 遍历列表
for fruit in apple banana orange; do
echo "水果:$fruit"
done
# 遍历数字范围(bash支持)
for i in {1..5}; do
echo "数字:$i"
done
# C语言风格(bash支持)
for ((i=0; i<3; i++)); do
echo "i=$i"
done
(2)while 循环
条件为真时重复执行:
#!/bin/bash
count=1
while [ $count -le 3 ]; do
echo "计数:$count"
count=$((count + 1)) # 数值计算($((表达式)))
done
读取文件每一行:
# 读取file.txt的每一行并输出
while read line; do
echo "行内容:$line"
done < file.txt # < 重定向输入
(3)until 循环
条件为假时重复执行(与 while 相反):
count=1
until [ $count -gt 3 ]; do
echo "计数:$count"
count=$((count + 1))
done
7. 函数
封装可复用的代码块:
# 定义函数(两种格式)
function hello() {
echo "Hello, $1!" # $1是函数的第一个参数
}
# 或简化为:
greet() {
local name=$1 # local声明局部变量(仅函数内有效)
echo "欢迎,$name!"
return 0 # 返回值(0-255,默认最后一条命令的状态)
}
# 调用函数
hello "Tom" # 输出:Hello, Tom!
greet "Jerry" # 输出:欢迎,Jerry!
echo "greet函数返回值:$?" # 输出0(成功)
8. 管道与重定向
-
管道(|) :将前一个命令的输出作为后一个命令的输入。例:
ls -l | grep ".txt"(查找当前目录的 txt 文件)。 -
重定向:
>:覆盖写入文件(例:echo "test" > file.txt)。>>:追加写入文件(例:echo "new line" >> file.txt)。<:从文件读取输入(例:cat < file.txt)。2>:重定向错误输出(例:ls error.txt 2> err.log,错误写入 err.log)。&>:同时重定向标准输出和错误(例:command &> output.log)。
四、实战示例:文件备份脚本
功能:将指定目录备份到backup文件夹,文件名含当前日期。
#!/bin/bash
# 备份脚本:backup.sh
# 检查参数(需传入要备份的目录)
if [ $# -ne 1 ]; then
echo "用法:$0 <要备份的目录>"
exit 1 # 非0退出表示失败
fi
src_dir=$1
# 检查目录是否存在
if [ ! -d "$src_dir" ]; then
echo "错误:$src_dir 不是目录或不存在"
exit 1
fi
# 备份文件名(例:20251111_backup.tar.gz)
backup_name=$(date +%Y%m%d)_backup.tar.gz
# 创建备份目录(若不存在)
mkdir -p backup
# 打包备份
tar -czf "backup/$backup_name" "$src_dir"
# 检查备份是否成功
if [ $? -eq 0 ]; then
echo "备份成功:backup/$backup_name"
else
echo "备份失败"
exit 1
fi
运行:./backup.sh ~/Documents,会在当前目录的backup文件夹生成带日期的压缩包。
五、常见问题
- 脚本执行报错 "Permission denied":未赋予执行权限,需
chmod +x 脚本名。 - 变量引用报错:赋值时等号前后有空格,或字符串比较未加引号(空变量会导致语法错误)。
- 条件判断报错:中括号
[ ]前后缺少空格,或使用了错误的比较符号(如数值用>而非-gt)。