Shell脚本编程实战:从零入门到运维自动化
2026/9/1 2:14:17 网站建设 项目流程

运维岗位的自动化能力,往往就集中在 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 或专门的运维平台。此外,脚本虽然方便,但执行前要把影响想清楚,尤其是涉及rmdd> /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 bashversion信息,环境就可用。

还需要一个文本编辑器。初学者推荐 VS Code,安装 Remote-SSH 插件后可以远程编辑服务器脚本,也可以用系统自带的 Vim:

vim 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会被展开;用单引号时变量不会展开。下面是区别:

name="world" 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 2>&1 || 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 2>&1; then echo "$ip 存活" else echo "$ip 不可达" fi done < ip_list.txt

ip_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 name="global name" set_name() { local name="local name" echo "函数内部: $name" } set_name echo "函数外部: $name"

输出结果是“函数内部: local name”和“函数外部: global name”。如果不加local,函数内赋值会覆盖全局变量,容易出隐性 bug。

一个更接近真实运维场景的例子:检查磁盘空间并给出告警提示。

#!/bin/bash # 检查磁盘空间使用率 threshold=80 check_disk() { local usage=$(df / | awk 'NR==2 {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、awk

grepsedawk合称 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.log

grep -E使用扩展正则,匹配多位数用[0-9]{2}[0-9][0-9]更简洁。生产环境分析接口报错时,这种玩法要熟练。

再看sedsed最核心的操作是替换,基本格式是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

最后看awkawk的强项是按列处理,默认用空格或 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.log

awkBEGINEND块需要单独理解。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 2>&1; then echo "$host 存活" else echo "$host 不可达" fi done < "$list_file"

host_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}'提取 IP,sort排序让相同 IP 相邻,uniq -c统计每项出现次数,sort -rn按次数降序排列,head -10取前 10。每个命令只做一件事,串起来完成一个完整的统计需求。

同样的思路可以统计请求状态码分布:

awk '{print $9}' /var/log/nginx/access.log | sort | uniq -c | sort -rn

8.3 案例三:磁盘空间告警脚本

定时巡检磁盘,超过阈值就告警,这是非常实用的运维场景:

#!/bin/bash # 磁盘空间告警脚本 # 阈值单位是 % threshold=85 alert_file="/tmp/disk_alert.log" usage=$(df / | awk 'NR==2 {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 2>&1

crontab -e会打开定时任务配置,按行写入上面的配置即可。注意脚本中最好使用绝对路径,因为 crontab 环境里 PATH 可能不包含/usr/sbin等目录,直接写df有时会报找不到命令。更稳妥的写法是在脚本开头指定 PATH:

#!/bin/bash PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

9. 调试技巧与性能观察

写完脚本不会一次通过是常态,重要的是掌握调试方法。

最基础的是给脚本加执行权限后,如果提示Permission denied,执行:

chmod +x script.sh

如果脚本报语法错误,用bash -n只做语法检查,不执行:

bash -n script.sh

如果需要一步步看变量变化,用bash -x

bash -x script.sh

bash -x会把每条命令的执行过程打印出来,变量值也会展开显示。变量赋值不对、循环范围有问题、命令替换结果为空,都能从-x输出里看到线索。这是排查脚本问题最重要的工具。

还有一种常见问题:在 Windows 上写完脚本传到 Linux 执行时报错,提示$'\r': command not found。原因是 Windows 的换行符是\r\n,Linux 是\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 2>&1 && echo "{} ok" || echo "{} fail"

并发能大幅缩短批量检测时间,但要注意控制并发数,避免瞬间打满系统资源。这里也建议用shellcheck做静态检查,在多数 Linux 发行版里可用包管理器安装,能自动发现常见的引号、变量、语法问题:

shellcheck script.sh

10. 常见问题与排查方法

问题现象可能原因排查方式解决方案
执行./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 脚本不是一门“看完就会”的技术,而是“写熟了才真正会”。建议现在就打开终端,从第一个变量脚本开始,照着文章里的代码敲一遍。敲完变量、循环、函数、三剑客、实战案例这五个阶段,你的运维自动化基础就真正打牢了。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询