ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

Shell脚本实战:从变量循环到三剑客,搞定Linux自动化运维

Shell脚本实战:从变量循环到三剑客,搞定Linux自动化运维 这次我们来看一套 Shell 脚本编程实战内容主题是变量、循环、函数和文本三剑客。网上讲 Shell 的教程很多但不少是零散命令的拼凑拿到运维场景里根本连不成一条能跑的链路。这套内容的价值在于它把编写 Linux 自动化任务最常用的语法和工具串起来了不是“看懂语法”就结束而是能直接写出批量处理、日志分析、服务巡检一类的脚本。先交代清楚目标读者。如果你正在做运维、云计算运维、桌面运维或者刚入门 Linux 想要一条更高效的学习路径这篇文章可以直接收藏。正文会从环境准备开始逐步讲变量、条件判断、循环、函数再重点拆解 grep、sed、awk 这三个文本处理工具最后落到批量任务、日志清理、定时任务、API 调用这类真实场景。看完之后你至少能独立写出一个带参数校验、带日志输出、能定时执行的脚本。文章不会停留在“列命令”这个层面而是会给出完整示例、执行思路和常见错误。每段代码都标注了语言直接复制到你的 Linux 环境就能跑。涉及生产环境的部分会明确给出使用边界避免把一条没验证的脚本丢到线上机器里造成事故。1. 核心能力速览能力项说明适用平台Linux / Unix 环境主流发行版均可编程语言Bash / Shell核心内容变量、条件判断、循环、函数、grep/sed/awk 三剑客启动方式Bash 解释器直接执行无需安装额外框架硬件要求无 GPU 需求纯 CPU 环境即可是否支持批量任务支持适合批量文件处理、日志分析、服务巡检、备份清理是否支持 API 接入可通过 curl 等命令调用 HTTP 接口是否支持定时调度支持配合 cron 定时任务学习成本较低掌握基础命令后即可上手适合场景Linux 运维、自动化测试、日志分析、云计算运维、桌面运维从应用场景看Shell 脚本是 Linux 环境下最直接的自动化手段。不需要安装庞大的运行环境只要系统里有 Bash就能写、能跑、能接到 cron 里做周期任务。2. 适用场景与使用边界先说适合做什么。日常运维里有大量重复操作比如检查多台服务器的存活状态、清理过期日志、批量重命名文件、提取日志里的 IP 和状态码、统计接口响应时间这些都可以用 Shell 脚本快速完成。Shell 的哲学是“把命令串起来”所以它特别适合处理文本、文件和进程类任务。再说哪些场景不建议用 Shell。如果遇到非常复杂的 JSON 数据处理、多线程并发计算、图形界面程序、大规模分布式任务调度更稳妥的选择是 Python、Go 或专门的编排工具。Shell 脚本一旦写得过于复杂调试成本会明显上升不如直接用更现代的语言维护。使用边界要特别注意三件事第一不要在未测试的情况下直接在生产环境运行脚本。Shell 脚本里很多命令是不可逆的例如rm -rf、重定向覆盖文件、批量执行远程命令先在小范围或测试机上验证。第二涉及批量远程操作、端口扫描、数据抓取时确保你有目标主机或系统的合法授权。未授权的扫描、爆破、批量探测可能违反安全合规要求。第三不要在脚本里硬编码明文密码、密钥或敏感数据。如果必须调用凭据优先使用环境变量、加密配置或密钥管理工具。日志输出也要避免包含敏感信息。Shell 脚本是运维利器但越强的工具越要控制使用边界。写脚本之前先想清楚这个任务是否适合 Shell、会不会影响现有服务、失败后如何回滚。3. 环境准备与前置条件Shell 脚本开发对硬件要求非常低你只需要一台安装了 Linux 的机器或虚拟机即使是云服务器、树莓派、Windows WSL 也都可以。现在常见的 Linux 发行版如 Ubuntu、Debian、CentOS、Rocky Linux 都自带 Bash。先确认当前环境的 Shell 类型和版本echo $SHELL bash --version正常情况下输出里会显示/bin/bash或/usr/bin/bash以及 Bash 的版本号。如果系统中同时存在 zsh、sh 或其他 Shell本文的脚本统一按照 Bash 语法编写。开发 Shell 脚本不需要专门的 IDE但推荐使用支持语法高亮的编辑器比如 vim、nano、VS Code。如果是远程服务器直接用 vim 编辑最方便。检查系统里是否具备常用命令which grep sed awk curl tar crontab这些命令在大多数发行版中都是默认安装的。如果提示缺少某个命令可以根据系统包管理器安装。例如# Ubuntu / Debian sudo apt update sudo apt install -y grep sed gawk curl cron # CentOS / Rocky / RHEL sudo yum install -y grep sed gawk curl cronie创建一个专门存放脚本的目录避免散落在用户目录或系统目录mkdir -p ~/shell-practice cd ~/shell-practice这个目录就是我们的实验区。后续创建的脚本都放在这里保持文件整洁也方便测试权限和路径。第一个脚本很简单验证环境是否可用#!/bin/bash echo Hello, Shell echo 当前 Shell: $SHELL echo 当前用户: $(whoami)写入文件后需要通过chmod添加执行权限再运行chmod x ~/shell-practice/first.sh ~/shell-practice/first.sh看到三行输出说明环境完全正常。4. Shell 变量与基础语法4.1 变量定义与引用Shell 变量不需要声明类型赋值时注意等号两边不能有空格#!/bin/bash namealice age26 echo 姓名: $name echo 年龄: ${age}岁变量名由字母、数字、下划线组成不能以数字开头。使用$变量名或${变量名}引用变量推荐使用花括号写法便于在拼接字符串时明确边界。Shell 还支持只读变量#!/bin/bash readonly VERSION1.0.0 VERSION2.0.0 # 这里会报错4.2 特殊变量脚本执行时系统会传入一些特殊变量它们在自动化脚本中非常常用特殊变量含义$0当前脚本的文件名$1、$2第 1、2 个位置参数$#参数个数$所有参数列表$?上一条命令的退出码0 表示成功$$当前进程 PID写一个脚本演示#!/bin/bash echo 脚本名称: $0 echo 第一个参数: $1 echo 第二个参数: $2 echo 参数数量: $# echo 所有参数: $ echo 上一条命令退出码: $?执行测试bash args.sh hello world输出会依次显示脚本名、传入的两个参数、参数数量和退出码。这类特殊变量在写带参脚本时是基础中的基础。4.3 字符串处理字符串拼接和截取是脚本开发中最常见的操作之一#!/bin/bash strhello, linux shell echo 长度: ${#str} echo 截取前5个字符: ${str:0:5} echo 截取从第7个字符开始: ${str:7} echo 替换第一个 linux 为 Linux: ${str/linux/Linux} echo 替换所有 l 为 L: ${str//l/L}4.4 算术运算Bash 内置的算术运算使用$(( ))不要直接写1 1让它计算那会被当作字符串#!/bin/bash a10 b3 echo 加法: $((a b)) echo 减法: $((a - b)) echo 乘法: $((a * b)) echo 除法取整: $((a / b)) echo 取余: $((a % b))如果脚本里有比较复杂的数学逻辑也可以借助awk或 Python但简单的运算用$(( ))就够。4.5 用户输入与交互使用read命令读取用户输入适合写成半交互式的运维脚本#!/bin/bash read -p 请输入要检查的服务名: service_name echo 你输入的是: $service_name如果要实现静默输入比如输入密码可以加-s参数read -s -p 请输入密码: password echo echo 密码长度: ${#password}4.6 命令替换命令替换是把命令的输入结果保存到变量里这是 Shell 自动化的核心能力之一#!/bin/bash current_date$(date %Y-%m-%d) current_dir$(pwd) echo 今天日期: $current_date echo 当前目录: $current_dir旧式写法使用反引号不推荐。$( )更清晰且支持嵌套。例如hours$(($(date %H) 0)) echo 当前小时: $hours5. 条件判断与流程控制5.1 if / elif / elseShell 的条件判断语法要注意方括号前后的空格少了空格会直接报错#!/bin/bash read -p 请输入一个数字: num if [ $num -gt 10 ]; then echo 这个数大于 10 elif [ $num -eq 10 ]; then echo 这个数等于 10 else echo 这个数小于 10 fi数字比较用以下运算符-eq等于、-ne不等于、-gt大于、-lt小于、-ge大于等于、-le小于等于。字符串比较使用或!#!/bin/bash str1hello str2world if [ $str1 $str2 ]; then echo 两个字符串相等 else echo 两个字符串不相等 fi if [ -z $str1 ]; then echo 字符串为空 fi5.2 文件判断Linux 运维脚本里大量使用文件判断例如检查配置文件是否存在、目录是否可写、日志文件是否为空表达式含义-f $file是否为普通文件-d $dir是否为目录-e $path路径是否存在-s $file文件是否非空-r $file是否可读-w $file是否可写-x $file是否可执行#!/bin/bash path/etc/nginx/nginx.conf if [ -f $path ]; then echo 文件存在 else echo 文件不存在 fi if [ -r $path ]; then echo 文件可读 fi5.3 逻辑运算符组合多个条件时用表示与、||表示或、!表示非#!/bin/bash age25 citybeijing if [[ $age -ge 18 $city beijing ]]; then echo 条件满足 fi这里使用了[[ ]]它相比[ ]支持更丰富的语法比如正则匹配推荐在 Bash 脚本中优先使用#!/bin/bash urlhttps://example.com/api/v1 if [[ $url ~ ^https:// ]]; then echo 以 https 开头 fi5.4 case 多分支case 适合处理有限分支场景比如根据用户传入参数选择执行不同逻辑#!/bin/bash action$1 case $action in start) echo 启动服务 ;; stop) echo 停止服务 ;; restart) echo 重启服务 ;; *) echo 用法: $0 {start|stop|restart} exit 1 ;; esaccase 的写法比一串 if/elif 更清晰尤其是在写服务管理脚本时非常有效。5.5 退出码与安全保护Shell 脚本里exit可以主动退出并返回状态码。后面配合$?可以判断脚本是否成功#!/bin/bash if [ ! -d /var/log/myapp ]; then echo 目录不存在创建中 mkdir -p /var/log/myapp if [ $? -ne 0 ]; then echo 创建目录失败 exit 1 fi fi echo 目录准备完成另外在脚本开头加一句set -e可以让脚本在任何一条命令出错时立即退出避免错误被忽略set -u可以检查未定义变量set -o pipefail可以防止管道中错误被吞掉。推荐在正式脚本中这样写#!/bin/bash set -euo pipefail这行配置相当于给脚本加了一层保护很多“诡异问题”都能在第一时间暴露出来。6. 循环结构与批量任务6.1 for 循环for 循环可以遍历数字列表、文件列表、命令行参数等。最常见的用法是遍历指定区间#!/bin/bash for i in {1..5}; do echo 第 $i 次循环 done遍历文件列表是运维常用场景#!/bin/bash for file in /tmp/*.log; do echo 处理文件: $file done批量重命名文件例如把所有.txt后缀改成.bak#!/bin/bash for file in *.txt; do mv $file ${file%.txt}.bak echo 重命名: $file - ${file%.txt}.bak done这里使用了变量替换${file%.txt}作用是把后缀.txt去掉。如果文件名中包含空格必须加引号。6.2 while 循环while 循环适合在满足条件时反复执行。最典型的场景是逐行读取文件#!/bin/bash while read -r line; do echo 读取到: $line done /etc/hostname也可以用 while 做计数循环#!/bin/bash count1 while [ $count -le 5 ]; do echo count$count count$((count 1)) done6.3 无限循环与后台运行一些巡检或监控类脚本需要持续运行可以使用无限循环配合sleep控制频率#!/bin/bash while true; do echo $(date %F\ %T) 监控中内存使用率: $(free -h | awk /^Mem/{print $3}) sleep 5 done这种脚本不要直接在前台跑可以用nohup放到后台运行把输出写入日志nohup bash monitor.sh monitor.log 21 6.4 循环控制 break / continuebreak 跳出整个循环continue 跳过当前循环剩余部分进入下一轮#!/bin/bash for i in {1..10}; do if [ $i -eq 3 ]; then echo 跳过数字 3 continue fi if [ $i -eq 7 ]; then echo 到达 7退出循环 break fi echo 当前数字: $i done6.5 循环内使用后台并发如果有大量批量任务顺序执行太慢可以在循环内用后台执行再用wait等待所有任务结束#!/bin/bash for ip in 192.168.1.1 192.168.1.2 192.168.1.3; do ( ping -c 1 -W 1 $ip /dev/null 21 echo $ip 存活 || echo $ip 不通 ) done wait echo 所有检查完成这种并发写法会同时发起多个 ping相比逐条执行能明显减少总耗时。但要注意并发数量不要太多否则会拖慢系统生产环境建议限制并发数。6.6 循环内批量检查服务端口批量检查多台服务器端口是否开放结合nc或/dev/tcp实现#!/bin/bash hosts(192.168.1.10 192.168.1.11) port22 for host in ${hosts[]}; do timeout 3 bash -c echo /dev/tcp/$host/$port 2/dev/null if [ $? -eq 0 ]; then echo $host:$port 端口正常 else echo $host:$port 端口不可达 fi done7. 函数封装与脚本结构7.1 函数定义与调用当脚本里有多处重复逻辑时应该封装成函数。函数需要在调用之前定义#!/bin/bash log_info() { echo [$(date %Y-%m-%d %H:%M:%S)] INFO: $1 } log_error() { echo [$(date %Y-%m-%d %H:%M:%S)] ERROR: $1 } log_info 开始执行备份 log_error 备份失败7.2 函数参数与返回值函数内部的$1、$2是函数参数不是脚本参数。返回值用return返回 0 到 255 的整数#!/bin/bash check_service() { local service_name$1 systemctl is-active $service_name /dev/null 21 if [ $? -eq 0 ]; then echo active return 0 else echo inactive return 1 fi } status$(check_service nginx) echo nginx 状态: $status如果想返回一个字符串直接用 echo 输出用变量接收比 return 更灵活。7.3 局部变量 local函数内部定义的变量默认是全局的容易污染外部环境。使用local声明局部变量是更安全的做法#!/bin/bash my_func() { local temp内部值 echo 函数内: $temp } my_func echo 函数外访问 temp: $temp上面代码最后一行会输出空字符串因为temp只在函数内有效。7.4 脚本通用结构一个正式脚本应该有清晰的阶段划分。下面是一个带参数校验、日志、主函数调用的结构模板#!/bin/bash set -euo pipefail # 常量定义 SCRIPT_DIR$(cd $(dirname $0) pwd) LOG_FILE$SCRIPT_DIR/script.log # 日志函数 log() { echo [$(date %Y-%m-%d %H:%M:%S)] $* | tee -a $LOG_FILE } # 参数校验 if [ $# -lt 1 ]; then log 参数不足用法: $0 target exit 1 fi # 核心处理函数 process() { local target$1 log 开始处理 $target # 实际业务逻辑 } # 主函数入口 main() { process $1 log 全部完成 } main $1这种结构看起来很“标准”但在真实运维里很实用。日志、参数校验、退出码控制都在最前面后面改业务逻辑时不用调整外围结构。8. 三剑客实战grep、sed、awk文本三剑客是 Linux 运维处理日志和配置文件的看家工具。单独掌握每个命令不难难的是把它们串到脚本流程里。8.1 grep文本过滤grep 用来在文件或标准输出中匹配文本行。最常用参数参数含义-E支持扩展正则表达式-i忽略大小写-v反向匹配-c统计匹配行数-n显示行号-r递归搜索目录示例# 在 nginx 访问日志中搜索 500 状态码 grep -n 500 /var/log/nginx/access.log # 统计错误日志行数 grep -c ERROR /var/log/app/error.log # 排除注释行和空行查看配置文件有效内容 grep -vE ^\s*#|^\s*$ /etc/nginx/nginx.conf在脚本里grep 经常配合条件判断使用#!/bin/bash if grep -q listen 443 /etc/nginx/nginx.conf; then echo 已配置 HTTPS 监听 else echo 未配置 HTTPS 监听 fi-q参数表示静默模式不输出内容只影响退出码适合在 if 条件中使用。8.2 sed流式编辑sed 可以对文本进行增删改查不需要用 vim 打开文件非常适合脚本内自动修改配置。替换字符串是最常用的操作# 把文件中的 old 替换为 new sed s/old/new/ file.txt # 全局替换并直接写回文件 sed -i s/old/new/g file.txt # 在每行末尾追加内容 sed -i s/$/ 追加内容/ file.txt # 删除匹配行 sed -i /^#/d file.txtsed 里最常见的坑是-i的兼容性。GNU sed 写sed -i没问题macOS 自带 BSD sed 需要加扩展名否则会提示undeclared identifier。在脚本里批量修改配置文件非常实用。例如把所有配置项的旧 IP 换成新 IP#!/bin/bash config_file/etc/myapp/config.ini sed -i s/192.168.1.100/10.0.0.100/g $config_file执行前一定要先备份或者先不带-i输出预览sed s/192.168.1.100/10.0.0.100/g $config_file | head -508.3 awk文本分析与列处理awk 是一个小型的文本处理语言最擅长按列处理数据。默认按照空格或制表符切分每一行$1表示第一列$NF表示最后一列NF表示列数NR表示行号。先看一个最简单的例子# 打印 /etc/passwd 中的用户名和默认 Shell awk -F: {print $1, $NF} /etc/passwd-F:指定冒号为分隔符因为 passwd 文件是用冒号分隔的。实际运维中awk 最常用于分析访问日志。比如统计 nginx 日志中访问量前 10 的 IPawk {print $1} /var/log/nginx/access.log | sort | uniq -c | sort -rn | head -10这里不只是 awk还串联了sort、uniq、head这就是 Shell 自动化的典型写法用管道把多个工具组合起来完成一个分析任务。统计 HTTP 状态码分布awk {print $9} /var/log/nginx/access.log | sort | uniq -c计算请求耗时总和和平均值awk {sum $NF} END {print 请求数, NR, 总耗时, sum, 平均耗时, sum/NR} /var/log/nginx/access.logawk 在脚本中还被用来提取进程 PID、磁盘使用率等指标#!/bin/bash disk_usage$(df / | awk NR2{print $5} | sed s/%//) echo 根分区使用率: $disk_usage% if [ $disk_usage -gt 85 ]; then echo 磁盘使用率过高需要清理 fi8.4 三剑客组合实战日志分析把三剑客组合起来可以完成一条完整的日志分析链路。下面是统计今天的 ERROR 日志按模块分类数量的脚本#!/bin/bash today$(date %Y-%m-%d) log_file/var/log/myapp/error_$today.log if [ ! -f $log_file ]; then echo 日志文件不存在: $log_file exit 1 fi # 提取 ERROR 行去掉时间戳前缀按模块分类统计 grep ERROR $log_file \ | sed s/^\[.*\] \[\(.*\)\] .*/\1/ \ | sort \ | uniq -c \ | sort -rn这条命令依次完成过滤错误日志、提取模块名、排序、统计数量、按数量倒序排列。9. 自动化任务实战9.1 清理由脚本自动归档后的旧文件很多服务会产生大量临时文件需要定期清理超过 7 天的文件#!/bin/bash # 清理 /var/log/myapp/ 下超过 7 天的 .log 文件 find /var/log/myapp/ -name *.log -mtime 7 -exec ls -lh {} \;find配合mtime可以找出指定时间之前修改过的文件。先把要删的文件列出来确认无误后再加-deletefind /var/log/myapp/ -name *.log -mtime 7 -delete不要直接在生产环境执行带-delete的命令。第一次运行先使用ls -lh检查列表确认范围准确后再清理。9.2 每周备份数据库定期备份是一个典型需求。下面脚本将 MySQL 数据库导出为 SQL 文件并压缩保存#!/bin/bash set -euo pipefail BACKUP_DIR/data/backup/mysql DB_NAMEmyapp DB_USERbackup_user DB_PASS${MYSQL_PASSWORD} mkdir -p $BACKUP_DIR backup_file$BACKUP_DIR/${DB_NAME}_$(date %Y%m%d_%H%M%S).sql.gz mysqldump -u$DB_USER -p$DB_PASS $DB_NAME | gzip $backup_file echo 备份完成: $backup_file这里密码从环境变量读取而不是硬编码在脚本里更符合安全规范。9.3 服务健康检查结合 curl 检查 HTTP 服务是否正常失败时重启并发送通知#!/bin/bash set -euo pipefail urlhttp://127.0.0.1:8080/health service_namemyapp http_code$(curl -s -o /dev/null -w %{http_code} --max-time 5 $url || echo 000) if [ $http_code 200 ]; then echo [$(date %F %T)] $service_name 状态正常 else echo [$(date %F %T)] $service_name 异常HTTP 状态码: $http_code systemctl restart $service_name echo 已尝试重启 fi9.4 定时任务 crontab脚本写完以后可以交给 cron 定期执行。先编辑当前用户的 crontabcrontab -e添加以下几类配置# 每天凌晨 3 点执行备份脚本 0 3 * * * /home/user/shell-practice/backup.sh /home/user/shell-practice/backup.log 21 # 每 5 分钟执行健康检查 */5 * * * * /home/user/shell-practice/health_check.sh /home/user/shell-practice/health.log 21 # 每周日凌晨 1 点清理日志 0 1 * * 0 find /var/log/myapp/ -name *.log -mtime 30 -delete写 crontab 时注意脚本里的命令要使用绝对路径因为 cron 环境与交互式终端不同PATH 可能不完整。脚本本身最好也加上日志输出和失败重试机制。9.5 批量服务器巡检生产环境有多台服务器时可以写一个带主机列表的巡检脚本#!/bin/bash set -euo pipefail hosts_file/etc/myapp/hosts.txt log_file/var/log/myapp/inventory.log while read -r host; do if [ -z $host ]; then continue fi echo [$(date %F %T)] 检查 $host ping -c 1 -W 2 $host /dev/null 21 if [ $? -eq 0 ]; then echo $host 可达 else echo $host 不可达 | tee -a $log_file fi done $hosts_file如果要远程执行命令检查磁盘、内存可以配合 SSH 密钥登录后逐台执行。但务必确认你拥有这些服务器的合法管理权限避免未授权访问。9.6 Shell 脚本调用 REST APIShell 虽然不是写 HTTP 服务端的最佳语言但作为客户端调用 API 非常方便。下面是一个调用 API 获取数据的示例#!/bin/bash set -euo pipefail api_urlhttps://api.example.com/v1/status token${API_TOKEN} # 发起 GET 请求并保存响应 response$(curl -s --max-time 10 -H Authorization: Bearer $token $api_url) echo API 响应: $response # 检查响应中是否包含 success if echo $response | grep -q status:success; then echo 任务执行成功 else echo 任务可能失败需要人工确认 exit 1 fi发送 POST 请求批量提交数据curl -s -X POST https://api.example.com/v1/tasks \ -H Content-Type: application/json \ -H Authorization: Bearer $token \ -d {task_name: backup, target: web-01}如果接口返回 JSON并且需要提取字段可以用jq工具。它专门处理 JSON 数据#!/bin/bash response$(curl -s https://api.example.com/v1/status) task_id$(echo $response | jq -r .task_id) status$(echo $response | jq -r .status) echo 任务 ID: $task_id, 状态: $status在未安装 jq 的服务器上可以用yum install jq或apt install jq安装非常轻量。9.7 批量任务带日志与失败重试批量任务不能只“无脑跑”需要记录每个任务的执行状态和失败次数。下面是一个带简单重试机制的脚本#!/bin/bash set -euo pipefail task_file/tmp/tasks.txt log_file/var/log/myapp/batch_task.log max_retry3 while read -r task; do [ -z $task ] continue retry0 while [ $retry -lt $max_retry ]; do retry$((retry 1)) echo [$(date %F %T)] 开始执行任务: $task (第 $retry 次) if bash -c $task $log_file 21; then echo [$(date %F %T)] 任务成功: $task break else echo [$(date %F %T)] 任务失败: $task sleep 2 fi done done $task_file echo 批量任务结束详细日志见 $log_file这个脚本读入任务列表每条任务最多重试 3 次并把执行日志追加到日志文件。生产环境如果要控制并发可以在此基础上加入后台执行与wait。10. 资源占用与性能观察Shell 脚本本身几乎没有常驻内存消耗真正影响性能的是脚本里调用的外部命令和循环方式。掌握以下几点能明显提升脚本执行效率。第一减少子进程创建。Shell 中每调用一次外部命令grep、sed、awk、curl 等都会创建新进程。如果循环 1000 次每次执行 3 个外部命令就会创建 3000 个进程。常见的优化思路是先捕获命令输出再在变量层面做处理而不是在循环里反复启动命令。第二合理使用管道。管道虽然方便但每条管道都会产生额外进程。如果只是简单文本处理可以直接用 awk 完成过滤和统计避免 “grep awk sed” 三个进程串行处理同一份数据。第三使用time测算脚本耗时time bash batch_task.sh输出会显示 real、user、sys 三项时间。real 是真实耗时user 是用户态 CPU 时间sys 是内核态 CPU 时间。第四使用top、ps观察脚本所在进程的资源占用ps -ef | grep batch_task.sh top -p $(pgrep -f batch_task.sh | head -1)如果脚本在循环中持续占用 CPU可能是出现了死循环或外部命令调用链条过长。可以用set -x开启调试模式观察脚本执行过程定位卡住的步骤。第五控制并发数量。前面提到的后台并发写法能加速任务但如果一次性启动几十上百个后台进程系统负载会快速上升。使用简单的并发控制#!/bin/bash max_concurrent5 task_count0 for ip in 192.168.1.{1..20}; do ( ping -c 1 -W 1 $ip /dev/null 21 echo $ip 存活 || echo $ip 不通 ) task_count$((task_count 1)) if [ $((task_count % max_concurrent)) -eq 0 ]; then wait fi done wait echo 并发巡检完成这样做既利用了并行能力又不会把系统资源打满。11. 常见问题与排查方法问题现象可能原因排查方式解决方案执行脚本报 Permission denied文件没有执行权限ls -l script.shchmod x script.sh提示 command not found环境变量 PATH 不完整echo $PATH使用绝对路径或安装缺失命令脚本运行时全部正常但 cron 执行失败cron 环境没有加载用户环境变量在脚本中打印日志查看 cron 输出脚本开头添加 source 配置文件或使用绝对路径/bin/bash^M错误或换行符导致怪问题脚本是 Windows 编辑的存在 CRLF 换行file script.sh使用sed -i s/\r$// script.sh转换变量比较时报 integer expression expected变量不是数字或为空echo 变量值: $num检查先做变量判空和类型处理条件判断一直不生效方括号前后缺少空格检查[和]改成[ $var value ]脚本中 rm 或 mv 没有生效变量包含空格未加引号echo $file确认内容变量引用统一加双引号grep 匹配不到内容但文本确实存在正则表达式错误或文件编码问题cat file | head查看原文调整正则确认必要编码awk 输出为空分隔符设置不正确查看原始文本结构根据实际分隔符调整-F忘了给脚本加执行权限直接./script.sh运行权限位不足chmod x script.sh用bash script.sh临时运行使用bash script.sh能运行./script.sh无法运行缺少 shebang 或没有权限head -1 script.sh添加#!/bin/bash并设置权限Shell 脚本问题排查最重要的不是记住所有语法而是学会“把执行过程打开看”。在脚本开头加set -x执行时会把每条命令展开到终端命令是否正确、变量是否展开都能直接看到。还需要注意一点脚本里如果出现rm -rf或 file这类破坏性命令执行前一定要打印确认信息。可以写一段确认逻辑#!/bin/bash read -p 确认要删除 $target 目录吗输入 yes 继续: confirm if [ $confirm ! yes ]; then echo 已取消 exit 1 fi这样至少能防止手误造成不可逆后果。12. 最佳实践与使用建议第一脚本开头统一使用set -euo pipefail。这一行能避免大量因为命令失败、变量未定义、管道错误被忽略导致的问题。第一次写脚本的人可能觉得它碍事但一旦跑过生产任务就会明白这行保护的价值。第二变量引用统一加双引号。$file和$file的区别在处理带空格的文件名时会直接决定脚本是否崩溃。这是新手最容易踩的坑。第三目录与文件路径分清楚。脚本的工作目录不一定是你执行命令的目录。在脚本开头声明BASE_DIR$(cd $(dirname $0) pwd) cd $BASE_DIR这样无论从哪里调用脚本文件路径都能保持稳定。第四日志要有时间戳和级别。运维脚本如果没有任何输出出了问题会非常难查。至少每条关键操作要输出一行带时间戳的信息。第五批量任务必须加失败重试和日志。一次跑完就结束的脚本只适合教学生产环境里网络抖动、资源不足、依赖服务没起来都会导致任务失败。重试机制和日志是底线。第六敏感信息不入库。脚本里的密码、Token、私钥路径不要明文写入代码仓库。使用环境变量、配置文件权限控制或专门的密钥管理工具。第七合规与授权。批量巡检、远程执行命令、调用第三方接口前确认你拥有合法授权。不要用 Shell 脚本做未授权扫描、批量爆破、抓取他人数据等操作。测试环境先跑通生产环境先小范围验证再加到 cron。第八写脚本时就要考虑失败后的影响。不要写那种默认“一切正常”的脚本。每个关键步骤后都要判断退出码失败时提供明确提示。13. 从这套实战内容继续往后怎么走看完这套内容你已经具备独立编写运维脚本的基础能力。建议先验证的是把前文的批量重命名、日志分析和服务健康检查脚本在你的 Linux 环境里跑一遍确认每个环节都能正常输出。最容易踩的坑集中在三处一是条件判断两边的空格二是 Windows 编辑导致的 CRLF 换行三是脚本执行权限没有设置。这三个问题解决了大部分脚本都能顺利跑起来。如果还想继续深入下一阶段可以按这个顺序扩展在现有脚本中引入getopts支持类似./deploy.sh --env prod --force的参数解析方式用 Shell 脚本封装一套自己的工具函数库把日志、时间格式化、HTTP 请求、文件备份等通用能力抽出来结合jq处理 JSON 格式的 API 响应实现更完整的接口自动化把脚本接入 crontab逐步建立自己的运维自动化任务集更进一步可以关注 Ansible、Python 脚本等更高级的自动化工具但 Shell 作为底层能力始终不会过时。Shell 脚本的门槛不在于语法有多难而在于你能否把零散的 Linux 命令组织成一条清晰、可靠、可维护的执行链。把变量、条件、循环、函数和文本处理这一套流程练熟了后续再去接触更复杂的自动化工具底子就会扎实很多。
返回列表