Shell 脚本编程
大约 8 分钟约 2383 字
Shell 脚本编程
简介
Shell 脚本是 Linux 系统管理和自动化运维的核心工具。通过编写 Shell 脚本,可以批量执行命令、定时任务、系统监控、自动部署等。Bash 是最常用的 Shell,掌握其语法和常用技巧是后端开发者的基本技能。
特点
基本语法
脚本结构
#!/bin/bash
# 第一行指定解释器
# 设置安全选项
set -e # 命令失败时退出
set -u # 使用未定义变量时退出
set -o pipefail # 管道中任何命令失败时退出
# 变量
APP_NAME="myapp"
APP_PORT=8080
APP_DIR="/opt/myapp"
# 当前时间
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
echo "部署 ${APP_NAME} 到端口 ${APP_PORT}"变量与字符串
#!/bin/bash
# 变量赋值(等号两边不能有空格)
name="world"
count=10
# 使用变量
echo "Hello, ${name}!"
echo "Count: ${count}"
# 字符串操作
str="Hello World"
echo "${#str}" # 字符串长度:11
echo "${str:0:5}" # 截取:Hello
echo "${str/World/Bash}" # 替换:Hello Bash
echo "${str,,}" # 转小写
echo "${str^^}" # 转大写
# 默认值
echo "${undefined:-default}" # 未定义时使用默认值
echo "${port:=8080}" # 未定义时赋值并使用
# 命令替换
current_dir=$(pwd)
file_count=$(ls | wc -l)
today=$(date +%Y-%m-%d)条件判断
#!/bin/bash
# if-elif-else
score=85
if [ ${score} -ge 90 ]; then
echo "优秀"
elif [ ${score} -ge 80 ]; then
echo "良好"
elif [ ${score} -ge 60 ]; then
echo "及格"
else
echo "不及格"
fi
# 文件判断
file="/etc/passwd"
if [ -f "${file}" ]; then # 文件存在且是普通文件
echo "${file} 存在"
fi
if [ -d "/var/log" ]; then # 目录存在
echo "/var/log 目录存在"
fi
# 字符串判断
str="hello"
if [ -z "${str}" ]; then # 字符串为空
echo "空字符串"
fi
if [ "${str}" == "hello" ]; then # 字符串相等
echo "匹配"
fi
# 常用判断
# -eq, -ne, -gt, -ge, -lt, -le 数值比较
# -f, -d, -e, -r, -w, -x 文件判断
# -z, -n 字符串空/非空
# ==, != 字符串比较循环
#!/bin/bash
# for 循环
for i in 1 2 3 4 5; do
echo "Number: ${i}"
done
# C 风格 for
for ((i=0; i<10; i++)); do
echo "Count: ${i}"
done
# 遍历文件
for file in /var/log/*.log; do
echo "处理:${file}"
wc -l "${file}"
done
# while 循环
count=0
while [ ${count} -lt 10 ]; do
echo "Count: ${count}"
((count++))
done
# 读取文件逐行
while IFS= read -r line; do
echo "Line: ${line}"
done < input.txt
# until 循环(条件为假时执行)
retry=0
until curl -s http://localhost:8080/health > /dev/null 2>&1; do
((retry++))
echo "等待服务启动... 第 ${retry} 次"
sleep 2
if [ ${retry} -ge 30 ]; then
echo "服务启动超时"
exit 1
fi
done
echo "服务已就绪"函数
#!/bin/bash
# 定义函数
log() {
local level=$1
local message=$2
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
echo "[${timestamp}] [${level}] ${message}"
}
# 带返回值的函数
check_port() {
local port=$1
if ss -tlnp | grep -q ":${port} "; then
return 0 # 端口被占用
else
return 1 # 端口空闲
fi
}
# 使用函数
log "INFO" "开始部署"
if check_port 8080; then
log "WARN" "端口 8080 已被占用"
else
log "INFO" "端口 8080 可用"
fi
# 部署函数
deploy() {
local app_name=$1
local app_dir="/opt/${app_name}"
log "INFO" "部署 ${app_name}"
# 拉取代码
cd "${app_dir}" || { log "ERROR" "目录不存在:${app_dir}"; return 1; }
git pull origin main
# 构建和发布
dotnet publish -c Release -o /tmp/${app_name}
# 重启服务
sudo systemctl restart ${app_name}
# 健康检查
sleep 5
if curl -sf http://localhost:8080/health > /dev/null; then
log "INFO" "${app_name} 部署成功"
else
log "ERROR" "${app_name} 部署失败"
return 1
fi
}
# 批量部署
for app in api-service web-service worker-service; do
deploy "${app}" || log "ERROR" "${app} 部署失败"
done实用脚本
系统监控
#!/bin/bash
# system_monitor.sh — 系统资源监控
# CPU 使用率
cpu_usage() {
local usage=$(top -bn1 | grep "Cpu(s)" | awk '{print $2}')
echo "CPU: ${usage}%"
}
# 内存使用
memory_usage() {
local total=$(free -h | awk '/Mem:/{print $2}')
local used=$(free -h | awk '/Mem:/{print $3}')
local percent=$(free | awk '/Mem:/{printf("%.1f"), $3/$2*100}')
echo "内存: ${used}/${total} (${percent}%)"
}
# 磁盘使用
disk_usage() {
echo "磁盘使用:"
df -h | grep -E "^/dev|Filesystem"
}
# 高 CPU 进程
top_processes() {
echo "Top 5 CPU 进程:"
ps aux --sort=-%cpu | head -6
}
# 网络连接
network_stats() {
echo "网络连接数:"
ss -s | head -3
}
echo "===== 系统监控报告 $(date) ====="
echo ""
cpu_usage
memory_usage
echo ""
disk_usage
echo ""
top_processes
echo ""
network_stats自动备份
#!/bin/bash
# backup.sh — 数据库自动备份
set -euo pipefail
# 配置
DB_HOST="localhost"
DB_PORT="1433"
DB_USER="sa"
DB_PASS="${DB_PASSWORD}" # 从环境变量读取
BACKUP_DIR="/data/backup"
KEEP_DAYS=30
# 数据库列表
DATABASES=("MyApp" "MyApp_Log" "MyApp_Config")
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1"
}
# 创建备份目录
mkdir -p "${BACKUP_DIR}"
# 备份每个数据库
for db in "${DATABASES[@]}"; do
timestamp=$(date +%Y%m%d_%H%M%S)
backup_file="${BACKUP_DIR}/${db}_${timestamp}.bak"
log "备份 ${db}..."
# SQL Server 备份
/opt/mssql-tools/bin/sqlcmd -S "${DB_HOST},${DB_PORT}" \
-U "${DB_USER}" -P "${DB_PASS}" \
-Q "BACKUP DATABASE [${db}] TO DISK='${backup_file}' WITH FORMAT, COMPRESSION"
if [ $? -eq 0 ]; then
log "${db} 备份成功:${backup_file}"
else
log "${db} 备份失败!"
continue
fi
# 压缩
gzip "${backup_file}"
done
# 清理过期备份
log "清理 ${KEEP_DAYS} 天前的备份..."
find "${BACKUP_DIR}" -name "*.bak.gz" -mtime +${KEEP_DAYS} -delete
log "备份完成".NET 应用部署
#!/bin/bash
# deploy_dotnet.sh — .NET 应用自动部署
set -euo pipefail
APP_NAME="myapp"
APP_DIR="/opt/${APP_NAME}"
REPO_URL="https://github.com/user/myapp.git"
BRANCH="main"
BACKUP_DIR="/data/backup/${APP_NAME}"
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] [$1] $2"
}
# 前置检查
preflight() {
log "INFO" "前置检查..."
command -v dotnet >/dev/null 2>&1 || { log "ERROR" "未安装 .NET SDK"; exit 1; }
if ! systemctl is-active --quiet "${APP_NAME}"; then
log "WARN" "服务未运行"
fi
}
# 备份当前版本
backup_current() {
local timestamp=$(date +%Y%m%d_%H%M%S)
local backup_path="${BACKUP_DIR}/${timestamp}"
mkdir -p "${backup_path}"
cp -r "${APP_DIR}/publish" "${backup_path}/" 2>/dev/null || true
log "INFO" "已备份到 ${backup_path}"
}
# 拉取代码
pull_code() {
log "INFO" "拉取代码..."
if [ -d "${APP_DIR}/.git" ]; then
cd "${APP_DIR}"
git fetch origin
git reset --hard "origin/${BRANCH}"
else
git clone -b "${BRANCH}" "${REPO_URL}" "${APP_DIR}"
cd "${APP_DIR}"
fi
local commit=$(git rev-parse --short HEAD)
log "INFO" "当前版本:${commit}"
}
# 构建
build() {
log "INFO" "开始构建..."
dotnet publish src/${APP_NAME} \
-c Release \
-o "${APP_DIR}/publish" \
--no-restore
log "INFO" "构建完成"
}
# 部署
deploy() {
log "INFO" "重启服务..."
sudo systemctl restart "${APP_NAME}"
# 等待健康检查
local retry=0
while [ ${retry} -lt 30 ]; do
if curl -sf http://localhost:8080/health > /dev/null; then
log "INFO" "部署成功!"
return 0
fi
((retry++))
sleep 2
done
log "ERROR" "部署失败,回滚..."
rollback
exit 1
}
# 回滚
rollback() {
local latest=$(ls -t "${BACKUP_DIR}" | head -1)
if [ -n "${latest}" ]; then
cp -r "${BACKUP_DIR}/${latest}/publish" "${APP_DIR}/"
sudo systemctl restart "${APP_NAME}"
log "INFO" "已回滚到 ${latest}"
fi
}
# 主流程
main() {
log "INFO" "===== 开始部署 ${APP_NAME} ====="
preflight
backup_current
pull_code
build
deploy
log "INFO" "===== 部署完成 ====="
}
main "$@"调试技巧
# 调试模式
bash -x script.sh # 打印每条执行的命令
bash -n script.sh # 只检查语法,不执行
# 在脚本中开启调试
set -x # 开启
set +x # 关闭
# 捕获错误
trap 'echo "错误发生在第 $LINENO 行"; exit 1' ERR优点
缺点
总结
Shell 脚本是 Linux 运维的基本功。掌握变量、条件、循环、函数四大语法,配合系统命令组合,就能完成大部分自动化任务。复杂场景(多服务器编排、复杂数据处理)建议使用 Python 或 Ansible。核心原则:set -euo pipefail,函数化组织,日志记录,错误处理。
关键知识点
- DevOps 主题的核心是让交付更快、更稳、更可审计。
- 自动化不是把命令脚本化,而是把失败、回滚、权限和观测一起设计进去。
- 生产链路必须明确制品、环境、凭据、配置和责任边界。
项目落地视角
- 把流水线拆成构建、测试、制品、部署、验证和回滚几个阶段。
- 为关键步骤补齐日志、指标、通知和人工兜底点。
- 定期演练扩容、回滚、故障注入和灾备切换。
常见误区
- 只关注部署成功,不关注失败恢复和审计追踪。
- 把环境差异藏在临时脚本或人工操作里。
- 上线频率高了以后,没有标准化制品和配置管理。
进阶路线
- 继续补齐 GitOps、可观测性、平台工程和成本治理。
- 把主题和应用架构、安全、权限、备份恢复联动起来理解。
- 形成团队级平台能力,而不是每个项目重复造轮子。
适用场景
- 当你准备把《Shell 脚本编程》真正落到项目里时,最适合先在一个独立模块或最小样例里验证关键路径。
- 适合构建自动化交付、基础设施治理、监控告警和生产发布体系。
- 当团队规模扩大、发布频率提升或环境变多时,这类主题会显著影响交付效率。
落地建议
- 所有自动化流程尽量做到幂等、可审计、可回滚。
- 把制品、变量、凭据和执行权限分层管理。
- 定期演练扩容、回滚、密钥轮换和灾备恢复。
排错清单
- 先定位失败发生在代码、构建、制品、环境还是权限层。
- 检查流水线变量、凭据、镜像标签和目标环境配置是否一致。
- 如果问题偶发,重点看并发发布、资源争抢和外部依赖抖动。
复盘问题
- 如果把《Shell 脚本编程》放进你的当前项目,最先要验证的输入、输出和失败路径分别是什么?
- 《Shell 脚本编程》最容易在什么规模、什么边界条件下暴露问题?你会用什么指标或日志去确认?
- 相比默认实现或替代方案,采用《Shell 脚本编程》最大的收益和代价分别是什么?
