目录
Shell 脚本编程是 Linux/Unix 系统中自动化任务、批量处理和系统管理的核心技术。它通过将一系列命令按逻辑组织成文本文件,实现自动化执行,极大提升了工作效率。以下从基础到进阶详细介绍 Shell 脚本编程:
一、什么是 Shell 脚本?
- Shell:是用户与操作系统内核交互的命令行解释器(如 bash、zsh、sh 等),负责解析并执行用户输入的命令。
- Shell 脚本:将一系列 Shell 命令按逻辑顺序写入文本文件,通过解释器批量执行,实现自动化任务(如日志分析、备份、部署等)。
最常用的 Shell 是 bash(Bourne Again SHell),本文以 bash 为例讲解。
二、基础入门:脚本创建与执行
1. 脚本文件格式
Shell 脚本文件以 .sh
为扩展名(非强制),开头必须指定解释器(shebang 行):
#!/bin/bash
# 这是注释(# 开头的行为注释)
echo "Hello, Shell Script!" # 输出内容
#!/bin/bash
:指定脚本由 bash 解释器执行(必须放在第一行)。
2. 执行脚本的 3 种方式
方式 1(推荐):赋予执行权限后直接运行
chmod +x script.sh # 赋予执行权限
./script.sh # 运行脚本(需在脚本所在目录)
方式 2:通过 bash 解释器直接调用
bash script.sh # 无需执行权限,直接用 bash 运行
方式 3:通过
source
或.
执行(会在当前 Shell 环境中运行,变量可保留)source script.sh # 等价于 . script.sh
三、核心语法:变量与输入输出
1. 变量定义与使用
定义变量:变量名 = 值(等号两边无空格)
name="Alice" # 字符串变量
age=25 # 数值变量
使用变量:通过 $变量名
或 ${变量名}
引用({}
用于区分边界)
echo "Name: $name" # 输出:Name: Alice
echo "Age: ${age}岁" # 输出:Age: 25岁({} 避免歧义)
只读变量:用 readonly
定义,无法修改
readonly pi=3.14
pi=3.1415 # 报错:pi: readonly variable
删除变量:用 unset
(只读变量不可删除)
unset name # 删除 name 变量
2. 字符串处理
单引号 vs 双引号:
单引号 ''
:字符串原样输出,不解析变量和转义字符
echo 'Name: $name' # 输出:Name: $name
双引号 ""
:解析变量和转义字符(如 \n
换行)
echo "Name: $name" # 输出:Name: Alice
字符串长度:${#变量名}
echo "Length of name: ${#name}" # 输出:5("Alice" 长度为 5)
字符串截取:${变量名:起始位置:长度}
str="hello world"
echo ${str:6:5} # 输出:world(从索引 6 开始,取 5 个字符)
3. 特殊变量(脚本参数与状态)
变量 | 含义 |
---|---|
$0 |
脚本文件名 |
$1~$n |
脚本的第 1~n 个参数($1 是第一个参数) |
$# |
参数的总个数 |
$* |
所有参数的集合(作为单个字符串) |
$@ |
所有参数的集合(作为独立字符串) |
$? |
上一个命令的退出状态(0 表示成功) |
$$ |
当前脚本的进程 ID(PID) |
示例:script.sh
内容
#!/bin/bash
echo "脚本名:$0"
echo "第一个参数:$1"
echo "参数总数:$#"
echo "所有参数:$@"
执行:./script.sh apple banana
,输出:
脚本名:./script.sh
第一个参数:apple
参数总数:2
所有参数:apple banana
4. 输入输出重定向
输出重定向:将命令结果写入文件(覆盖 / 追加)
echo "Hello" > output.txt # 覆盖写入 output.txt
echo "World" >> output.txt # 追加写入 output.txt
输入重定向:从文件读取输入(替代键盘输入)
wc -l < input.txt # 统计 input.txt 的行数(等价于 wc -l input.txt)
管道 |
:将前一个命令的输出作为后一个命令的输入
ps aux | grep "python" # 查找包含 "python" 的进程
四、控制结构:条件与循环
1. 条件判断(if-else)
语法:
if 条件; then
# 条件成立时执行
elif 条件; then
# 其他条件成立时执行
else
# 所有条件不成立时执行
fi
条件表达式:用 [ ]
(test 命令的简写)或 [[ ]]
(bash 扩展,支持更多特性),注意括号前后必须有空格。
常见判断类型:
数值比较:-eq
(等于)、-ne
(不等于)、-gt
(大于)、-lt
(小于)等
a=10
b=20
if [ $a -lt $b ]; then
echo "$a < $b"
fi
字符串比较:=
(等于)、!=
(不等于)、-z
(空字符串)、-n
(非空字符串)
str="test"
if [ -n "$str" ]; then # 字符串非空
echo "str is not empty"
fi
文件测试:-f
(普通文件)、-d
(目录)、-r
(可读)、-w
(可写)、-x
(可执行)
file="script.sh"
if [ -f "$file" ] && [ -x "$file" ]; then # 是文件且可执行
echo "$file is an executable file"
fi
2. 循环(for/while)
(1)for 循环
语法 1:遍历列表
for fruit in apple banana orange; do
echo "Fruit: $fruit"
done
语法 2:类 C 风格(循环范围)
for ((i=1; i<=5; i++)); do
echo "Count: $i"
done
语法 3:遍历目录文件
for file in ./test/*; do # 遍历 test 目录下的所有文件
if [ -f "$file" ]; then
echo "File: $file"
fi
done
(2)while 循环(条件为真时持续执行)
count=1
while [ $count -le 3 ]; do
echo "Count: $count"
count=$((count + 1)) # 变量自增(算术运算用 $(( )))
done
无限循环(配合 break
退出):
while true; do
read -p "输入 q 退出:" input
if [ "$input" = "q" ]; then
break # 退出循环
fi
done
3. case 语句(多分支判断)
适用于变量匹配多个固定值的场景:
read -p "输入分数(A/B/C):" grade
case $grade in
A) echo "优秀" ;;
B) echo "良好" ;;
C) echo "及格" ;;
*) echo "无效输入" ;; # * 匹配所有未列出的值
esac
五、函数:代码复用
1. 函数定义与调用
# 定义函数
greet() {
echo "Hello, $1!" # $1 是函数的第一个参数
}
# 调用函数(传递参数)
greet "Bob" # 输出:Hello, Bob!
2. 函数返回值
通过 return
返回状态码(0~255,0 表示成功)
add() {
return $(( $1 + $2 )) # 返回两数之和(状态码范围限制,不适合大数值)
}
add 3 5
echo "结果:$?" # 输出:8($? 获取上一个命令的返回值)
通过 echo
输出结果(适合返回字符串或大数值)
multiply() {
echo $(( $1 * $2 )) # 输出乘积
}
result=$(multiply 4 5) # 用 $() 捕获输出
echo "结果:$result" # 输出:20
六、常用工具:文本处理与系统操作
Shell 脚本的强大之处在于结合各类命令工具,常见场景:
1. 文本处理
grep
:查找匹配的行(支持正则表达式)
grep "error" app.log # 从 app.log 中查找包含 "error" 的行
awk
:按列处理文本(数据提取、统计)
awk '{print $1, $3}' data.txt # 打印 data.txt 的第 1 列和第 3 列
sed
:文本替换(批量修改内容)
sed -i 's/old/new/g' file.txt # 将 file.txt 中所有 "old" 替换为 "new"
2. 系统操作
find
:查找文件
find /home -name "*.txt" # 在 /home 下查找所有 .txt 文件
tar
:压缩 / 解压文件
tar -czf backup.tar.gz ./data # 压缩 data 目录为 backup.tar.gz
df/du
:查看磁盘空间
df -h # 查看磁盘使用情况(人类可读格式)
七、错误处理与调试
1. 错误处理
set -e
:脚本中任何命令失败(返回非 0 状态)时立即退出
#!/bin/bash
set -e # 开启错误退出
command1 # 若失败,脚本直接退出
command2 # 不会执行
trap
:捕获信号(如脚本退出时执行清理操作)
cleanup() {
echo "脚本退出,执行清理..."
# 清理临时文件等操作
}
trap cleanup EXIT # 脚本退出时调用 cleanup 函数
2.脚本调试
- 执行时加
-x
参数:打印执行的每一条命令(方便定位问题)
bash -x script.sh # 调试模式运行脚本
八、实战示例:自动备份脚本
#!/bin/bash
# 功能:每天备份指定目录到 /backup,并保留最近 7 天的备份
# 配置参数
SOURCE_DIR="/home/user/documents" # 要备份的目录
BACKUP_DIR="/backup" # 备份存储目录
DATE=$(date +%Y%m%d) # 当前日期(如 20231001)
BACKUP_FILE="$BACKUP_DIR/doc_backup_$DATE.tar.gz"
# 检查源目录是否存在
if [ ! -d "$SOURCE_DIR" ]; then
echo "错误:源目录 $SOURCE_DIR 不存在!"
exit 1 # 非 0 状态表示失败
fi
# 创建备份目录(若不存在)
mkdir -p "$BACKUP_DIR"
# 执行备份
echo "开始备份 $SOURCE_DIR 到 $BACKUP_FILE..."
tar -czf "$BACKUP_FILE" "$SOURCE_DIR"
# 检查备份是否成功
if [ $? -eq 0 ]; then
echo "备份成功!"
else
echo "备份失败!"
exit 1
fi
# 删除 7 天前的备份
echo "清理 7 天前的备份..."
find "$BACKUP_DIR" -name "doc_backup_*.tar.gz" -mtime +7 -delete
echo "操作完成!"
九、最佳实践
- 注释清晰:关键步骤添加注释,说明用途和逻辑。
- 变量复用:将路径、配置等定义为变量,方便维护。
- 检查边界:执行命令前检查文件 / 目录是否存在、权限是否足够。
- 避免硬编码:通过参数或配置文件传递可变值。
- 测试脚本:先用
bash -n script.sh
检查语法错误,再逐步调试。
十、应用场景
- 系统监控:定时检查磁盘、内存、进程状态。
- 日志分析:批量处理日志文件,提取关键信息。
- 自动化部署:一键部署代码、配置环境。
- 批量处理:批量重命名文件、转换格式等。