Shell脚本从入门到实战:变量、循环、函数与Linux三剑客全解析

发布时间:2026/8/31 4:10:52
Shell脚本从入门到实战:变量、循环、函数与Linux三剑客全解析 如果你每天都要登录十几台 Linux 服务器敲同样的命令如果每次排查日志都要在grep、awk、sed之间来回接力如果你发现自己加班到深夜只是为了改 50 个配置文件、清理 100 个旧日志那么你缺的不是更快的鼠标而是一套真正属于自己的自动化能力。Shell 脚本就是运维工程师最低门槛、最高回报的自动化武器。它不像 Java 和 Python 那样需要复杂的工程结构也不需要专门的编译环境一个文本编辑器加一个 Linux 终端就能开工。但很多初学者在接触 Shell 时会有一种错觉这不过是大白话命令拼在一起随便写写就能跑。真正进入实际项目后才发现变量引用少了一个双引号、循环里的空格多了一个、函数返回值没接住都会让脚本行为变得奇怪甚至把生产环境搞出大问题。这篇文章会从零开始把 Shell 脚本中最核心的变量、循环、函数和 Linux 三剑客讲透并用一个真实运维周期里最常见的“磁盘告警 日志清理”场景带你完整地写出一个可上生产环境的脚本。读完你不仅能看懂别人写的脚本还能在自己的服务器上独立落地一套自动化任务。1. 这篇文章真正要解决的问题很多运维新手学 Shell 时会陷入两个极端一是只学命令不会串联二是只看教程不上手。结果就是遇到实际问题时脑海里什么都有印象但写不出一段能跑的脚本。还有一部分人是踩了大坑才回头补基础比如在脚本里用rm -rf把目录删了用for循环误操作了全部文件或者在crontab里写脚本时发现环境变量全部丢失。这篇文章的核心目标是解决三个问题把 Shell 脚本的地基打牢。变量、判断、循环、函数这些语法点不只是记命令而是理解它们为什么这样设计什么时候用什么时候不用。把 Linux 三剑客串联起来用。grep、sed、awk不是三个孤立的命令它们天然互补。掌握了它们的组合方式日志分析、批量替换、报表提取都会轻松很多。提供一份可以照搬的实战脚本。从需求分析、命令选择、脚本编写到验证排查最后沉淀成一个可以直接放到 crontab 里的磁盘监控脚本。如果你是一个刚入行的运维工程师、每天要处理服务器和日志的研发工程师、或者对 Linux 自动化感兴趣的在校学生这篇文章特别适合你。读完之后你至少能独立解决下面这类问题写一个脚本检查磁盘使用率并在超过阈值时打印告警。写一个循环批量重命名一批文件或批量执行远程命令。用一条命令从日志文件中提取出现频率最高的几个 IP。在脚本出错时机智地定位问题而不是靠眼睛一行行瞪。2. Shell 脚本的基础认知解释器、执行方式与常见误区在写第一行 Shell 之前先搞清楚 Shell 到底是什么。Shell 是一个命令行解释器它接收用户输入的命令调用操作系统提供的功能再把结果返回给用户。Linux 上常见的 Shell 有bash、zsh、sh、csh等。绝大多数 Linux 发行版默认 Shell 都是bash而/bin/sh通常是一个指向bash或dash的软链接。虽然sh和bash大部分语法兼容但为了充分使用数组、[[ ]]等高级特性脚本里建议明确写成#!/bin/bash。这里想强调一个概念Shell 脚本本质是把一系列命令按照编程逻辑组织起来。它不像 Java 那样有强类型、类、异常体系而是依赖命令的退出状态码、标准输出和标准错误来协作。0表示成功非零表示失败这个规则贯穿始终。2.1 创建一个最简脚本在终端里执行下面的命令创建脚本mkdir -p ~/shell-demo cd ~/shell-demo cat hello.sh EOF #!/bin/bash echo Hello, Shell echo 当前用户是: $(whoami) EOF然后赋予执行权限并运行chmod x hello.sh ./hello.sh如果一切正常你会看到输出结果。如果你直接执行./hello.sh得到Permission denied说明脚本没有执行权限需要先执行chmod x hello.sh。这里有几个新手最容易踩的坑没有写#!/bin/bash或写成了#!/bin/bash\r在 Windows 上编辑过文件后容易出现这个问题。把脚本放在当前目录却直接输入hello.sh系统会在PATH路径中寻找结果找不到。正确做法是./hello.sh。使用sh hello.sh执行脚本时sh可能不是bash某些语法会报错。2.2 三种常见执行方式执行方式示例特点直接执行./script.sh需要执行权限脚本内容在当前进程中运行通过解释器执行bash script.sh不需要执行权限推荐用于调试在当前 Shell 中执行source script.sh或. script.sh不产生子进程脚本中的变量会在当前 Shell 中保留第三种方式常用于加载配置文件或环境变量例如source ~/.bashrc。如果你希望一个脚本修改当前终端的环境变量必须用source方式运行否则变量只在子 Shell 中生效。2.3 脚本调试的基础方法不需要等脚本出问题才开始学习调试。写脚本时应该养成边写边跑的习惯。常用调试命令bash -n script.sh # 只检查语法不执行 bash -x script.sh # 跟踪执行打印每条命令bash -x会把执行的每一句命令和变量展开结果都显示出来新手定位变量为空、逻辑分支错误时非常好用。后面章节里我们还会结合实战场景再讲一次。3. 变量与字符串处理最容易在细节上翻车的地方3.1 变量的定义和使用Shell 变量定义非常简单但有一个铁律等号两侧不能有空格。# 正确 namezhangsan version1.2 # 错误会执行名为 name 的命令 name zhangsan变量引用使用$或${}花括号写法在变量名后面还有其他字符时特别重要。appmyapp echo 当前应用: ${app}_v1 # 如果写成 $app_v1会被解析成变量 app_v1变量内容有空格时引用时务必加双引号否则会触发词拆分。这个坑极其常见file/data/logs/app log.txt cat $file # 错误会被拆成 cat /data/logs/app 和 log.txt cat $file # 正确3.2 单引号、双引号与反引号单引号变量不做展开所见即所得。双引号变量会展开命令替换会执行。反引号与$()命令替换区别在于$()支持嵌套可读性更好。nameworld echo hello $name # 输出 hello $name echo hello $name # 输出 hello world echo 今天日期: $(date %F)在实际脚本中建议非特殊情况下都使用双引号避免路径和内容中的空格导致问题。3.3 位置参数与特殊变量脚本在接收外部参数时会用到这些变量变量含义$0脚本名称$1、$2第一个、第二个参数$#参数个数$所有参数每个参数独立$*所有参数整体作为一个字符串$?上一条命令的退出状态码下面是一段演示脚本#!/bin/bash echo 脚本名称: $0 echo 参数个数: $# echo 第一个参数: $1 echo 所有参数: $运行bash args.sh a b c它会按预期输出。参数为空时要加默认值写成${1:-default}这在脚本健壮性中很常见。3.4 字符串处理常用操作strhello-world-shell echo ${#str} # 长度 echo ${str:6:5} # 从第 6 个字符开始截取 5 个字符: world echo ${str/world/WORLD} # 替换第一个匹配 echo ${str//-/_} # 替换所有匹配: hello_world_shell这些操作在批量处理文件名、解析配置时非常实用。4. 条件判断与控制流让脚本拥有决策能力4.1 if 与 test 命令的写法Shell 的条件判断核心是test命令[ ]和[[ ]]都是test的语法糖。区别在于[[ ]]是 bash 关键字支持更丰富的运算符和正则匹配推荐在 bash 脚本中使用。#!/bin/bash score85 if [[ $score -ge 60 ]]; then echo 及格 elif [[ $score -ge 90 ]]; then echo 优秀 else echo 不及格 fi常用测试语法类型写法说明数值比较-eq、-ne、-gt、-lt、-ge、-le数值比较不能用字符串比较,!,-z,-n-z表示空字符串文件判断-f,-d,-e,-r,-w,-x检查文件类型和权限逻辑连接,||,!与、或、非一个常见的误区是[ $var -gt 10 ]当$var为空时执行后变成了[ -gt 10 ]直接报语法错误。解决办法是给变量加默认值或用[[ ]]例如[[ ${var:-0} -gt 10 ]]。4.2 case 多分支当判断值比较固定时case比if更清晰#!/bin/bash envprod case $env in dev) echo 开发环境;; test) echo 测试环境;; prod) echo 生产环境;; *) echo 未知环境;; esac4.3 for 循环循环是批量操作的基础。Shell 的for循环支持多种写法#!/bin/bash # 写法1: 列表 for ip in 192.168.1.1 192.168.1.2 192.168.1.3; do echo 检查 $ip done # 写法2: 范围 for i in {1..10}; do echo 第 $i 次 done # 写法3: 命令结果 for file in /etc/*.conf; do echo 配置: $file done # 写法4: C 风格 for ((i1; i5; i)); do echo index$i done注意当文件名包含空格时for file in $(ls *.log)这种旧写法会出现问题更稳妥的方式是for file in /var/log/app/*.log直接交给 Shell 做路径扩展。4.4 while 循环与 break/continuewhile适用于按条件循环的场景例如读取文件每一行#!/bin/bash while read -r line; do echo 读取到: $line done /etc/hosts在循环内部break用于跳出整个循环continue用于跳过当前迭代。最经典的安全防止死循环写法是count0 while true; do echo 运行次数: $count if [[ $count -ge 5 ]]; then break fi ((count)) done5. 函数把重复逻辑封装起来函数的作用不只是减少代码量更重要的是让脚本有清晰的层次。很多新手写出了几百行的线性脚本改一个逻辑要点半天就是因为没有用函数拆解。5.1 函数的基本写法#!/bin/bash log_info() { echo [$(date %F %T)] INFO: $1 } log_error() { echo [$(date %F %T)] ERROR: $1 2 } log_info 开始部署 log_error 部署失败函数定义注意几点函数内部$1、$2是函数的参数不是脚本的参数。return只返回 0-255 之间的状态码不能返回字符串。函数内定义的变量如果不加local会变成全局变量污染脚本其他部分。#!/bin/bash get_disk_usage() { local mount_point$1 local usage usage$(df -h $mount_point | awk NR2 {print $5}) echo $usage } result$(get_disk_usage /) echo 根分区使用率: $result这个例子使用了命令替换和awk函数通过echo把计算结果输出到标准输出调用方用$()接住。5.2 使用函数组织逻辑实战中我习惯把脚本分成三两个函数参数解析函数、核心逻辑函数、输出结果函数。这样后边维护时只需要看对应函数不需要读一大坨主流程。6. Linux 三剑客grep、sed、awk 的实战组合grep、sed、awk被称为 Linux 三剑客是运维处理文本数据的核心工具。三者各司其职但不孤立。工具核心能力适用场景grep按行过滤查找日志中的关键字、按正则过滤sed流式编辑替换、删除、插入、批量修改文件awk按列处理和统计提取字段、求和、排序统计、生成报表6.1 grep快速筛选# 在日志中搜索 ERROR并输出行号 grep -n ERROR /var/log/app.log # 统计出现次数 grep -c ERROR /var/log/app.log # 忽略大小写 grep -i error /var/log/app.log # 多文件一起搜索 grep -n Exception /var/log/app-*.loggrep最重要的是理解正则表达式。普通字符直接匹配元字符如^、$、.、*能表达更灵活的模式。例如# 匹配以 2025- 开头的行 grep ^2025- /var/log/app.log # 匹配 4 位数字 grep -E [0-9]{4} /var/log/app.log6.2 sed流式替换与编辑sed最常见的用途是替换但它的能力远不止于此。# 将文件中的 8080 替换为 9090全局替换 sed -i s/8080/9090/g config.properties # 删除空行 sed -i /^$/d config.properties # 只打印第 5 到第 10 行 sed -n 5,10p /var/log/app.log-i表示直接修改文件这是高权限操作。生产环境的配置变更不建议直接手写sed -i指向重要文件。先备份再替换后验证至少要有这个习惯。cp nginx.conf nginx.conf.bak sed -i s/worker_processes 1/worker_processes auto/ nginx.conf nginx -t6.3 awk按列处理和统计awk的核心逻辑是“模式 动作”。它的默认分隔符是空白$1、$2表示对应列$0表示整行。文本文件如/etc/passwd使用冒号分隔可以用-F指定。# 打印 /etc/passwd 中的用户名 awk -F: {print $1} /etc/passwd # 打印磁盘使用率 df -h / | awk NR2 {print $5}awk还能做统计和累加# 统计数字之和 echo -e 10\n20\n30 | awk {sum $1} END {print sum , sum} # 从日志中统计请求数量访问时间在凌晨 1 点的行 awk $4 ~ /^\[01\/Jan/ /var/log/nginx/access.log | wc -l注意nginx 日志的字段位置取决于log_format。默认 combined 格式中IP 是$1但请求行和状态码不一定是你以为的位置。这里不给出固定的状态码字段编号因为它会因为格式不同而变化。更稳妥的办法是先打印一行看字段再用 awk 处理。这个习惯非常有用。6.4 三剑客组合实战一个最常见的场景从 nginx 访问日志中找出访问量最大的前 5 个 IP。awk {print $1} access.log | sort | uniq -c | sort -rn | head -5这里awk负责提取 IPsort排序uniq -c去重并统计次数sort -rn按次数反向排序head -5取前 5。另一个场景把配置文件中注释的行删除后再替换参数。这种组合在批量调整配置时很顺手sed /^#/d config.properties | sed s/^old.*/oldnew/ config_new.properties7. 完整实战编写一个磁盘监控与日志清理脚本前面讲的都是基础语法。这一节我们从零开始写一个可放入 crontab 的脚本。需求如下检查指定分区的磁盘使用率。如果使用率超过阈值输出告警信息。清理指定目录下超过 30 天的过期日志。所有输出都带时间戳方便后续排查。完整脚本如下。#!/bin/bash # 文件路径: ~/shell-demo/disk_clean.sh # 用途: 磁盘使用率检查与旧日志清理 set -euo pipefail # 可配置参数 MOUNT_POINT/ THRESHOLD80 LOG_DIR/var/log/myapp KEEP_DAYS30 # 日志函数 log_info() { echo [$(date %F %T)] INFO: $* } log_warn() { echo [$(date %F %T)] WARN: $* 2 } # 检查磁盘使用率 check_disk_usage() { local usage usage$(df -P $MOUNT_POINT | awk NR2 {print $5} | tr -d %) if [[ -z $usage ]]; then log_warn 无法获取磁盘使用率: $MOUNT_POINT return 1 fi if [[ $usage -gt $THRESHOLD ]]; then log_warn 磁盘使用率 $usage% 超过阈值 $THRESHOLD% else log_info 磁盘使用率 $usage% 正常 fi return 0 } # 清理旧日志 clean_old_logs() { if [[ ! -d $LOG_DIR ]]; then log_warn 目录不存在: $LOG_DIR return 1 fi log_info 开始清理 $LOG_DIR 下 $KEEP_DAYS 天前的日志文件 # 先查看拟删除文件确认无误后可去掉 -print 并加上 -delete find $LOG_DIR -type f -name *.log -mtime ${KEEP_DAYS} -print # 确认后取消下面一行的注释 # find $LOG_DIR -type f -name *.log -mtime ${KEEP_DAYS} -delete } main() { log_info 磁盘监控任务开始 check_disk_usage clean_old_logs log_info 磁盘监控任务结束 } main脚本编写的几个关键点MOUNT_POINT、THRESHOLD、LOG_DIR、KEEP_DAYS放在文件顶部方便维护。set -euo pipefail是 bash 脚本的保险机制。-e让脚本在遇到非零退出码时退出-u让未定义变量直接报错-o pipefail使管道命令的返回状态以最后一个失败命令为准。这三件套可以避免很多低级错误。df -P保证输出格式在不同系统上更一致awk NR2 {print $5}提取使用率tr -d %去掉百分号。find先输出拟删除文件列表而不是直接删除这是生产环境里非常重要的安全习惯。确认无误后再启用删除动作。函数内使用local避免变量污染。8. 运行结果与效果验证给脚本赋予执行权限并运行chmod x ~/shell-demo/disk_clean.sh bash ~/shell-demo/disk_clean.sh如果当前根分区使用率低于 80%且日志目录存在预期输出大致是[2025-06-20 10:30:01] INFO: 磁盘监控任务开始 [2025-06-20 10:30:01] INFO: 磁盘使用率 43% 正常 [2025-06-20 10:30:01] INFO: 开始清理 /var/log/myapp 下 30 天前的日志文件 [2025-06-20 10:30:01] INFO: 磁盘监控任务结束 如果使用率高于 80%你会看到一行WARN而且这条信息会输出到标准错误。验证是否真的成功除了看输出可以用两条命令确认echo $?如果$?为 0说明主流程正常。如果某个函数返回非零脚本可能在中间退出。此时用调试模式执行追踪出问题的位置bash -x ~/shell-demo/disk_clean.shbash -x会打印每一步命令重点看变量有没有被正确赋值awk提取结果是不是空。这个方法基本能覆盖 80% 的脚本问题。另外脚本中保留了注释状态的-delete行。先在测试目录里准备几个旧日志文件验证find可以正确列出它们再正式开启删除。不要在没有备份和测试的情况下直接在生产目录执行删除操作。9. 常见问题与排查思路问题现象可能原因排查方式解决方案./script.sh执行报Permission denied脚本没有执行权限ls -l script.sh查看权限chmod x script.sh报错$\r: command not found脚本在 Windows 上编辑换行符是 CRLFfile script.sh或cat -v script.shsed -i s/\r$// script.sh或使用dos2unix变量明明是空的赋值语句等号两侧有空格用bash -x检查变量展开改成varvalue形式单引号包裹的文本没有替换把变量写在了单引号里检查引号类型需要变量展开时使用双引号for file in $(ls *.txt)结果异常文件名中有空格或特殊字符对包含空格的文件名预判直接使用通配符for file in *.txtset -u之后脚本报未定义变量引用了没有声明的变量检查变量名拼写使用${var:-默认值}或提前赋值脚本在终端运行正常在 crontab 里却失败cron 环境变量和交互 Shell 不一致在脚本里临时打印 PATH在脚本开头显式设置 PATH或使用绝对路径find删除后误删了不该删的文件没有先打印再删除查看回收站或备份生产环境先备份再测试再执行10. 最佳实践与工程建议Shell 脚本的工程化水平直接决定了它是“一次性工具”还是“可维护资产”。下面这些建议来自生产环境踩坑后的总结建议写进你的日常习惯里。10.1 脚本头部写使用说明一个规范脚本开头应该包含注释说明脚本用途、作者、修改时间、用法示例。这一点在团队协作中尤其重要。半年后再回来看自己的脚本没有注释会非常痛苦。10.2 严格模式与手动退出推荐在脚本入口位置写入set -euo pipefail在需要允许某条命令失败时显式处理例如command_that_may_fail || { echo 命令失败但继续执行 }这样既不会因为没有-e导致错误被忽略也不会因为一条无关紧要的命令失败导致整个流程崩溃。10.3 变量引用统一加双引号不管是普通变量还是位置参数引用时都写成$var。只要变量内容可能包含空格、换行、特殊字符不加引号就可能在传参、比较、命令替换时被拆分。这个习惯能规避一大类问题。10.4 不解析 ls不使用 rm -rf 变量$(ls *.log)这类写法在遇到空格和特殊字符时会出错。用通配符和find替代。对于删除操作永远先打印后删除并注意rm不能和未加引号的变量直接拼接。10.5 接入定时任务前测试脚本写完先手动跑一次然后放进测试环境跑几天最后再上生产。crontab 中执行环境与非交互式 Shell 不同建议在脚本中显式指定绝对路径包括命令的全路径。如果拿不准命令在哪儿用which command查看。10.6 用 ShellCheck 检查脚本ShellCheck 是 Shell 静态分析工具能找出大量潜在问题包括引号、变量表示、权限、无效语法等。安装方式以系统为准# Debian/Ubuntu 系的参考命令 apt install shellcheck # CentOS/RHEL 系的参考命令 yum install shellcheck安装后执行shellcheck disk_clean.sh它会输出警告级别和错误级别的提示。新手可以先用它辅助学习但要注意 ShellCheck 的输出是建议不是必须全盘照做最终要理解每条警告背后的原因。10.7 日志与告警分离脚本输出分为标准和错误两个通道不要把所有信息都打印到屏幕也不要把错误信息悄悄地吞掉。日志记录到文件告警通过邮件、钉钉或企业微信机器人发送这些能力都可以集成在函数中。11. 总结与后续学习方向这篇文章把 Shell 脚本中最核心的知识点串了一遍变量与字符串处理、条件判断与循环、函数封装、Linux 三剑客以及一个完整可运行的磁盘监控脚本。更重要的是我特意强调了几个会带你误入歧途的细节等号两侧不能有空格、变量引用要加双引号、生产环境删除要慎重、脚本调试优先用bash -x。这些内容都是新手到初级运维的分水岭。下一步的学习方向可以很明确。第一把正则表达式吃透它是 grep、sed、awk 的共同基础。第二练习 awk 的统计和排序能力很多报表类工作可以完全由 awk 完成。第三学习 ShellCheck 的使用把自己的旧脚本逐一把警告清掉这个过程本身就是一个提高工程能力的好练习。第四了解 Linux 中的定时任务、systemd timer把脚本接入自动化调度系统。Shell 脚本是一座桥连接你的命令直觉和系统管理能力。不要急着写复杂脚本先从小任务开始把一个命令手工执行十遍再把它变成脚本执行一遍再让它接受参数和执行时间。这样反复几次你的自动化能力就真正长在身上了。建议把这篇文章收藏备用动手写脚本时对照着检查。如果你在练习中遇到具体的报错欢迎在评论区带着脚本片段和交流讨论。写脚本这件事最大的成就感不只是跑通而是你能清楚地知道自己每一步在做什么。