ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

25个常见Linux bash脚本示例:从变量循环到自动化运维实战

25个常见Linux bash脚本示例:从变量循环到自动化运维实战 不少人第一次认真学 bash心里想的是“背几条常用命令就够了吧”。但真到了要写脚本的时候才发现一头雾水变量怎么赋值、参数怎么传、判断怎么写、循环怎么跑每个点都要现查。这个“25 个常见 Linux bash 脚本示例”的项目标题就是冲着这个痛点来的——不跟你讲枯燥的语法手册直接给 25 个能落地、能改改就用的脚本场景。这篇博文我打算换个讲法不把 25 个例子简单地堆成一列而是拆成“为什么这么设计”“每类示例在解决什么问题”“实际跑起来要注意哪些坑”三个层次让你是真的学会写脚本而不只是复制粘贴。25 个常见 Linux bash 脚本示例写给刚上手的你1. 内容整体设计与思路拆解1.1 为什么“25 个示例”是最合适的入门粒度我见过很多教程一上来就扔 200 个代码片段看两页就放弃了也见过只讲 5 个例子的看完还是不会自己写。25 个这个数量恰好覆盖了 bash 脚本的核心语法骨架变量、位置参数、用户输入、条件判断、常见循环、文件操作、文本处理、系统监控、以及最后的综合实战。这个数量不是拍脑袋定的。如果你把日常脚本拆开看会发现 80% 的操作都在重复使用几件事拿输入变量或者参数、做判断if/case/while、处理文件查找、改名、备份、提取信息grep、awk、sort、执行系统命令。25 个示例正好能把这几大类全部覆盖到又不会让初学者产生“我永远学不完”的挫败感。更关键的一点是这 25 个示例是“递进式”的。前 5 个解决“怎么把脚本跑起来”中间 10 个解决“怎么处理文件和文本”后面 10 个进入“系统和日常自动化”。你在阅读时不用一口气吃成胖子每看一组动手敲一遍再往下走基础会更扎实。我自己带团队时也这样要求新人看完前 5 个示例必须独立写出一个带参数的备份脚本看完后面 10 个能写一个日志统计小工具。这两关过了才算入门。1.2 这 25 个示例覆盖的知识点分布与学习路径为了方便你对照自查我先把 25 个示例涉及的知识点整理成一张思维地图阶段示例编号核心知识点学习目标基础流程控制1-5变量、$USER/$PWD、位置参数、read、if、for掌握脚本输入和输出文件与目录操作6-10目录判断、tar 备份、find 查找、for 改名、du 统计能搞定日常文件维护文本处理与日志分析11-15awk、grep -o、字符串替换、while read、sort/uniq能从日志中提取关键信息系统状态监控16-20uptime、free、pgrep、df、ps 排序能监控服务器健康状态自动化与综合实战21-25随机密码、进度条、批量 ping、环境自检、一键发布能独立完成小型自动化任务这个分布也反映了我推荐的进阶路线先解决“怎么让脚本听话”再解决“怎么让它处理一堆文件”接着提高“怎么从文本里找金子”然后才是“怎么盯住系统资源”最后把它们组合成真正的自动化工具。很多时候你觉得 bash 脚本难是因为变量和条件判断还没熟就去啃 sed/awk 的高级写法自然一碰就碎。2. 开始写 bash 脚本前先搞懂这三件事2.1 脚本的可执行权限与 Shebang新手经常卡在一个很尴尬的问题上脚本明明写对了执行时报permission denied。原因很简单Linux 下要运行一个以./方式调用的脚本必须先给脚本加上可执行权限。chmod x myscript.sh ./myscript.sh同时再看脚本的第一行也就是 Shebang#!/usr/bin/env bash这行告诉系统用哪个解释器来运行文件。我推荐写成#!/usr/bin/env bash而不是/bin/bash因为不同发行版中 bash 的绝对路径不一定都在/bin下用env去 PATH 里找更稳妥。实际工作中经常有人把 Windows 下的脚本直接传到 Linux 服务器第一行带着\r导致系统找不到解释器报一堆莫名其妙的错稍后我会在排错篇里细说。2.2 变量、引号与空格这三个“隐形杀手”bash 的语法自由度很大但自由度也意味着容易踩坑。我总结了新手最容易犯的三类问题。第一个坑号两边不能有空格。写 Python 或 JavaScript 习惯了的人很容易写成name tom # 错误bash 会认为 name 是一个命令 nametom # 正确第二个坑变量取值一定要用双引号包起来。假设有一个文件路径是my documents/backup.tar.gz如果你写成rm -rf $pathbash 会把空格当分隔符直接拆成两个参数轻则误删重则把目录搞乱。最稳妥的写法是path/home/user/my documents/backup.tar.gz rm -f $path第三个坑命令替换应该用$( )不推荐用反引号。反引号在嵌套时极易看错而$( )的可读性和可嵌套性都好很多。today$(date %Y%m%d) echo 今天是 $today2.3 调试和防护手段bash -n 与 set -euo pipefail写脚本没人保证一次通过所以调试是你的日常操作。我建议从一开始就养成两个习惯第一每写完一个脚本先做语法检查bash -n myscript.sh这条命令只检查语法不真正执行。如果输出为空说明语法没问题。第二在脚本头部加上防护选项#!/usr/bin/env bash set -euo pipefail我来拆解一下这行的含义set -e遇到第一个错误就退出避免在错误状态下继续执行造成更大破坏。set -u使用未定义的变量时报错退出这能救回无数人的手。set -o pipefail管道中只要有一个命令失败整个管道就视为失败。这三个选项对我而言是“保命符”尤其是set -u。你可能不理解直到你某天写了一个rm -rf $dir而$dir因为变量拼写错误没被赋值——如果没有set -ubash 会把它当成空字符串执行rm -rf /这是很多运维事故的根源。加了它脚本会当场报错退出。3. 25 个经典示例的实操拆解3.1 第 1-5 个变量、参数与基础流程控制这个阶段解决的是“怎么让脚本接收输入、做出反应”。我用 5 个例子带你走一遍 bash 的基本骨架。示例 1Hello World 与内置变量#!/usr/bin/env bash echo Hello, World! echo 当前用户: $USER echo 当前目录: $PWD这是所有脚本的起点。$USER和$PWD是 bash 内置的环境变量直接拿来用就行。注意变量名是用$引用的但是赋值的时候不需要$。很多新手在这点上绕不清楚记住一句话赋值不带$取值才带$。示例 2位置参数与默认值#!/usr/bin/env bash name${1:-world} echo Hello, $name!这个脚本运行./greet.sh tom时输出Hello, tom!不传参数时输出Hello, world!。${1:-world}这个写法是 bash 参数展开的一个经典技巧如果$1存在就用$1否则用默认值world。后面你写脚本时凡是“参数可省”的场景都能用这个套路。示例 3read 读取用户输入#!/usr/bin/env bash read -p 请输入项目名称: project mkdir -p $project echo 已创建项目目录: $projectread -p会在读取前打印提示文本然后把你输入的内容存进变量。这个脚本虽然简单但它体现了交互式脚本的编写思路。注意mkdir -p后面我给变量加了引号防止项目名里带空格导致路径被拆开。示例 4if/elif/else 判断参数数量#!/usr/bin/env bash if [ $# -eq 0 ]; then echo 没有传任何参数 elif [ $# -eq 1 ]; then echo 只传了一个参数: $1 else echo 传了多个参数: $ fi$#是参数个数$是所有参数列表。[ ]是test命令的简写注意[后面和]前面一定要有空格写成[$# -eq 0]会直接报错。这是新手最常犯的语法错误我几乎每周都会在社区回答里看到一次。示例 5for 循环展示文件名#!/usr/bin/env bash for file in *.txt; do echo 找到文本文件: $file done如果当前目录下没有.txt文件*.txt不会被展开成空而是原样输出字符串*.txt严格来说会打印一行不存在的文件名。想严谨一点可以在脚本开头加一句shopt -s nullglob这样当没有匹配文件时循环体就不会执行了。这个小细节是很多人在批量处理文件时踩过的坑。3.2 第 6-10 个文件与目录日常操作处理文件和目录是运维场景里出现频率最高的事情。这 5 个示例能解决大部分“备份、归档、清理、改名”的需求。示例 6判断目录是否存在不存在则创建#!/usr/bin/env bash if [ ! -d /backup/logs ]; then mkdir -p /backup/logs echo 目录 /backup/logs 已创建 fi-d是判断目录是否存在的条件表达式!表示取反。这个模式非常常用几乎每个需要写日志的服务脚本都会用到。你可以把它当成一个固定模板哪里需要保证目录存在就往哪里贴。示例 7按日期打 tar 包备份#!/usr/bin/env bash today$(date %Y%m%d) tar -czf backup_${today}.tar.gz /home/user/project echo 备份完成: backup_${today}.tar.gz这个脚本用$(date %Y%m%d)拿到当天日期拼到文件名里。好处是每天执行都会生成一个新归档不会覆盖前一天的备份。backup_${today}这个写法要注意如果写成backup_$today.tar.gzbash 会把变量名解析成$today.tar.gz因为today.tar.gz是一个合法变量名的一部分这就是为什么要用花括号把变量名明确围起来。示例 8find 查找并删除 30 天前的日志#!/usr/bin/env bash find /var/log/myapp -name *.log -mtime 30 -delete生产环境里日志轮转是必须做的事不然磁盘迟早被撑爆。这里的-mtime 30意思是修改时间在 30 天前-delete直接删除。但我强烈建议第一次执行时先不要加-delete而是用-print或-ls把匹配到的文件列出来看看确认无误后再删。find /var/log/myapp -name *.log -mtime 30 -print示例 9批量把 .txt 重命名为 .md#!/usr/bin/env bash for file in *.txt; do mv $file ${file%.txt}.md done${file%.txt}是“从右边开始匹配并删除.txt后缀”的变量展开语法。这个操作批量改名时非常实用。同样的思路可以扩展到改前缀、加时间戳等场景。再次提醒如果你开了nullglob会更安全同时mv的两个参数都要加引号防止文件名带空格时报错。示例 10统计当前目录下各子目录大小#!/usr/bin/env bash du -sh /var/www/* 2/dev/null | sort -rh | head -20du -sh输出每个目标的总大小sort -rh按人类可读数字从大到小排序head只取前 20 行。2/dev/null的意思是吞掉错误输出比如某些目录没有权限访问时不刷屏报错。这个用法在排查“哪个目录把磁盘吃满了”时属于必杀技。3.3 第 11-15 个文本处理与日志分析日志和文本处理是 bash 脚本最显价值的地方。前面还只算体力活这 5 个示例开始让脚本变得“聪明”。示例 11统计访问日志中排名前 10 的 IP#!/usr/bin/env bash awk {print $1} /var/log/nginx/access.log | sort | uniq -c | sort -rn | head -10这个管道命令非常经典awk 取第一列也就是访问日志里的客户端 IPsort 排序后让相同 IP 连续排列uniq -c 统计重复次数sort -rn 按次数倒序head 取前十名。如果你第一次看到这个命令组合感到眼花建议拆开一段一段加管道一步步看中间结果很快就能理解。示例 12从文本中提取所有 IP 地址#!/usr/bin/env bash grep -oE ([0-9]{1,3}\.){3}[0-9]{1,3} access.log | sort -ugrep -o只输出匹配的部分-E启用扩展正则。([0-9]{1,3}\.){3}[0-9]{1,3}是一个粗略的 IPv4 匹配规则。sort -u去重得到不重复的 IP 清单。这个脚本可以用于审计日志中的外部访问来源也可以配合其他工具做进一步分析。示例 13字符串替换与截取#!/usr/bin/env bash urlhttps://example.com/api/v1/users domain${url#https://} echo 去掉协议后: $domain version${domain##*/} echo 最后一段路径: $version#用于从左边删除匹配的前缀##是从左边删除最长匹配%从右边删除匹配的后缀%%从右边删除最长匹配。如果你经常处理 URL 或文件路径这四个符号必须掌握。这就像 Python 里strip、split的轻量替代方案在脚本里能省掉不少外部命令调用。示例 14while read 逐行处理文件#!/usr/bin/env bash while IFS read -r line; do echo 处理: $line done userlist.txtIFS表示不重置字段分隔符read -r防止反斜杠被转义。这两个细节保证每一行被“原样”读取不会把行首空格吞掉也不会把\n之类的转义变样。循环结束后用 userlist.txt把文件内容作为标准输入喂给循环。这是批量处理用户名单、服务器列表的标准写法。示例 15找出当前目录下最大的 10 个子目录#!/usr/bin/env bash du -h --max-depth1 . 2/dev/null | sort -rh | head -10--max-depth1的意思是不递归到更深的层级只看当前目录下的一级子目录。如果你管理一个数据盘想知道哪个项目占了最多空间这个脚本两秒钟就能给你答案。配合计划任务还能在磁盘快满时自动发提醒。3.4 第 16-20 个系统状态与资源监控这 5 个示例直接与服务器健康状态挂钩。监控不一定非要上 Prometheus 这类重型工具很多场景下一个 bash 脚本加 cron 就足够用了。示例 16输出 CPU 平均负载#!/usr/bin/env bash load$(uptime | awk -Fload average: {print $2}) echo 当前系统负载: $loaduptime会输出系统运行时间和平均负载awk -Fload average:用字符串作为分隔符把后半段取出来。负载值一般有三个数字分别对应 1 分钟、5 分钟、15 分钟。判断是否过载不能只看 1 分钟值要结合核数和 15 分钟趋势。示例 17内存使用率与告警#!/usr/bin/env bash mem_total$(free -m | awk NR2{print $2}) mem_used$(free -m | awk NR2{print $3}) mem_percent$((mem_used * 100 / mem_total)) echo 内存使用率: ${mem_percent}% if [ $mem_percent -gt 90 ]; then echo 内存告警请及时检查进程。 fi这里有一点必须说明free输出里第二行的used列其实已经减掉了 buffer/cache 的一部分如果你追求更准确的“真实占用率”可能要按available来计算。但对于一个入门告警脚本这个写法足够用。等你有需要时再去研究 free 命令的详细输出逻辑。示例 18检查 nginx 进程是否存活#!/usr/bin/env bash if pgrep -x nginx /dev/null; then echo nginx 运行中 else echo nginx 未运行尝试启动... systemctl start nginx fipgrep -x是精确匹配进程名/dev/null表示不把这个命令的输出显示到终端只关心它的退出状态码。如果状态码是 0说明进程存在。这个脚本可以挂在 cron 里每 5 分钟执行一次做最基础的服务守护。示例 19磁盘分区使用率检查#!/usr/bin/env bash used$(df -h / | awk NR2{print $5} | tr -d %) if [ $used -gt 80 ]; then echo 警告根分区已使用 ${used}% else echo 磁盘状态正常: ${used}% fidf -h /拿到根分区信息NR2取第二行第 5 列是已用百分比结尾带着%符号所以要用tr -d %把它删掉这样后续才能做数字比较。-gt是大于的意思。这个模式是磁盘监控的基础模板你可以照着扩展出内存、inode 等多项检查。示例 20列出占用内存最高的前 5 个进程#!/usr/bin/env bash ps aux --sort-%mem | head -6ps aux列出所有进程的详细状态--sort-%mem按内存占用从大到小排序。head -6是因为第一行是表头真正取到的前 5 条才是数据。排查内存泄漏时我会先跑这个脚本再对比不同时间点的进程内存变化判断哪个进程在持续增长。3.5 第 21-25 个自动化运维与综合实战前面 20 个示例都是“零件”最后 5 个开始“组装”。这也是你从“会写命令”走向“会写工具”的分水岭。示例 21生成随机密码#!/usr/bin/env bash password$(openssl rand -base64 12 | tr -d \n) echo 新密码: $passwordopenssl rand -base64 12生成 12 字节随机数并做 base64 编码。tr -d \n把换行符删掉。如果你不想依赖 openssl也可以用/dev/urandomhead -c 16 /dev/urandom | base64 | tr -d | head -c 12两条都可以。如果你需要批量创建系统账户并下发随机密码把这个脚本放进 for 循环里就能一次性生成几十个。示例 22用 bash 写一个简易进度条#!/usr/bin/env bash for i in {1..10}; do printf 进度: for j in $(seq 1 $i); do printf # done echo $((i * 10))% sleep 0.2 done这只是一个演示性质的进度条真正要做命令行进度条有很多成熟工具但理解这个双层循环结构很重要。外层的{1..10}是 bash 支持的序列展开内层的seq用来控制#的数量。你以后写“批量处理 N 个文件每个文件给个进度反馈”的逻辑时思路就是从这里来的。示例 23批量 ping 测试多台主机连通性#!/usr/bin/env bash hosts(192.168.1.1 192.168.1.2 192.168.1.3) for host in ${hosts[]}; do if ping -c 1 -W 1 $host /dev/null 21; then echo $host 可达 else echo $host 不可达 fi donehosts(...)定义数组${hosts[]}取出所有元素。ping -c 1只发一个包-W 1等待 1 秒超时。与其手动一台台敲 ping不如把这个命令挂到 cron 里每天跑一次早上看一眼结果哪台机器掉线一目了然。示例 24环境自检脚本#!/usr/bin/env bash tools(git node python3 gcc docker) for tool in ${tools[]}; do if command -v $tool /dev/null 21; then echo $tool: 已安装 ($(command -v $tool)) else echo $tool: 未安装 fi donecommand -v是查找命令路径的标准方法比which更通用。这个脚本非常适合放在新服务器的初始化阶段或者 CI 流程的第一步。它能快速告诉你这台机器缺哪些依赖省去一个环境问题排查半天的时间。示例 25一键发布脚本#!/usr/bin/env bash set -euo pipefail APP_DIR/opt/myapp cd $APP_DIR git pull origin main || { echo 拉取代码失败 exit 1 } if command -v make /dev/null 21; then make build fi systemctl restart myapp echo 发布完成: $(date)这个综合脚本把很多技巧串到了一起set -euo pipefail保证出错即停git pull || { ... exit 1; }是“命令失败时执行分支”的写法构建步骤用command -v判断有没有 make最后重启服务并打印时间。写发布脚本最重要的原则是一旦某个环节失败绝不能让后面的步骤继续执行否则会出大问题。我见过太多发布事故都是因为脚本没加 error handler代码都没拉下来后面还硬要执行重启结果服务直接挂了。4. 常见问题与排查技巧实录4.1 脚本“跑不起来”的五个典型原因我把新手反馈最多的“脚本不工作”案例汇总成了一个速查表帮你快速定位报错或现象最可能的原因解决方式Permission denied脚本没有可执行权限chmod x script.shcommand not found第一行 shebang 有问题或变量名写错用bash script.sh直接调检查 shebangsyntax error near unexpected token脚本里有 Windows 换行符CRLFsed -i s/\r$// script.sh变量输出为空变量拼写不一致或赋值语句写错用echo DEBUG: $var打印检查命令明明存在但脚本报找不到PATH 不包含该命令路径不要假设环境脚本里用全路径或command -v4.2 语法报错、CRLF 换行与运行环境的坑Windows 上写脚本再传到 Linux 执行是新手重灾区。Windows 记事本或部分编辑器使用的是\r\n作为换行符而 Linux 只认\n。这个看不见的\r会让 bash 在执行第一行 shebang 时找到的是bash\r直接报错也可能导致脚本每个命令末尾都带着一个回车字符。遇到这种情况先用file命令确认file myscript.sh如果输出里出现with CRLF line terminators就用下面的命令清理sed -i s/\r$// myscript.sh更省事的办法是安装dos2unixdos2unix myscript.sh还有一类问题是运行环境差异。例如脚本里#!/usr/bin/env bash但系统里 bash 可能不在 PATH 中或者你在一台 CentOS 上写的语法拿到 Ubuntu 上没问题但反过来就可能因为 sed/awk 版本不同而报错。我处理这类问题时会先确认脚本要跑在什么系统上再决定语法能写到多“花哨”。4.3 我处理线上脚本问题的三段式排查法在部署环境里出问题没有编辑器可以慢慢调试所以我的排查路径基本固定成三步。第一步复现并看完整报错。很多人只看报错的最后一行比如unexpected end of file然后就懵了。正确的做法是回到终端最上面找到脚本实际执行到哪个命令时开始出错。报错信息前面通常会有脚本文件名和行号先记住它。第二步用bash -n做语法校验。如果语法没问题再执行bash -x script.sh。-x模式会把每个被执行的命令展开并打印出来你可以清楚看到变量被替换成了什么值。很多时候“不知道脚本里某个变量到底是什么”才是排查不动的根源而-x一跑全都暴露了。第三步给关键节点插入调试输出。我一般会在可疑变量后面加一行echo DEBUG: project$project 22表示把调试信息输出到标准错误这样即使脚本把标准输出重定向到了日志文件调试信息依然会在终端里显示。完成排查后把这些 DEBUG 行删掉即可。除了这三步还有一个关于排错的独家心得如果你发现一个脚本在手动执行时正常放进 crontab 里却不工作十有八九是环境变量问题。cron 的执行环境只有极简的 PATH很多脚本里直接写的命令比如python3在 cron 下是找不到的。解决办法是在脚本开头显式设置 PATHexport PATH/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:$PATH这个坑我踩过不止一次现在每写一个要交给 cron 执行的脚本第一件事就是把 PATH 补全。还有个容易被忽略的问题在脚本里用rm -rf这类高危命令时一定要确认变量不为空。例如rm -rf /backup/$project如果$project因为某种原因为空实际执行的是rm -rf /backup/整个备份目录会被删光。这种灾难性误删完全可以通过提前检查变量来避免if [ -z $project ]; then echo 错误project 变量为空已终止执行 2 exit 1 fi最后我再分享一个实用经验学这 25 个示例最好的方法不是读而是抄下来改着玩。把示例 7 的备份目录换成你自己的项目目录跑一遍把示例 11 的日志路径换成你手边任何一个文本文件跑一遍把示例 25 的去和自己的部署流程比一比。改着改着你就会发现 bash 脚本不过是一堆命令的组合而你已经能自己组合出需要的工具了。这 25 个示例练完建议立刻挑一个自己最烦的重复性任务给它写一个自动化脚本——那才是你真正入门的时刻。
返回列表