前言
Shell 脚本是将一系列 Linux 命令组合在一起自动执行的程序。它能帮助你自动化备份、部署、日志处理等日常运维任务,是每个后端开发者和运维工程师的必备技能。
本文基于 bash,这是 Linux 上最通用的 Shell。
一、第一个 Shell 脚本
1 2 3 4
| #!/bin/bash
echo "Hello, 七月小站!" echo "当前时间: $(date)"
|
解释:
| 代码 |
含义 |
#!/bin/bash |
Shebang,指定解释器 |
# 注释 |
以 # 开头的行为注释 |
echo |
输出文本 |
$(command) |
命令替换,执行命令并获取输出 |
运行脚本:
1 2
| chmod +x hello.sh ./hello.sh
|
二、变量
2.1 定义与使用
1 2 3 4 5 6 7 8 9 10
| #!/bin/bash name="July" age=25 readonly PI=3.14159
echo "我是${name},年龄${age}" echo "π ≈ $PI"
unset age
|
⚠️ 注意:= 两边不能有空格!
2.2 特殊变量
1 2 3 4 5 6 7 8
| #!/bin/bash echo "脚本名: $0" echo "第1个参数: $1" echo "第2个参数: $2" echo "参数个数: $#" echo "所有参数: $@" echo "上一条命令的退出状态: $?" echo "当前进程 PID: $$"
|
1 2 3 4 5 6
| ./test.sh hello world
|
2.3 读取用户输入
1 2 3 4 5 6 7 8 9
| #!/bin/bash echo -n "请输入名字: " read name echo "你好, $name!"
read -p "年龄: " age read -sp "密码: " password echo
|
三、字符串操作
1 2 3 4 5 6 7 8 9 10 11 12 13
| #!/bin/bash str="Hello, Bash!"
echo "长度: ${#str}" echo "子串: ${str:0:5}" echo "替换: ${str/Bash/World}" echo "是否包含: $(echo $str | grep -q 'Bash' && echo yes || echo no)"
a="Hello" b="World" c="$a, $b!" echo $c
|
四、数组
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| #!/bin/bash
fruits=("苹果" "香蕉" "橘子" "草莓")
echo ${fruits[0]} echo ${fruits[@]} echo ${#fruits[@]}
for fruit in "${fruits[@]}"; do echo "$fruit" done
fruits+=("葡萄") unset fruits[1]
|
五、条件判断
5.1 if 语句
1 2 3 4 5 6 7 8 9 10 11 12
| #!/bin/bash score=85
if [ $score -ge 90 ]; then echo "优秀" elif [ $score -ge 80 ]; then echo "良好" elif [ $score -ge 60 ]; then echo "及格" else echo "不及格" fi
|
5.2 常用判断条件
数值比较:
| 运算符 |
含义 |
-eq |
等于 |
-ne |
不等于 |
-gt |
大于 |
-lt |
小于 |
-ge |
大于等于 |
-le |
小于等于 |
文件判断:
| 条件 |
含义 |
-f file |
是普通文件 |
-d dir |
是目录 |
-e path |
路径存在 |
-r file |
可读 |
-w file |
可写 |
-x file |
可执行 |
-s file |
非空 |
-z "$str" |
字符串为空 |
-n "$str" |
字符串非空 |
"$a" = "$b" |
字符串相等 |
"$a" != "$b" |
字符串不等 |
5.3 逻辑运算符
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| if [ $age -ge 18 ] && [ $age -le 60 ]; then echo "成年人" fi
if [ $age -ge 18 -a $age -le 60 ]; then echo "成年人" fi
if [ "$role" = "admin" ] || [ "$role" = "superadmin" ]; then echo "管理员" fi
if [ ! -f "config.yml" ]; then echo "配置文件不存在" fi
|
5.4 现代写法 [[ ]](推荐)
1 2 3 4 5 6 7 8
| if [[ $name =~ ^J.*y$ ]]; then echo "名称以 J 开头,y 结尾" fi
if [[ -f config.yml && -r config.yml ]]; then echo "配置文件可读" fi
|
5.5 case 语句
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| #!/bin/bash read -p "输入 (start/stop/restart): " action
case $action in start) echo "启动服务..." ;; stop) echo "停止服务..." ;; restart) echo "重启服务..." ;; *) echo "用法: $0 {start|stop|restart}" exit 1 ;; esac
|
六、循环
6.1 for 循环
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| for color in red green blue; do echo "$color" done
files=(*.log) for file in "${files[@]}"; do echo "处理: $file" done
for ((i=0; i<5; i++)); do echo "第 $i 次" done
for user in $(cut -d: -f1 /etc/passwd); do echo "用户: $user" done
|
6.2 while 循环
1 2 3 4 5 6 7 8 9 10 11
| #!/bin/bash count=0 while [ $count -lt 5 ]; do echo $count ((count++)) done
while IFS= read -r line; do echo "行: $line" done < /etc/hosts
|
6.3 break / continue
1 2 3 4 5 6 7 8 9 10 11 12 13
| for i in {1..10}; do if [ $i -eq 5 ]; then break fi echo $i done
for i in {1..10}; do if [ $i -eq 5 ]; then continue fi echo $i done
|
七、函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| #!/bin/bash
greet() { local name=$1 echo "你好, $name!" }
add() { return $(($1 + $2)) }
multiply() { echo $(($1 * $2)) }
greet "July" add 3 5 echo "结果: $?"
result=$(multiply 3 5) echo "3 × 5 = $result"
|
八、文件操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| #!/bin/bash
echo "Hello" > output.txt echo "World" >> output.txt
content=$(<output.txt) while IFS= read -r line; do echo "$line" done < output.txt
if [ -f "output.txt" ]; then echo "文件大小: $(stat -c%s output.txt) 字节" fi
tempfile=$(mktemp) echo "临时数据" > "$tempfile"
rm "$tempfile"
|
九、错误处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| #!/bin/bash set -euo pipefail
error_exit() { echo "错误: $1" >&2 exit 1 }
if ! command -v curl &> /dev/null; then error_exit "curl 未安装" fi
cleanup() { echo "清理临时文件..." rm -f /tmp/myapp-*.tmp } trap cleanup EXIT
command_that_might_fail || true
|
十、实用脚本实战
10.1 自动备份脚本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| #!/bin/bash set -euo pipefail
SOURCE_DIR="/var/www/myapp" BACKUP_DIR="/backup" RETENTION_DAYS=7 DATE=$(date +%Y%m%d_%H%M%S) BACKUP_FILE="${BACKUP_DIR}/backup_${DATE}.tar.gz"
mkdir -p "$BACKUP_DIR"
echo "[$(date)] 开始备份 $SOURCE_DIR" tar -czf "$BACKUP_FILE" -C "$(dirname "$SOURCE_DIR")" "$(basename "$SOURCE_DIR")"
echo "备份完成: $BACKUP_FILE ($(du -h "$BACKUP_FILE" | cut -f1))"
find "$BACKUP_DIR" -name "backup_*.tar.gz" -mtime +$RETENTION_DAYS -delete echo "已清理 ${RETENTION_DAYS} 天前的备份"
|
10.2 服务健康检查
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| #!/bin/bash
SERVICE="nginx" LOG_FILE="/var/log/health_check.log"
log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE" }
if ! systemctl is-active --quiet "$SERVICE"; then log "⚠ $SERVICE 未运行,尝试重启..." systemctl restart "$SERVICE" sleep 3 if systemctl is-active --quiet "$SERVICE"; then log "✅ $SERVICE 重启成功" else log "❌ $SERVICE 重启失败!请检查" fi else log "✅ $SERVICE 运行正常" fi
|
10.3 批量重命名文件
1 2 3 4 5 6 7 8 9 10
| #!/bin/bash
count=1 for file in *.jpg; do [ -f "$file" ] || continue mv "$file" "$(printf '%03d' $count).jpg" echo "$file → $(printf '%03d' $count).jpg" ((count++)) done
|
10.4 日志分析脚本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| #!/bin/bash
LOG_FILE="${1:-/var/log/nginx/access.log}"
if [ ! -f "$LOG_FILE" ]; then echo "日志文件不存在: $LOG_FILE" exit 1 fi
echo "===== Nginx 访问日志分析 =====" echo "日志文件: $LOG_FILE" echo "总请求数: $(wc -l < "$LOG_FILE")" echo ""
echo "===== Top 10 访问 IP =====" awk '{print $1}' "$LOG_FILE" | sort | uniq -c | sort -rn | head -10
echo "" echo "===== 状态码分布 =====" awk '{print $9}' "$LOG_FILE" | sort | uniq -c | sort -rn
|
10.5 项目部署脚本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| #!/bin/bash set -euo pipefail
APP_DIR="/var/www/myapp" GIT_REPO="https://gitee.com/time2017/myapp.git" BRANCH="master"
echo "===== 开始部署 ====="
cd "$APP_DIR"
git checkout "$BRANCH" git pull origin "$BRANCH"
npm ci --production
npm run build
pm2 restart myapp
echo "===== 部署完成 ====="
|
十一、定时任务(Cron)
1 2 3 4 5 6 7 8
| crontab -e
crontab -l
crontab -r
|
Cron 表达式格式:
1 2 3 4 5 6 7
| * * * * * command_to_run │ │ │ │ │ │ │ │ │ └── 星期 (0-7, 0和7都代表周日) │ │ │ └──── 月份 (1-12) │ │ └────── 日期 (1-31) │ └──────── 小时 (0-23) └────────── 分钟 (0-59)
|
示例:
1 2 3 4 5 6 7 8 9 10 11
| 0 2 * * * /home/user/backup.sh >> /var/log/backup.log 2>&1
0 * * * * /home/user/health_check.sh
0 9 * * 1 /home/user/weekly_report.sh
*/5 * * * * /home/user/monitor.sh
|
十二、常用技巧
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| mkdir -p project/{src/{js,css,img},dist,docs}
while [[ $# -gt 0 ]]; do case $1 in -h|--help) echo "帮助信息" exit 0 ;; -n|--name) NAME="$2" shift 2 ;; -v|--verbose) VERBOSE=true shift ;; *) echo "未知参数: $1" exit 1 ;; esac done
RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' NC='\033[0m'
echo -e "${GREEN}成功${NC}: 操作完成" echo -e "${RED}错误${NC}: 操作失败" echo -e "${YELLOW}警告${NC}: 请检查配置"
for i in {1..100}; do printf "\r进度: [%-50s] %d%%" $(printf '=%.0s' $(seq 1 $((i/2)))) $i sleep 0.02 done echo ""
|
调试技巧
1 2 3 4 5 6 7 8 9 10 11 12
| bash -x script.sh
set -x set +x
exec 2>debug.log set -x
|
结语
Shell 脚本的核心价值在于自动化重复劳动。学习路径建议:
- 先写好单个命令,确认正确
- 把命令放进脚本,添加变量
- 加上条件判断和错误处理
- 加入定时任务实现自动运行
不需要一次记住所有语法——把本文当作速查手册,需要时翻开即可。
把重复的事情交给脚本,把时间留给自己。🐚