1. Shell脚本编程概述
Shell脚本是Linux/Unix系统中自动化任务的利器。作为系统管理员和开发者的必备技能,它能够将繁琐的日常操作转化为可重复执行的自动化流程。不同于其他编程语言需要编译环境,Shell脚本直接运行在命令行界面,具有即时生效、调试方便的特点。
我最初接触Shell是为了解决服务器日志定期清理的问题。手动执行rm命令既容易出错又耗时,而一个简单的脚本就能完美解决这个问题。随着经验积累,我发现Shell能做的事情远不止于此——从文件批量处理到系统监控,从数据备份到自动化部署,Shell脚本几乎能覆盖所有日常系统管理需求。
2. 基础语法精要
2.1 脚本结构解析
每个Shell脚本都遵循特定的结构。以下是一个标准模板:
bash复制#!/bin/bash
# 注释:脚本描述和用法说明
# 变量定义
CONFIG_FILE="/etc/app.conf"
# 函数定义
check_disk() {
local disk_usage=$(df -h | grep '/dev/sda1')
echo "Disk usage: $disk_usage"
}
# 主程序逻辑
main() {
echo "脚本开始执行:$(date)"
check_disk
# 其他操作...
echo "脚本执行完成:$(date)"
}
# 执行入口
main "$@"
关键要点:
#!/bin/bash指定解释器路径(称为shebang)- 注释使用
#符号,良好的注释习惯至关重要 - 变量定义避免使用连字符(如
my-var非法) - 函数定义采用
function_name() { ... }格式
2.2 变量操作技巧
Shell变量处理有其独特之处:
bash复制# 基本赋值(等号两侧不能有空格)
name="value"
# 使用变量
echo $name # 简单引用
echo ${name} # 标准引用(推荐)
# 特殊变量
echo $0 # 脚本名称
echo $1 # 第一个参数
echo $# # 参数个数
echo $? # 上条命令退出状态
# 默认值处理
echo ${VAR:-default} # VAR未设置时使用default
echo ${VAR:=default} # VAR未设置时赋值default
重要提示:变量引用时总是加上花括号是个好习惯,可以避免解析歧义。例如
$file_1和${file}_1有完全不同的含义。
2.3 流程控制实战
条件判断
bash复制# 基本if结构
if [ condition ]; then
commands
elif [ condition ]; then
commands
else
commands
fi
# 文件测试示例
if [ -f "/path/to/file" ]; then
echo "文件存在"
fi
# 数值比较
if [ "$a" -gt "$b" ]; then
echo "a大于b"
fi
常用测试运算符:
- 文件测试:
-e(存在),-f(普通文件),-d(目录) - 字符串测试:
=,!=,-z(空),-n(非空) - 数值比较:
-eq,-ne,-gt,-lt
循环结构
bash复制# for循环
for i in {1..5}; do
echo "Iteration $i"
done
# while循环
count=1
while [ $count -le 5 ]; do
echo "Count: $count"
((count++))
done
# until循环(与while条件相反)
until [ $count -gt 10 ]; do
echo "Until: $count"
((count++))
done
3. 高级特性深入
3.1 函数开发实践
Shell函数是代码复用的核心单元:
bash复制# 定义带参数的函数
log_message() {
local level="$1" # local限制变量作用域
local msg="$2"
echo "[$(date)] [$level] $msg" >> /var/log/myscript.log
}
# 调用函数
log_message "INFO" "脚本启动"
# 返回值处理
is_file_exist() {
[ -f "$1" ] && return 0 || return 1
}
is_file_exist "/etc/passwd"
if [ $? -eq 0 ]; then
echo "文件存在"
fi
3.2 信号处理机制
脚本需要对系统信号做出响应:
bash复制#!/bin/bash
# 定义清理函数
cleanup() {
echo "捕获中断信号,执行清理..."
rm -f /tmp/tempfile.*
exit 1
}
# 注册信号处理
trap cleanup SIGINT SIGTERM
# 主程序
echo "脚本PID: $$"
echo "按Ctrl+C测试信号处理"
sleep 60
常见信号:
- SIGINT (2): Ctrl+C中断
- SIGTERM (15): 终止请求
- SIGKILL (9): 强制终止(不可捕获)
3.3 数组与关联数组
bash复制# 索引数组
fruits=("Apple" "Banana" "Orange")
echo ${fruits[1]} # 输出Banana
fruits[3]="Grape" # 添加元素
echo ${#fruits[@]} # 数组长度
# 关联数组(Bash 4+)
declare -A user
user=([name]="John" [age]=30 [email]="john@example.com")
echo ${user[name]}
4. 实战案例解析
4.1 日志分析脚本
bash复制#!/bin/bash
# 分析Nginx访问日志的TOP 10 IP
LOG_FILE="/var/log/nginx/access.log"
OUTPUT_FILE="/tmp/top_ips.txt"
# 检查日志文件
[ ! -f "$LOG_FILE" ] && { echo "错误:日志文件不存在"; exit 1; }
# 分析函数
analyze_log() {
echo "分析时间: $(date)" > "$OUTPUT_FILE"
echo "=====================" >> "$OUTPUT_FILE"
# 提取TOP 10 IP
awk '{print $1}' "$LOG_FILE" | sort | uniq -c | sort -nr | head -10 >> "$OUTPUT_FILE"
# 统计总请求数
total=$(wc -l < "$LOG_FILE")
echo "总请求数: $total" >> "$OUTPUT_FILE"
}
analyze_log
echo "分析完成,结果保存在 $OUTPUT_FILE"
4.2 系统监控脚本
bash复制#!/bin/bash
# 系统资源监控脚本
THRESHOLD=90 # 百分比阈值
LOG_FILE="/var/log/system_monitor.log"
# 监控函数
monitor() {
# CPU使用率
cpu_usage=$(top -bn1 | grep "Cpu(s)" | sed "s/.*, *\([0-9.]*\)%* id.*/\1/" | awk '{print 100 - $1}')
# 内存使用率
mem_usage=$(free | grep Mem | awk '{print $3/$2 * 100.0}')
# 磁盘使用率
disk_usage=$(df -h / | awk 'NR==2 {print $5}' | tr -d '%')
# 记录日志
echo "$(date) - CPU: ${cpu_usage}% | Memory: ${mem_usage}% | Disk: ${disk_usage}%" >> "$LOG_FILE"
# 报警检查
if (( $(echo "$cpu_usage > $THRESHOLD" | bc -l) )); then
send_alert "CPU" "$cpu_usage"
fi
# 类似检查内存和磁盘...
}
send_alert() {
local metric=$1
local value=$2
echo "警告:$metric 使用率过高 ($value%)" | mail -s "系统警报" admin@example.com
}
# 主循环
while true; do
monitor
sleep 300 # 5分钟间隔
done
5. 调试与优化
5.1 调试技巧
bash复制#!/bin/bash -x # 启用调试模式
# 或者在脚本中局部调试
set -x # 开启调试
command1
command2
set +x # 关闭调试
# 常用调试选项
set -e # 命令失败时退出
set -u # 使用未定义变量时报错
set -o pipefail # 管道中任意命令失败则整体失败
5.2 性能优化
-
减少子进程创建:
bash复制# 不良实践:每次循环都调用grep for file in *; do grep "pattern" "$file" done # 优化方案:单次grep处理 grep "pattern" * -
使用内置字符串操作:
bash复制# 替代awk/sed的简单操作 str="hello world" echo ${str:6} # 子串:world echo ${str/world/Shell} # 替换 -
选择高效工具:
- 文本处理:
awk>sed>grep>while read - 数学运算:
$(( ))或let优于外部expr
- 文本处理:
6. 安全最佳实践
6.1 输入验证
bash复制#!/bin/bash
# 检查参数数量
if [ $# -lt 2 ]; then
echo "用法: $0 <用户名> <目录>"
exit 1
fi
# 验证用户存在
if ! id "$1" &>/dev/null; then
echo "错误:用户 $1 不存在"
exit 1
fi
# 验证目录安全
if [[ "$2" != /safe/path/* ]]; then
echo "错误:只能操作/safe/path/下的目录"
exit 1
fi
6.2 权限控制
bash复制# 检查root权限
if [ "$(id -u)" -ne 0 ]; then
echo "该脚本需要root权限"
exit 1
fi
# 最小权限原则
run_as_user() {
local user=$1
local cmd=$2
sudo -u "$user" -- bash -c "$cmd"
}
# 安全文件操作
safe_tmpfile() {
local prefix=$1
umask 077 # 限制文件权限
mktemp "/tmp/${prefix}.XXXXXX"
}
7. 现代Shell扩展
7.1 Bash 5.x新特性
bash复制# 关联数组改进
declare -A dict=([key1]=value1 [key2]=value2)
# 参数扩展增强
${var@U} # 转换为大写
${var@L} # 转换为小写
# 进程替换改进
diff <(cmd1) <(cmd2)
7.2 ShellCheck静态分析
安装和使用ShellCheck:
bash复制# Debian/Ubuntu
sudo apt install shellcheck
# 使用示例
shellcheck myscript.sh
常见问题检测:
- 未引用的变量
- 不正确的条件表达式
- 不安全的命令执行
- 不兼容的Shell特性
8. 跨平台兼容性
8.1 兼容性技巧
bash复制#!/bin/sh
# 使用POSIX兼容语法
# 替代Bash特有语法
case "$OSTYPE" in
linux*) echo "Linux系统" ;;
darwin*) echo "MacOS系统" ;;
*) echo "其他系统" ;;
esac
# 通用命令检测
if command -v python3 >/dev/null 2>&1; then
echo "Python3可用"
else
echo "需要安装Python3"
fi
8.2 工具替代方案
| 功能需求 | Linux | MacOS | BSD |
|---|---|---|---|
| 获取IP地址 | ip addr |
ifconfig |
ifconfig |
| 进程搜索 | pgrep |
`ps aux | grep` |
| 时间戳获取 | date +%s |
date +%s |
date -j +%s |
| 文件修改时间 | stat -c %Y |
stat -f %m |
stat -f %m |
9. 资源推荐
9.1 学习资料
-
书籍:
- 《Linux命令行与Shell脚本编程大全》
- 《Bash Cookbook》
- 《Shell脚本学习指南》
-
在线资源:
- Bash官方文档:
man bash - Google Shell风格指南
- Linux Documentation Project (TLDP)
- Bash官方文档:
9.2 实用工具
| 工具名称 | 用途描述 | 安装方法 |
|---|---|---|
| jq | JSON处理工具 | apt install jq |
| csvkit | CSV数据处理套件 | pip install csvkit |
| moreutils | 增强型Unix工具集 | apt install moreutils |
| parallel | 并行任务处理 | apt install parallel |
| httpie | 现代curl替代品 | pip install httpie |
10. 持续集成中的应用
10.1 CI/CD集成示例
bash复制#!/bin/bash
# GitLab CI示例脚本
set -eo pipefail
# 构建阶段
build() {
echo "开始构建..."
make all
./run_tests
}
# 部署阶段
deploy() {
local env=$1
echo "部署到 $env 环境"
case "$env" in
staging)
scp -r build/* user@staging:/app
;;
production)
kubectl apply -f k8s/
;;
*)
echo "未知环境"
exit 1
;;
esac
}
# 主流程
case "$CI_JOB_NAME" in
"build_and_test")
build
;;
"deploy_staging")
deploy staging
;;
"deploy_prod")
deploy production
;;
esac
10.2 错误处理模式
bash复制# 高级错误处理函数
error_exit() {
local msg="$1"
local code=${2:-1} # 默认退出码1
echo "[ERROR] $msg" >&2
exit "$code"
}
# 安全命令执行
safe_run() {
"$@" || error_exit "命令失败: $*" $?
}
# 使用示例
safe_run critical_command
safe_run another_command arg1 arg2
