
运维岗位的自动化能力往往就集中在 Shell 脚本这一层。日常巡检要重复敲命令日志分析要反复看文件服务部署要一步步执行操作这些工作一旦变成手工操作效率低不说还容易漏步骤。把重复操作用 Shell 脚本固化下来是 Linux 运维入门最值得先花时间搞定的事情。这次我们来看一套从零开始的 Shell 脚本编程实战路线覆盖变量、条件判断、循环、函数以及 grep、sed、awk 三剑客。学完之后能解决什么问题批量处理服务器状态、分析日志里的错误信息、写定时巡检脚本、快速处理文本文件这些运维高频场景都能用脚本完成。整套内容不依赖特定厂商平台只要有一台 Linux 机器就可以跑成本几乎为零。这篇文章会按实际使用顺序展开先讲环境准备再写第一个脚本然后逐个突破变量、条件、循环、函数最后用三剑客做文本处理和完整实战案例。每段都带可运行的示例代码读者可以边看边在自己机器上验证。无论你是刚接触 Linux 的新人还是已经会敲命令但没系统写过脚本的运维工程师这套路线都适用。1. 核心能力速览能力项说明学习目标掌握 Shell 脚本编写能力覆盖变量、条件、循环、函数、文本处理核心工具Bash、grep、sed、awk使用平台Linux 发行版、macOS、Windows 下的 WSL 或 Git Bash环境依赖无需安装额外付费软件Linux 自带终端即可代码要求会基本的 Vim 或 VS Code 使用即可主要场景批量命令执行、日志分析、定时巡检、服务启停、文本批量处理适合人群运维工程师、后端开发、测试工程师、学习 Linux 的在校学生进阶方向结合 crontab 做定时任务结合 xargs 做并行处理结合 Python 做更复杂的自动化2. 适用场景与使用边界Shell 脚本最擅长的场景是“文本 命令 文件”类自动化。比如登录服务器后要连续执行 10 条命令检查系统状态写成脚本后一条命令就能完成日志文件每天增长需要用 grep 和 awk 快速统计访问量、错误码多台服务器需要批量做同样的配置变更用脚本加循环就能处理。这些都是运维日常里频率最高、最适合脚本化的操作。但也要清楚 Shell 的边界。复杂的数据结构处理、高并发任务编排、强类型业务逻辑不适合拿 Shell 硬写这类工作更适合 Python、Go 或专门的运维平台。此外脚本虽然方便但执行前要把影响想清楚尤其是涉及rm、dd、 /dev/sda这类对系统有破坏性的命令时必须先在测试环境验证。生产环境操作要遵循变更流程避免“一条脚本删库”的事故。在安全和合规方面脚本中如果涉及服务器账号、数据库密码、API Token不要明文写在脚本里并上传到公开仓库。建议使用环境变量或密钥管理工具。涉及采集服务器日志、分析访问数据时也要确认数据来源是否合规不要在未授权的情况下处理他人系统的敏感信息。3. 环境准备与前置条件Shell 脚本本身不挑环境核心要求是有一个可用的 Linux 环境。常见选择有几种本机安装 Linux 发行版例如 Ubuntu、CentOS、Debian适合以 Linux 为日常工作系统的用户。Windows 用户可以使用 WSL在 Windows 上直接运行 Linux 终端环境隔离干净安装也方便。使用虚拟机安装 Linux适合想在隔离环境里练习又不影响主系统的读者。使用云服务器适合已经有一台服务器、需要直接做运维练习的场景。不建议买任何付费课程和工具这些方案全部免费。安装好系统后先确认 Bash 版本bash --version绝大多数 Linux 发行版默认都带 Bash版本一般不会低于 4.0。如果输出里显示GNU bash和version信息环境就可用。还需要一个文本编辑器。初学者推荐 VS Code安装 Remote-SSH 插件后可以远程编辑服务器脚本也可以用系统自带的 Vimvim test.sh编辑器不是重点重点是能写文件并保存。脚本文件本身也没有严格的扩展名要求但习惯上用.sh结尾方便识别和编辑器高亮。另外建议保留一台可以反复折腾的 Linux 机器不要一上来就在生产服务器上测试。网络搜索材料里反复出现“linux面试题测试”“shell脚本编程100例”这类关键词训练环境能随便造才能把语法练熟。4. 第一个 Shell 脚本变量与基本语法新建一个脚本文件vim first.sh写入如下内容#!/bin/bash # 第一个脚本打印系统信息 name运维小站 echo 你好$name echo 当前用户: $(whoami) echo 当前目录: $(pwd) echo 当前时间: $(date %Y-%m-%d %H:%M:%S)保存后执行bash first.sh如果脚本没有执行权限用bash first.sh不需要加执行权限适合刚开始接触的阶段。想直接./first.sh执行需要先加上执行权限chmod x first.sh ./first.sh这段脚本涉及 Shell 最基础的四个概念#!/bin/bash是解释器声明name运维小站是变量赋值$name是变量引用$(whoami)是命令替换也就是把命令输出作为字符串使用。变量使用的几个细节要特别注意。变量赋值等号两边不能有空格写成name test会报错。字符串中要取变量值用双引号$name会被展开用单引号时变量不会展开。下面是区别nameworld echo hello $name # 输出 hello world echo hello $name # 输出 hello $name位置参数在脚本中也很常用它解决了“脚本怎么接收外部输入”的问题#!/bin/bash # 用法: ./args.sh 参数1 参数2 echo 脚本名: $0 echo 第一个参数: $1 echo 第二个参数: $2 echo 参数个数: $# echo 所有参数: $执行bash args.sh dev prod输出会依次显示脚本名、两个参数、参数个数和所有参数。这个能力在写批量任务时非常重要同一个脚本可以配合不同参数反复调用。另一个高频需求是保存命令输出到变量。比如检查端口是否在监听port_count$(ss -tlnp | grep 8080 | wc -l) if [ $port_count -gt 0 ]; then echo 8080 端口正在监听 else echo 8080 端口未监听 fi这里已经出现if判断了属于下一节的内容但可以看出来变量 命令替换 条件判断是 Shell 自动化最常用的组合。5. 条件判断与循环掌握流程控制5.1 if 条件判断脚本执行不能永远是“一路往下走”要根据条件做不同处理。if是最基本的判断结构#!/bin/bash # 判断输入的数字是正数、负数还是零 read -p 请输入一个数字: num if [ $num -gt 0 ]; then echo 正数 elif [ $num -lt 0 ]; then echo 负数 else echo 零 fi-gt是大于-lt是小于-eq是等于-ge是大于等于-le是小于等于。文件判断在运维中更常用#!/bin/bash # 判断配置文件是否存在 config/etc/nginx/nginx.conf if [ -f $config ]; then echo 配置文件存在 else echo 配置文件不存在 fi-f判断文件是否存在-d判断目录是否存在-z判断字符串是否为空。写脚本时先判断文件存在再操作能避免很多低级错误。和||是短路写法适合最简单的判断[ -d /var/log ] echo 日志目录存在 ping -c 1 baidu.com /dev/null 21 || echo 网络不通第一行的意思是“目录存在才输出”第二行的意思是“ping 不通才输出”。这种写法简洁但复杂逻辑还是建议用完整的if结构方便阅读和维护。5.2 for 循环运维场景里批量操作通常是循环完成的。for循环处理固定列表很方便#!/bin/bash # 批量输出一段文字 for ip in 192.168.1.1 192.168.1.2 192.168.1.3; do echo 检查主机: $ip done也可以配合seq生成数字序列#!/bin/bash # 创建 5 个测试目录 for i in $(seq 1 5); do mkdir -p test_dir_$i echo 已创建 test_dir_$i done更常见的写法是遍历目录下的所有文件#!/bin/bash # 批量重命名 .txt 文件添加日期前缀 for file in /tmp/files/*.txt; do base_name$(basename $file) mv $file /tmp/files/$(date %Y%m%d)_$base_name echo 已处理: $base_name done这里要注意文件名和路径尽量用双引号包起来。如果文件名里带空格不加引号会导致mv命令被拆分成多个参数出现“文件找不到”的报错。这是新手最容易遇到的问题。5.3 while 循环while适合“不知道具体执行次数只知道循环条件”的场景。最典型的是读取文件每一行#!/bin/bash # 逐行读取 IP 列表并 ping 测试 while read -r ip; do if ping -c 1 -W 1 $ip /dev/null 21; then echo $ip 存活 else echo $ip 不可达 fi done ip_list.txtip_list.txt每行写一个 IP 地址脚本会逐行读取。-W 1表示超时 1 秒避免某个 IP 不通时脚本卡住。生产环境跑大量主机检测时这种基于文件的循环写法很好用不用把 IP 写死在脚本里。5.4 case 分支case适合“根据参数内容走不同分支”的场景最典型的是服务启动脚本#!/bin/bash # 模拟服务管理脚本 case $1 in start) echo 启动服务... ;; stop) echo 停止服务... ;; restart) echo 重启服务... ;; *) echo 用法: $0 {start|stop|restart} exit 1 ;; esac执行bash service.sh restart会走 restart 分支。*)是默认分支当参数不匹配时输出帮助信息并退出。这个结构比多个if判断更清晰看起来也更专业。6. 函数让脚本变成可维护的工程脚本变长以后如果所有逻辑都堆在主干里后期改起来会很痛苦。函数的作用是把重复代码抽出来一次定义、多次调用。#!/bin/bash # 定义日志函数 log_info() { echo [INFO] $(date %Y-%m-%d %H:%M:%S) $1 } log_error() { echo [ERROR] $(date %Y-%m-%d %H:%M:%S) $1 } log_info 任务开始 # 模拟执行过程 sleep 1 log_info 任务执行中 log_error 发生异常请检查函数内部使用$1接收第一个参数。调用时传什么$1就是什么。这样脚本里的所有日志输出格式统一排查问题的时候非常方便。函数的局部变量用local声明避免影响全局变量。举个例子#!/bin/bash nameglobal name set_name() { local namelocal name echo 函数内部: $name } set_name echo 函数外部: $name输出结果是“函数内部: local name”和“函数外部: global name”。如果不加local函数内赋值会覆盖全局变量容易出隐性 bug。一个更接近真实运维场景的例子检查磁盘空间并给出告警提示。#!/bin/bash # 检查磁盘空间使用率 threshold80 check_disk() { local usage$(df / | awk NR2 {print $5} | sed s/%//) echo 根分区当前使用率: ${usage}% if [ $usage -ge $threshold ]; then echo 磁盘空间超过阈值需要清理 else echo 磁盘空间正常 fi } check_disk这里用到了awk取第五列再用sed去掉百分号属于三剑客的预演。函数化的好处是以后要检查多个目录只需要把路径作为参数传进去函数体不用改。脚本到这一步已经具备基本的工程结构了。下一步是把可能失败的命令检查到位避免脚本“表面执行成功、实际操作失败”。比较稳妥的建议是开启set -e#!/bin/bash set -e echo 开始执行 command_not_exist_here echo 这行不会输出set -e表示脚本中任何一条命令返回非零状态码脚本立即退出不会继续往下执行。对于批量部署类脚本这个设置能尽早暴露问题。也可以使用set -u检查变量是否被使用前已声明建议一起打开。7. 三剑客实战grep、sed、awkgrep、sed、awk合称 Linux 文本处理三剑客。三者的分工可以简单理解为grep负责筛选你想要的文本行sed负责对文本进行替换、删除等编辑操作awk负责按列提取内容并做统计计算。运维日志分析、配置改动、报表统计靠它们三个基本都能完成。先看grep。最常用的是从日志里找关键字# 在 nginx 访问日志中查找 HTTP 500 错误 grep 500 /var/log/nginx/access.log # 忽略大小写搜索 grep -i error /var/log/nginx/error.log # 正则匹配找出所有 4xx 和 5xx 状态码 grep -E (4[0-9]{2}|5[0-9]{2}) /var/log/nginx/access.log # 统计匹配行数 grep -c 500 /var/log/nginx/access.log # 反向匹配排除 debug 行 grep -v DEBUG app.loggrep -E使用扩展正则匹配多位数用[0-9]{2}比[0-9][0-9]更简洁。生产环境分析接口报错时这种玩法要熟练。再看sed。sed最核心的操作是替换基本格式是s/旧内容/新内容/# 替换文件中的 IP 地址输出到屏幕 sed s/192.168.1.10/10.0.0.10/ nginx.conf # 全局替换不加 g 只替换每行第一处 sed s/192.168.1.10/10.0.0.10/g nginx.conf # 直接修改文件注意会覆盖原文件 sed -i s/192.168.1.10/10.0.0.10/g nginx.conf # 删除空行 sed /^$/d config.txt # 只打印前 10 行 sed -n 1,10p access.log-i参数会直接修改文件执行前强烈建议先备份cp nginx.conf nginx.conf.bak sed -i s/old_config/new_config/g nginx.conf最后看awk。awk的强项是按列处理默认用空格或 Tab 分隔# 打印访问日志的第一列 IP awk {print $1} /var/log/nginx/access.log # 统计访问量最高的前 10 个 IP awk {print $1} /var/log/nginx/access.log | sort | uniq -c | sort -rn | head -10 # 按逗号分隔文件字段 awk -F , {print $1, $3} data.csv # 条件过滤只打印访问次数大于 100 的 IP awk {count[$1]} END {for (ip in count) if (count[ip] 100) print count[ip], ip} /var/log/nginx/access.logawk的BEGIN和END块需要单独理解。BEGIN在读取文件前执行END在读取完所有行后执行。上面的统计写法是把每个 IP 作为数组下标累加次数最后遍历输出这是awk做日志统计最典型的用法。三剑客连起来可以完成一条完整的分析链路# 统计某时间段内 500 错误出现的次数 grep 31/May/2025 /var/log/nginx/access.log \ | grep 500 \ | awk {print $1} \ | sort | uniq -c | sort -rn这条命令先按日期筛选日志行再筛选 500 状态码然后提取 IP 并统计最终得到“哪些 IP 触发了 500 错误”。整个流程不需要编写复杂程序纯命令行就能完成。8. 运维实战案例从需求到脚本前面是语法这里串成完整案例。8.1 案例一批量检测主机存活运维经常需要确认一批主机是否在线手动逐个ping很浪费时间。脚本配合文件就可以完成#!/bin/bash # 批量检测主机存活 # 用法: bash check_hosts.sh host_list.txt list_file$1 if [ ! -f $list_file ]; then echo 找不到文件: $list_file exit 1 fi while read -r host; do [ -z $host ] continue if ping -c 1 -W 1 $host /dev/null 21; then echo $host 存活 else echo $host 不可达 fi done $list_filehost_list.txt内容示例:192.168.1.1 192.168.1.2 baidu.com执行bash check_hosts.sh host_list.txt这个脚本体现了几个关键点检查参数、检查文件是否存在、跳过空行、ping 超时控制。先把这 20 行脚本吃透比看 100 个零散命令有用。8.2 案例二Nginx 访问日志 Top 10 IP日志分析是运维面试里出现频率很高的题目核心命令就是用 awk、sort、uniq 组合#!/bin/bash # 统计访问量前 10 的 IP log_file/var/log/nginx/access.log if [ ! -f $log_file ]; then echo 日志文件不存在: $log_file exit 1 fi awk {print $1} $log_file | sort | uniq -c | sort -rn | head -10这里的管道逻辑需要看懂awk {print $1}提取 IPsort排序让相同 IP 相邻uniq -c统计每项出现次数sort -rn按次数降序排列head -10取前 10。每个命令只做一件事串起来完成一个完整的统计需求。同样的思路可以统计请求状态码分布awk {print $9} /var/log/nginx/access.log | sort | uniq -c | sort -rn8.3 案例三磁盘空间告警脚本定时巡检磁盘超过阈值就告警这是非常实用的运维场景#!/bin/bash # 磁盘空间告警脚本 # 阈值单位是 % threshold85 alert_file/tmp/disk_alert.log usage$(df / | awk NR2 {print $5} | sed s/%//) if [ $usage -ge $threshold ]; then echo $(date %Y-%m-%d %H:%M:%S) 磁盘使用率 ${usage}%超过阈值 ${threshold}% $alert_file echo 磁盘告警: 当前使用率 ${usage}% else echo 磁盘状态正常: 当前使用率 ${usage}% fi加上crontab就变成了定时任务# 每天 8 点执行一次磁盘检查 0 8 * * * /bin/bash /home/user/check_disk.sh /tmp/disk_check.log 21crontab -e会打开定时任务配置按行写入上面的配置即可。注意脚本中最好使用绝对路径因为 crontab 环境里 PATH 可能不包含/usr/sbin等目录直接写df有时会报找不到命令。更稳妥的写法是在脚本开头指定 PATH#!/bin/bash PATH/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin9. 调试技巧与性能观察写完脚本不会一次通过是常态重要的是掌握调试方法。最基础的是给脚本加执行权限后如果提示Permission denied执行chmod x script.sh如果脚本报语法错误用bash -n只做语法检查不执行bash -n script.sh如果需要一步步看变量变化用bash -xbash -x script.shbash -x会把每条命令的执行过程打印出来变量值也会展开显示。变量赋值不对、循环范围有问题、命令替换结果为空都能从-x输出里看到线索。这是排查脚本问题最重要的工具。还有一种常见问题在 Windows 上写完脚本传到 Linux 执行时报错提示$\r: command not found。原因是 Windows 的换行符是\r\nLinux 是\n文件里多余的\r被当成命令内容了。转换方法sed -i s/\r$// script.sh再把脚本用dos2unix转一下也可以但sed是每台 Linux 机器都有的。关于脚本性能入门阶段不用过度优化但有两个原则值得记住。第一循环里尽量不要频繁调用外部命令能一次awk处理完的不要循环 1000 次再grep。第二批量执行任务时不要一条条串行跑可以用xargs并行# 对 ip_list.txt 中的 IP 并发 ping最多同时跑 20 个进程 cat ip_list.txt | xargs -P 20 -I {} ping -c 1 -W 1 {} /dev/null 21 echo {} ok || echo {} fail并发能大幅缩短批量检测时间但要注意控制并发数避免瞬间打满系统资源。这里也建议用shellcheck做静态检查在多数 Linux 发行版里可用包管理器安装能自动发现常见的引号、变量、语法问题shellcheck script.sh10. 常见问题与排查方法问题现象可能原因排查方式解决方案执行./script.sh提示 Permission denied脚本没有执行权限ls -l script.sh查看权限chmod x script.sh执行时提示bad interpreter: /bin/bash^MWindows 换行符导致file script.sh查看类型sed -i s/\r$// script.sh变量输出为空变量名写错或变量未赋值用bash -x查看展开结果检查变量名和引用方式循环批处理时文件名带空格报错变量没加双引号查看bash -x输出使用$file形式引用脚本在 crontab 中不执行crontab 环境变量不完整查看/var/log/cron或 syslog脚本开头指定 PATH使用绝对路径grep匹配不到内容正则写错或文件编码问题先head文件确认格式检查分隔符必要时用grep -a按文本处理sed -i修改后内容不对替换规则写错先不加-i预览输出先运行替换命令查看结果再添加-i端口检测脚本一直显示未监听检测命令权限不够手动执行ss命令确认必要时切换用户或使用 sudo 执行awk取不到列内容字段分隔符不是空格cat -A查看文件隐藏符号用-F指定正确分隔符脚本执行到一半退出某条命令返回非零状态去掉set -e测试或打印状态码根据错误命令单独处理加 11. 最佳实践与学习路线建议Shell 脚本入门不难但想写出可维护、不出事故的脚本有几点建议可以尽早养成习惯。第一个建议是每写一个脚本都保留一个最小模板。模板里带上#!/bin/bash、脚本用途注释、set -euo pipefail、关键变量的默认值。这样每次开新脚本时不需要从零开始想结构也不会忘了设置错误退出。set -euo pipefail三个选项的意义分别是“出错即退出”“使用未声明变量即报错”“管道中任一条失败视为整体失败”对提升脚本稳定性很明显。第二个建议是目录结构规范化。建议把脚本、日志、输入数据、备份分开放置/opt/scripts/ # 脚本目录 /opt/scripts/logs/ # 脚本运行日志 /opt/scripts/backup/ # 修改前的备份文件 /home/user/input/ # 输入数据文件脚本里的输出路径不要写成散落的/tmp/xx全部统一到日志目录里排错时集中查看。第三个建议是养成“改动前先备份、执行前先预览”的习惯。凡是用sed -i修改配置文件先备份凡是删除文件的脚本第一版先用echo把命令打印出来而不是真正执行凡是批量任务先跑一个只有 3 条数据的小样本确认结果再全量跑。这些习惯能避免多数脚本事故。第四个建议是配合其他工具扩展能力。Shell 擅长的是连接系统命令和定时任务涉及复杂数据计算、网页请求、并发编排时可以考虑把 Shell 与 Python 结合Shell 负责调度和文件操作Python 负责解析和处理。更上层的自动化还可以了解 Ansible、Docker、Kubernetes 等运维工具但 Shell 是所有这些工具的理解基础。关于学习路径建议按这个顺序推进先掌握 Linux 常用命令然后学变量和条件判断接着写循环和函数再用三剑客处理文本最后把前面内容组合成 3 到 5 个完整运维脚本。这个过程大概需要 1 到 2 周的业余时间。完成之后再看招聘岗位里的 Shell 相关要求基本都能看懂也不会觉得脚本题吓人。面试中常见的 Shell 考察点无非是这几个方向变量与特殊变量、文件判断、循环遍历日志、awk 统计、sed 替换、脚本调试、定时任务配置。这些内容在这篇文章里都有覆盖。可以自己做一个脚本题库把每个点写成一个小脚本反复练习到不用查资料也能写出来。Shell 脚本不是一门“看完就会”的技术而是“写熟了才真正会”。建议现在就打开终端从第一个变量脚本开始照着文章里的代码敲一遍。敲完变量、循环、函数、三剑客、实战案例这五个阶段你的运维自动化基础就真正打牢了。