尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Shell脚本自动化运维实战:从基础语法到生产环境部署

Shell脚本自动化运维实战:从基础语法到生产环境部署 在 Linux 运维工作中手动执行重复性命令不仅效率低下而且极易出错。Shell 脚本作为连接用户与 Linux 内核的桥梁是实现自动化运维最直接、最核心的工具。无论是批量处理日志、监控系统状态、自动化部署还是日常的系统维护一个编写良好的 Shell 脚本都能将运维人员从繁琐的重复劳动中解放出来。本文面向希望从初级运维向中高级进阶的开发者将系统性地讲解 Shell 脚本的实战应用涵盖从基础语法到高级自动化场景并提供大量可直接复用的脚本案例。通过本文你将掌握如何构建健壮、可维护的自动化脚本并理解在生产环境中应用脚本时的关键考量。1. Shell 脚本基础从理解执行环境开始在编写第一个脚本之前必须理解 Shell 脚本的运行环境和工作机制。这能帮助你从根本上避免“命令在终端能执行在脚本里却报错”的典型问题。1.1 Shell 解释器与脚本执行方式Shell 脚本本质上是包含一系列命令的文本文件由特定的 Shell 解释器如 Bash、Zsh逐行读取并执行。脚本的第一行通常是指定解释器的shebang行。#!/bin/bash # 这是一个标准的 shebang 行告诉系统使用 /bin/bash 来执行此脚本脚本的执行方式主要有三种其背后的权限和子 Shell 环境差异显著作为可执行文件运行这是最推荐的方式。首先赋予脚本执行权限然后直接运行。chmod x your_script.sh # 添加执行权限 ./your_script.sh # 在当前目录下执行 /full/path/to/your_script.sh # 使用绝对路径执行这种方式会创建一个新的子 Shell 进程来运行脚本脚本中的环境变量修改通常不会影响父 Shell。使用解释器显式执行无需执行权限直接指定解释器。bash your_script.sh sh your_script.sh这种方式同样创建子 Shell但使用的解释器由命令指定可能与脚本内shebang指定的不同。在当前 Shell 环境中执行Source使用source命令或.符号。source your_script.sh . your_script.sh这种方式不会创建子 Shell脚本中的所有命令都在当前 Shell 环境中执行。这意味着脚本中定义的变量、函数、改变的目录cd都会直接影响当前终端会话。常用于加载环境配置。注意新手常犯的错误是在脚本中使用cd切换目录后发现终端当前目录没变从而认为脚本没生效。这恰恰是因为脚本在子 Shell 中运行目录切换只发生在子进程内。如果希望脚本影响当前环境应使用source命令。1.2 变量脚本的“记忆单元”变量是存储数据的容器。Shell 变量定义简单但作用域和引用规则需要特别注意。定义与使用nameLinux运维 # 定义变量等号两边不能有空格 echo $name # 使用 $ 符号引用变量值 echo ${name} # 使用花括号引用推荐方式边界更清晰环境变量与局部变量局部变量仅在当前 Shell 脚本或进程中有效。环境变量使用export声明可被子进程继承。例如PATH,HOME。export MY_PATH/usr/local/bin # 设置为环境变量 bash -c echo $MY_PATH # 子进程可以访问特殊变量Shell 提供了一系列预定义的特殊变量在脚本中非常有用。变量含义$0当前脚本的文件名。$1,$2, ...$n脚本的参数$1是第一个参数。$#传递给脚本的参数个数。$*所有参数作为一个整体字符串。$所有参数的列表每个参数独立。$?上一个命令的退出状态码0 表示成功非 0 表示失败。$$当前 Shell 进程的进程 ID (PID)。引号的重要性双引号会解析变量和命令替换。nameWorld echo Hello $name # 输出Hello World单引号所有内容原样输出不进行任何解析。echo Hello $name # 输出Hello $name反引号或$()用于命令替换将其中的命令执行结果作为值。current_timedate # 旧式写法 current_time$(date) # 推荐写法支持嵌套 echo Time is $current_time1.3 条件判断与流程控制脚本需要根据不同的情况做出决策这依赖于条件测试。测试命令test与[ ]test命令或其等效的[ ]符号用于评估表达式。注意[后和]前必须有空格。# 检查文件是否存在 if test -f /etc/passwd; then echo File exists. fi # 更常见的写法 if [ -f /etc/passwd ]; then echo File exists. fi常用测试运算符文件测试-e file文件是否存在。-f file是否为普通文件。-d file是否为目录。-r file是否可读。-w file是否可写。-x file是否可执行。字符串比较-z str字符串长度是否为 0。-n str字符串长度是否非 0。str1 str2字符串是否相等。str1 ! str2字符串是否不等。整数比较使用-eq,-ne,-gt,-lt,-ge,-leif [ $count -gt 10 ]; then echo Count is greater than 10. fi逻辑运算符-a(AND),-o(OR),!(NOT)。更推荐使用和||。if-elif-else 结构if [ condition1 ]; then # 命令块1 elif [ condition2 ]; then # 命令块2 else # 命令块3 ficase 语句适用于多分支匹配比多个if-elif更清晰。case $1 in start) echo Starting service... ;; stop) echo Stopping service... ;; restart) echo Restarting service... ;; *) # 默认情况匹配任何其他值 echo Usage: $0 {start|stop|restart} exit 1 ;; esac2. 核心自动化实战文件、文本与系统操作掌握了基础语法后我们可以开始解决实际的运维问题。文件操作、文本处理和系统状态监控是 Shell 脚本最擅长的领域。2.1 批量文件操作实战场景需要将某个目录下所有.log文件备份到backup目录并以日期重命名。#!/bin/bash # 批量备份日志文件脚本 BACKUP_DIR/var/log/backup LOG_DIR/var/log/app DATE_SUFFIX$(date %Y%m%d) # 1. 检查备份目录是否存在不存在则创建 if [ ! -d $BACKUP_DIR ]; then mkdir -p $BACKUP_DIR echo Created backup directory: $BACKUP_DIR fi # 2. 遍历日志目录下的所有 .log 文件 for logfile in $LOG_DIR/*.log; do # 检查通配符是否真的匹配到了文件防止无匹配时循环体仍执行一次 if [ -f $logfile ]; then # 提取文件名不含路径 filename$(basename $logfile) # 构造备份文件名 backup_name${filename%.log}_${DATE_SUFFIX}.log.bak # 执行复制 cp $logfile $BACKUP_DIR/$backup_name echo Backed up: $logfile - $BACKUP_DIR/$backup_name fi done echo Backup completed.关键点解析$(date %Y%m%d)获取当前日期格式如20231027。for ... in ...循环结构$LOG_DIR/*.log会展开为匹配的文件列表。basename去除路径只保留文件名。${filename%.log}参数扩展移除.log后缀。-f判断确保操作对象是普通文件避免目录或特殊文件导致错误。进阶查找并处理文件 使用find命令可以更灵活地定位文件。# 查找 /tmp 下超过 7 天且大于 10M 的 .tmp 文件并删除 find /tmp -name *.tmp -mtime 7 -size 10M -exec rm -f {} \; # 解释 # -name *.tmp: 匹配文件名 # -mtime 7: 修改时间在 7 天以前 # -size 10M: 文件大小大于 10MB # -exec ... \;: 对找到的每个文件执行后面的命令{} 代表文件名2.2 文本处理三剑客grep, sed, awk这是 Shell 脚本文本处理的灵魂工具。grep全局正则表达式搜索用于在文件或输入流中查找匹配模式的行。# 在 nginx 日志中查找包含 404 状态码的行 grep 404 /var/log/nginx/access.log # 递归查找当前目录下所有 .conf 文件中包含 server_name 的行并显示行号 grep -rn server_name --include*.conf . # 使用正则表达式查找 IP 地址 grep -Eo ([0-9]{1,3}\.){3}[0-9]{1,3} access.log # 查找不匹配的行反向选择 grep -v INFO app.log # 输出所有非 INFO 级别的日志sed流编辑器用于对文本进行过滤和转换替换、删除、插入等。# 将文件中的 “foo” 全部替换为 “bar”并直接修改原文件-i sed -i s/foo/bar/g filename.txt # 仅替换每行中第二次出现的 “foo” sed s/foo/bar/2 filename.txt # 删除文件中的空行 sed /^$/d filename.txt # 删除第 10 到 20 行 sed 10,20d filename.txt # 在匹配 “START” 的行后插入一行 “INSERTED LINE” sed /START/a\INSERTED LINE filename.txtawk强大的文本分析工具擅长处理结构化文本如 CSV、日志可以按列操作。# 打印 /etc/passwd 文件的第一列用户名和第三列用户ID awk -F: {print $1, $3} /etc/passwd # -F: 指定冒号为字段分隔符 # $1, $3 代表第一和第三个字段 # 计算文件的总行数 awk END {print NR} filename.txt # NR 是已读的记录数行数 # 对第二列数值求和假设第二列是数字 awk {sum $2} END {print sum} data.txt # 条件处理打印第一列且当第三列大于 1000 时 awk -F: $3 1000 {print $1} /etc/passwd # 使用 BEGIN 块初始化处理表头等 awk BEGIN {FS:; OFS\t; print User\tUID} {print $1, $3} END {print Total:, NR lines} /etc/passwd组合使用示例统计 Nginx 日志中每个 IP 的访问次数并按次数降序排列。awk {print $1} /var/log/nginx/access.log | sort | uniq -c | sort -nr # 分解 # 1. awk {print $1}默认以空格分隔打印第一列通常是 IP。 # 2. sort对 IP 进行排序为 uniq 做准备。 # 3. uniq -c统计并输出每个唯一 IP 的出现次数。 # 4. sort -nr按数字-n逆序-r排序次数最多的排在最前。2.3 系统状态监控与告警脚本一个简单的监控脚本检查磁盘使用率并在超过阈值时发送告警这里以发送邮件为例。#!/bin/bash # 磁盘使用率监控脚本 THRESHOLD80 # 使用率阈值百分比 HOSTNAME$(hostname) ALERT_EMAILadminexample.com # 使用 df 命令获取磁盘使用率排除 tmpfs 等特殊文件系统 df -hP | grep -E ^/dev/ | while read output; do # 提取使用率百分比去掉 % 符号 usep$(echo $output | awk { print $5 } | cut -d% -f1) # 提取分区名 partition$(echo $output | awk { print $6 }) if [ $usep -ge $THRESHOLD ]; then echo Warning: Partition \$partition\ on $HOSTNAME is ${usep}% full. | \ mail -s Disk Space Alert on $HOSTNAME $ALERT_EMAIL # 同时也在本地记录日志 logger -t disk_alert Partition $partition usage is ${usep}%. fi done脚本优化与生产考量日志记录使用logger命令将告警记录到系统日志如/var/log/syslog便于集中查看。避免重复告警可以引入一个“冷却期”机制比如将告警状态写入一个临时文件并检查文件时间避免短时间内频繁发送邮件。监控更多指标可以扩展脚本监控 CPU 负载uptime或cat /proc/loadavg、内存使用free -m、进程数等。使用专业监控工具对于大规模生产环境更推荐使用 Zabbix、Prometheus、Nagios 等专业监控系统本脚本适合作为补充或轻量级场景。3. 函数、信号处理与脚本健壮性要让脚本从“能用”变为“好用”和“可靠”必须关注代码组织和错误处理。3.1 函数模块化与代码复用将重复的代码块封装成函数提高脚本的可读性和可维护性。#!/bin/bash # 函数定义 log_message() { local level$1 local msg$2 local timestamp$(date %Y-%m-%d %H:%M:%S) # 将日志同时输出到标准错误和文件 echo [$timestamp] [$level] $msg 2 echo [$timestamp] [$level] $msg /var/log/my_script.log } # 带返回值的函数 check_service_status() { local service_name$1 # 使用 systemctl 检查服务状态抑制输出 if systemctl is-active --quiet $service_name; then return 0 # 成功服务活跃 else return 1 # 失败服务不活跃 fi } # 主程序逻辑 log_message INFO Script started. # 调用函数并检查返回值 if check_service_status nginx; then log_message INFO Nginx is running. else log_message ERROR Nginx is not running! Attempting to start... systemctl start nginx # 可以再次检查启动是否成功 fi log_message INFO Script finished.关键点local关键字在函数内声明局部变量避免污染全局作用域。函数参数通过$1,$2... 在函数内部访问。返回值使用return返回一个整数0-2550 通常表示成功。调用者通过$?获取。函数调用直接写函数名和参数即可。3.2 错误处理与脚本调试默认情况下Shell 脚本会继续执行即使上一个命令失败。这在生产环境中是危险的。启用严格模式在脚本开头添加以下设置能捕获很多常见错误。#!/bin/bash set -euo pipefail # -e: 任何命令失败返回非零状态则立即退出脚本。 # -u: 遇到未定义的变量时报错并退出。 # -o pipefail: 管道中任何一个命令失败整个管道就视为失败。手动检查命令返回值cp source_file dest_file if [ $? -ne 0 ]; then echo Error: Failed to copy file. 2 exit 1 fi # 更简洁的写法 if ! cp source_file dest_file; then echo Error: Failed to copy file. 2 exit 1 fi使用 trap 处理信号trap命令用于指定在脚本收到特定信号如用户中断CtrlC时执行的清理操作。#!/bin/bash cleanup() { echo Cleaning up temporary files... rm -f /tmp/myscript_temp_* echo Cleanup done. } # 注册信号处理函数在脚本退出EXIT、被中断INT、被终止TERM时执行 cleanup trap cleanup EXIT INT TERM # 创建临时文件 temp_file$(mktemp /tmp/myscript_temp_XXXXXX) echo Working with $temp_file... # ... 一些可能被中断的操作 ... sleep 10脚本调试set -x在运行脚本时打印出每一行实际执行的命令及其参数非常利于追踪逻辑错误。#!/bin/bash set -x # 开启调试 # ... 你的脚本 ... set x # 关闭调试使用bash -x script.sh直接运行脚本并开启调试。在关键位置使用echo或printf打印变量值。3.3 输入验证与安全永远不要信任外部输入。#!/bin/bash # 一个需要输入目录路径并备份的脚本 read -p Enter the directory path to backup: input_dir # 1. 验证输入是否为空 if [ -z $input_dir ]; then echo Error: Directory path cannot be empty. 2 exit 1 fi # 2. 去除可能的前后空格使用变量替换 input_dir$(echo $input_dir | xargs) # xargs 会去除首尾空白 # 3. 验证路径是否存在且是一个目录 if [ ! -d $input_dir ]; then echo Error: $input_dir is not a valid directory. 2 exit 1 fi # 4. 防止路径遍历攻击简单检查是否包含 .. if [[ $input_dir *..* ]]; then echo Error: Path contains .., which is not allowed. 2 exit 1 fi # 5. 使用绝对路径避免后续 cd 等操作出错 target_dir$(realpath $input_dir) echo Backing up directory: $target_dir # ... 后续备份逻辑 ...4. 高级自动化与生产实践将多个脚本和工具串联构建复杂的自动化工作流是运维工程师的核心能力。4.1 计划任务crontab 与 atcrontab用于周期性的任务调度。 编辑当前用户的 crontabcrontab -e格式分 时 日 月 周 命令# 每天凌晨 2 点 30 分执行备份脚本 30 2 * * * /opt/scripts/backup.sh /var/log/backup.log 21 # 每 5 分钟检查一次服务状态 */5 * * * * /opt/scripts/monitor_service.sh # 每周一早上 6 点清理临时文件 0 6 * * 1 find /tmp -type f -mtime 7 -delete注意crontab 中的命令最好使用绝对路径因为它的执行环境 PATH 非常有限。21将标准错误也重定向到日志文件。at用于一次性的定时任务。echo /opt/scripts/cleanup.sh | at 23:00 today # 今晚23点执行 at -l # 列出待执行任务 atrm job_id # 删除任务4.2 与其他工具集成Expect 自动化交互当需要自动化与交互式命令行程序如ssh,ftp,passwd的交互时可以使用expect。#!/usr/bin/expect # 自动 SSH 登录并执行命令 set timeout 30 set host 192.168.1.100 set user root set password your_password spawn ssh $user$host expect { *yes/no* { send yes\r; exp_continue } *password* { send $password\r } } # 登录成功后执行命令 expect *# send df -h\r expect *# send exit\r expect eof警告将密码明文写在脚本中是极不安全的。生产环境中应使用 SSH 密钥认证或从安全的密码管理器中获取凭证。4.3 构建简单的 CI/CD 部署脚本结合 Git、SSH 和 Shell 脚本可以实现简单的自动化部署。#!/bin/bash # deploy.sh - 简易部署脚本 set -euo pipefail REMOTE_USERdeploy REMOTE_HOSTprod-server.example.com REMOTE_PATH/var/www/myapp LOCAL_PROJECT_DIR/home/user/projects/myapp BRANCHmain echo Starting deployment... # 1. 本地构建如果是编译型语言 cd $LOCAL_PROJECT_DIR echo Pulling latest code from $BRANCH... git pull origin $BRANCH echo Running tests... # 假设有测试命令如 npm test 或 pytest # 如果测试失败set -e 会导致脚本退出 echo Building application... # 例如npm run build, mvn package, go build # 2. 准备部署包 TEMP_DIR$(mktemp -d) cp -r build/* $TEMP_DIR/ # 假设构建输出在 build/ 目录 # 可以在这里添加版本号文件等 # 3. 传输到远程服务器 echo Uploading to $REMOTE_HOST... rsync -avz --delete -e ssh $TEMP_DIR/ $REMOTE_USER$REMOTE_HOST:$REMOTE_PATH/ # 4. 远程执行部署后任务 echo Running remote tasks... ssh $REMOTE_USER$REMOTE_HOST set -e cd $REMOTE_PATH # 例如重启服务、迁移数据库、清理缓存 sudo systemctl restart myapp.service # 检查服务状态 if sudo systemctl is-active --quiet myapp.service; then echo Service restarted successfully. else echo ERROR: Service failed to start! 2 exit 1 fi # 5. 清理本地临时文件 rm -rf $TEMP_DIR echo Deployment completed successfully.4.4 生产环境脚本编写最佳实践清单启用严格模式脚本开头务必加上set -euo pipefail。使用绝对路径在脚本中特别是crontab中对所有命令、文件、目录使用绝对路径。记录详细的日志脚本应有日志功能记录开始、结束、关键步骤和错误。日志应包含时间戳和日志级别。输入验证与清理对所有外部输入参数、用户输入、文件内容进行验证、清理和转义。错误处理与退出码脚本应在失败时返回非零的退出码方便上游调用者判断。使用配置文件将可配置项如路径、阈值、地址提取到单独的配置文件中如.env或.conf便于管理。添加脚本说明在文件头部用注释说明脚本用途、作者、参数、示例和注意事项。进行代码审查和测试即使是脚本也应进行基本的测试包括正常流程和异常情况。权限最小化遵循最小权限原则不要用 root 身份运行所有脚本。使用sudo仅对需要特权的命令授权。版本控制将脚本纳入 Git 等版本控制系统进行管理。从简单的文件备份到复杂的自动化部署Shell 脚本的能力边界远超许多人的想象。其核心价值在于将零散的命令和逻辑固化、复用和自动化。掌握 Shell 脚本意味着你掌握了在 Linux 世界里“创造工具”的能力。下一步可以深入研究更复杂的文本处理场景如多文件关联分析、进程管理与并发控制如使用、wait、xargs -P以及如何将 Shell 脚本更好地集成到 Ansible、Jenkins 等更大型的自动化运维平台中从而构建起完整、高效的运维自动化体系。
返回列表