1. Shell脚本编程概述
Shell脚本是Linux/Unix系统中自动化任务的利器。作为一名运维工程师,我每天都要处理数十个Shell脚本,从简单的日志清理到复杂的系统部署。很多人觉得Shell语法简单,但真正写出健壮、高效的脚本需要掌握很多技巧。这个系列将带大家从基础语法开始,逐步深入到实际应用场景。
Shell脚本本质上是一个包含一系列命令的文本文件,通过Shell解释器执行。它的优势在于:
- 直接调用系统命令,无需编译
- 支持流程控制和函数封装
- 可以方便地处理文本和文件
- 跨平台(在类Unix系统通用)
2. 基础语法精要
2.1 脚本结构规范
一个标准的Shell脚本应该包含以下部分:
bash复制#!/bin/bash
# 作者:Your Name
# 日期:2023-07-20
# 描述:这个脚本的功能说明
set -euo pipefail # 开启严格模式
# 常量定义
readonly MAX_RETRY=3
readonly LOG_FILE="/var/log/myscript.log"
# 函数定义
function cleanup() {
# 清理逻辑
}
# 主程序
main() {
# 业务逻辑
}
# 脚本入口
main "$@"
关键技巧:总是使用
set -euo pipefail可以让脚本在出错时立即退出,避免隐藏错误。
2.2 变量操作进阶
变量处理是Shell脚本的核心:
bash复制# 字符串操作
str="Hello World"
echo ${str:0:5} # 子串:Hello
echo ${str//o/O} # 替换:HellO WOrld
# 数组操作
arr=("apple" "banana" "cherry")
echo ${arr[1]} # 访问:banana
echo ${#arr[@]} # 长度:3
# 关联数组(需要bash 4.0+)
declare -A dict=([key1]="value1" [key2]="value2")
echo ${dict[key1]} # value1
2.3 流程控制实战
条件判断和循环的实用写法:
bash复制# 条件判断
if [[ -f "/etc/passwd" && -r "/etc/passwd" ]]; then
echo "文件存在且可读"
elif (( $(id -u) == 0 )); then
echo "你是root用户"
else
echo "条件不满足"
fi
# 循环处理
for i in {1..5}; do
echo "第$i次循环"
done
while read -r line; do
echo "处理行: $line"
done < input.txt
3. 高级特性解析
3.1 函数设计与使用
良好的函数设计能提升脚本可维护性:
bash复制# 带参数和返回值的函数
process_file() {
local filename="$1" # 局部变量
local mode="${2:-read}" # 默认参数
[[ ! -f "$filename" ]] && return 1
case "$mode" in
read) cat "$filename" ;;
write) echo "data" > "$filename" ;;
*) return 2 ;;
esac
return 0
}
# 调用示例
if process_file "/tmp/test.txt"; then
echo "处理成功"
else
echo "处理失败,错误码$?"
fi
3.2 信号处理与并发
处理中断信号和实现并发:
bash复制# 信号处理
trap 'cleanup; exit 1' SIGINT SIGTERM
# 并发控制
max_workers=4
for i in {1..10}; do
(
# 子进程任务
do_work "$i"
) &
# 控制并发数
if [[ $(jobs -r | wc -l) -ge $max_workers ]]; then
wait -n
fi
done
wait # 等待所有后台任务完成
3.3 正则表达式应用
文本处理的利器:
bash复制# 基础匹配
if [[ "$OSTYPE" =~ linux ]]; then
echo "这是Linux系统"
fi
# 提取分组
email="user@example.com"
if [[ "$email" =~ ^([^@]+)@([^@]+)$ ]]; then
echo "用户名: ${BASH_REMATCH[1]}"
echo "域名: ${BASH_REMATCH[2]}"
fi
# sed高级用法
sed -E 's/([0-9]{3})-([0-9]{4})/\1****/g' data.txt
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; }
# 分析处理
echo "Top 10访问IP:" > "$OUTPUT_FILE"
awk '{print $1}' "$LOG_FILE" | sort | uniq -c | sort -nr | head -10 >> "$OUTPUT_FILE"
# 输出结果
cat "$OUTPUT_FILE"
4.2 系统监控脚本
bash复制#!/bin/bash
# 系统资源监控告警
WARNING=80
CRITICAL=90
# 获取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}')
# 告警逻辑
if (( $(echo "$cpu_usage > $CRITICAL" | bc -l) )); then
send_alert "CPU" "$cpu_usage"
elif (( $(echo "$cpu_usage > $WARNING" | bc -l) )); then
send_warning "CPU" "$cpu_usage"
fi
# 类似处理内存...
4.3 自动化部署脚本
bash复制#!/bin/bash
# 应用自动化部署脚本
DEPLOY_DIR="/opt/myapp"
BACKUP_DIR="/var/backups/myapp"
VERSION="1.2.0"
# 创建备份
backup() {
local timestamp=$(date +%Y%m%d%H%M%S)
tar -czf "$BACKUP_DIR/$timestamp.tar.gz" "$DEPLOY_DIR"
}
# 部署新版本
deploy() {
echo "正在部署版本 $VERSION"
rsync -avz --delete ./build/ "$DEPLOY_DIR/"
systemctl restart myapp
}
# 主流程
main() {
backup || { echo "备份失败"; exit 1; }
deploy
echo "部署成功完成"
}
main "$@"
5. 调试与优化技巧
5.1 调试方法
bash复制# 使用-x参数调试
bash -x script.sh
# 在脚本中局部调试
set -x # 开启调试
# 调试代码块
set +x # 关闭调试
# 使用trap调试
trap 'echo "在行 $LINENO: 变量x=$x"' DEBUG
5.2 性能优化
- 减少子进程创建:
bash复制# 不好:创建了两个子进程
var=`grep pattern file | wc -l`
# 好:只创建一个子进程
var=$(grep -c pattern file)
- 使用内置字符串操作:
bash复制# 不好:使用外部命令
len=$(echo "$str" | wc -c)
# 好:使用bash内置
len=${#str}
- 批量处理代替循环:
bash复制# 不好:逐行处理
while read line; do
process "$line"
done < file
# 好:使用xargs并行处理
tr '\n' '\0' < file | xargs -0 -P4 -n1 process
5.3 安全实践
- 变量引用:
bash复制# 危险:可能包含空格或特殊字符
rm -rf $DIR/*
# 安全:总是引用变量
rm -rf "$DIR"/*
- 输入验证:
bash复制# 检查输入是否为数字
if [[ "$1" =~ ^[0-9]+$ ]]; then
echo "有效数字"
else
echo "无效输入"
fi
- 权限控制:
bash复制# 检查脚本是否以root运行
if [[ $EUID -ne 0 ]]; then
echo "请使用root用户运行" >&2
exit 1
fi
6. 常见问题解决方案
6.1 编码问题
bash复制# 设置脚本编码
#!/bin/bash
export LANG=en_US.UTF-8
# 转换文件编码
iconv -f GBK -t UTF-8 input.txt > output.txt
6.2 路径问题
bash复制# 获取脚本所在目录
SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" &>/dev/null && pwd)
# 处理相对路径
abs_path=$(realpath "./relative/path")
6.3 兼容性问题
bash复制# 检查bash版本
if (( BASH_VERSINFO[0] < 4 )); then
echo "需要bash 4.0或更高版本"
exit 1
fi
# 跨平台命令处理
case "$(uname -s)" in
Linux*) machine=Linux;;
Darwin*) machine=Mac;;
CYGWIN*) machine=Cygwin;;
MINGW*) machine=MinGw;;
*) machine="UNKNOWN"
esac
7. 工具与资源推荐
7.1 开发工具
- ShellCheck:静态分析工具
bash复制# 安装
apt install shellcheck
# 使用
shellcheck script.sh
- Bash Debugger:调试工具
bash复制bashdb script.sh
- Visual Studio Code插件:
- Bash IDE
- Shell Format
- Shellman(代码片段)
7.2 学习资源
- 官方文档:
man bash- Bash参考手册
- 经典书籍:
- 《Linux命令行与Shell脚本编程大全》
- 《Advanced Bash-Scripting Guide》
- 在线练习:
8. 脚本模板与最佳实践
8.1 企业级脚本模板
bash复制#!/bin/bash
# 企业级脚本模板
# 1. 初始化设置
set -euo pipefail
IFS=$'\n\t'
umask 0077
# 2. 配置区
readonly SCRIPT_NAME="${0##*/}"
readonly VERSION="1.0.0"
readonly LOCK_FILE="/tmp/${SCRIPT_NAME}.lock"
readonly LOG_DIR="/var/log/${SCRIPT_NAME%.*}"
readonly MAX_RETRY=3
# 3. 函数定义
init() {
check_dependencies
setup_logging
create_lock
}
check_dependencies() {
local deps=("awk" "sed" "curl")
for dep in "${deps[@]}"; do
if ! command -v "$dep" >/dev/null 2>&1; then
die "缺少依赖: $dep"
fi
done
}
# 4. 主逻辑
main() {
init
parse_args "$@"
run_business_logic
}
# 5. 脚本入口
main "$@"
8.2 代码风格指南
-
命名约定:
- 常量:全大写加下划线
MAX_RETRY - 变量:小写加下划线
file_path - 函数:小驼峰
processData
- 常量:全大写加下划线
-
格式规范:
- 缩进:4个空格(不用Tab)
- 行宽:不超过80字符
- 函数:前后空行
-
注释要求:
- 文件头:作者、用途、版本
- 函数:功能、参数、返回值
- 复杂逻辑:解释业务意图
9. 性能对比测试
9.1 不同循环方式性能
测试10000次循环:
bash复制# for循环
time for ((i=0; i<10000; i++)); do :; done
# while循环
time i=0; while ((i++ < 10000)); do :; done
# seq+xargs
time seq 1 10000 | xargs -I{} :
测试结果(示例):
| 方式 | 用户时间 | 系统时间 |
|---|---|---|
| for循环 | 0.02s | 0.00s |
| while循环 | 0.03s | 0.00s |
| seq+xargs | 0.12s | 0.05s |
9.2 字符串连接方法
测试10000次字符串连接:
bash复制# 直接连接
result=""
time for ((i=0; i<10000; i++)); do result+="a"; done
# 数组join
result=()
time for ((i=0; i<10000; i++)); do result+=("a"); done
result=$(IFS=""; echo "${result[*]}")
测试结果:
| 方法 | 时间 | 内存占用 |
|---|---|---|
| 直接连接 | 0.45s | 高 |
| 数组join | 0.12s | 低 |
10. 扩展应用场景
10.1 与Python协作
bash复制#!/bin/bash
# 调用Python处理数据
# 生成数据
generate_data() {
seq 1 100
}
# 使用Python处理
process_with_python() {
python3 -c "
import sys, math
for line in sys.stdin:
num = int(line.strip())
print(f'{num}\t{math.sqrt(num):.2f}')
"
}
# 主流程
main() {
generate_data | process_with_python > results.tsv
}
main "$@"
10.2 自动化测试集成
bash复制#!/bin/bash
# 集成测试脚本
run_unit_tests() {
pytest tests/unit/
}
run_integration_tests() {
docker-compose up -d
pytest tests/integration/
docker-compose down
}
generate_report() {
allure generate --clean -o reports/
}
main() {
local exit_code=0
run_unit_tests || exit_code=$?
(( exit_code == 0 )) && run_integration_tests || exit_code=$?
generate_report
exit $exit_code
}
main "$@"
10.3 云平台自动化
bash复制#!/bin/bash
# AWS资源管理脚本
manage_ec2() {
local action="$1"
local instance_id="$2"
case "$action" in
start)
aws ec2 start-instances --instance-ids "$instance_id"
;;
stop)
aws ec2 stop-instances --instance-ids "$instance_id"
;;
*)
echo "无效操作"
return 1
esac
aws ec2 wait instance-"$action"ed --instance-ids "$instance_id"
}
# 使用示例
manage_ec2 "stop" "i-1234567890abcdef0"
掌握Shell脚本编程不仅能提高工作效率,还能培养解决问题的系统化思维。建议从简单的自动化任务开始,逐步挑战更复杂的场景,同时注意代码规范和可维护性。在实际工作中,我通常会先写伪代码梳理逻辑,再逐步实现各个功能模块,最后进行严格的测试和优化。
