ARTICLE DETAIL

资讯详情

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

基础Shell脚本示例大全:从变量、循环到日志清理与系统巡检

基础Shell脚本示例大全:从变量、循环到日志清理与系统巡检 看到“Basic Shell Script Examples”这个标题我第一时间想到的是自己刚入行那会儿翻烂了的脚本示例集。“基础”这两个字很容易被低估但说句实话我在生产环境里见过太多线上问题本质上都是基础语法没吃透导致的。这篇文章我把多年攒下来的、最常用的一批基础Shell脚本示例整理出来每个示例都是完整可跑的不需要花钱不需要注册账号直接复制保存成文件就能用这也是标题里Free Downloads的意思——白嫖也要嫖得有价值。无论你是刚开始接触Linux命令行的新手还是写过一阵子脚本但总在细节上翻车的老手这组示例都能帮你把基础钉牢。1. 为什么Shell脚本值得花时间吃透1.1 Shell脚本到底能解决什么问题日常运维和开发中最耗时的工作往往不是多高深的任务而是重复、琐碎、批量化的操作。比如每天早上登录服务器依次敲free、df -h、uptime看状态比如每周末手动清理一次过期日志比如几十台机器上要批量修改配置文件。这些操作单次只需要几秒但乘以时间、乘以机器数量就是一个惊人的开销。Shell脚本干的事情就是把你在终端里手动敲的命令按顺序编排起来再加上变量、判断和循环让机器在无人值守的情况下自动把这些事做完。举个例子我接过一个业务团队的需求每天凌晨要把前一天的应用日志归档压缩保留最近7天超过30天的备份直接删除。这个需求如果用人工去做每天至少占用一个人二十分钟还容易漏删或误删。用脚本处理一个cron定时任务就搞定了脚本本身不过三十行。这种收益不是技术上的炫酷是实打实地把时间还给了人。这也是为什么我坚持把Shell脚本列为运维和开发的基本功它不挑场景不依赖重型框架任何一个Linux环境都能跑。1.2 环境准备从哪个Shell开始要装什么写脚本之前先确认你手头的Shell环境。Linux和macOS默认基本都是Bash路径在/bin/bash绝大多数脚本在文件开头声明#!/bin/bash就能在所有Unix-like系统里跑。现在不少发行版默认Shell改成了zsh但Bash脚本在zsh里依然兼容所以不用纠结默认Shell是什么。Windows用户也不需要眼馋装一个Git Bash、WSL或者Cygwin底层都是完整的Bash环境脚本写法完全一致这点我后面踩坑章节还会细说。我建议初学者只学Bash这一套不要今天看个sh脚本明天学个zsh特性把精力分散了。Bash的语法和POSIX sh大部分兼容但Bash多了[[ ]]、数组、字符串处理这些更顺手的特性。学习方向确定之后准备一个能随便折腾的目录比如~/scripts把下面要讲到的示例都放到这里千万不要直接在系统关键目录里测试脚本。另外要说个很容易被忽略的事脚本文件后缀无所谓.sh只是约定俗成但文件内容第一行必须是shebang#!执行方式通常是./xxx.sh而直接打xxx.sh是去PATH路径里找命令如果你的脚本不在PATH里直接打名字是找不到的。2. 核心语法细节与实际操作要点2.1 变量定义、引用、作用域Shell脚本里的变量定义很简单变量名值但所有编程习惯里最容易被坑的就是这一行等号两边不能有空格。变量名值会被解析成一条完整的赋值语句而变量名 值会被当成三个词系统去执行名为“变量名”的命令然后自然报command not found。引用变量的时候前面加美元符比如echo $name更严谨的是写成${name}尤其在拼接字符串的场景里几乎必须用花括号把变量边界圈出来。写一个拼接的例子nameapp; curl -O http://example.com/${name}_v2.tar.gz如果不加大括号Shell会去找name_v2这个变量取出来的就是空值下载链接直接坏掉。这类问题在实际代码里非常隐蔽因为不报错只是行为不对。变量作用域方面脚本里默认所有变量都是全局的函数内也一样。如果你希望一个变量只活在函数里必须显式声明local否则函数调用完变量还在全局空间里悄悄存在改到哪里影响哪里排查起来非常痛苦。顺带把几个特殊变量记牢$0是脚本名$1、$2是位置参数$#是参数个数$?是上一条命令的退出码。退出码是Shell世界里最重要的信号0代表成功非0代表失败脚本判断逻辑全靠它驱动后面讲条件判断的时候还会反复用到。2.2 条件判断里的那些坑条件判断是Shell脚本里画风最清奇的部分。if后面跟的不是布尔表达式而是一条命令判断的依据是这条命令的退出码。最常见的[ condition ]写法本质上是test命令的语法糖所以方括号前后必须有空格这个空格丢了系统直接把[当成命令去执行报错非常经典。字符串比较用或都行数字比较要用-eq、-ne、-gt、-lt这些如果你用或者去比较数字Shell会按字符串比较10会排在2前面结果经常和直觉相反。文件判断也是高频用法-f判断是否为普通文件-d判断是否为目录-x判断是否可执行-e判断是否存在。这些我建议一次记全因为脚本里大量出现。Bash还提供[[ ]]扩展写法里面支持正则匹配和、||组合还能避免引号、通配符相关的坑所以只要是Bash环境我统一推荐用[[ ]]少踩很多基层的雷。举一个实际例子判断一个文件内容是否包含关键字if [[ $(grep -q ERROR /var/log/app.log) -eq 0 ]]; then这种写法就是错的grep -q本身已经返回退出码不需要再在外层套一层命令替换直接if grep -q ERROR /var/log/app.log; then干净利落。很多人在这一步绕弯子就是没理解“if后面是命令而不是表达式”这个底层逻辑。2.3 循环与数组的实战用法循环把脚本从“按顺序执行一遍”升级成“批量处理N遍”自动化能力基本靠它撑起来。最常用的是for循环标准写法是for i in list; do ...; done。list可以是$(ls *.log)这种方式也可以直接给一串值。但这里有个高频坑直接for i in $(ls)去遍历文件名一旦文件名里有空格文件名会被拆成多段循环体拿到的根本不是完整文件名。正确做法是让Shell自己处理通配符比如for f in *.log这种方式在文件名含空格时依然稳妥。逐行读取文件内容也是刚需处理配置文件和日志都用得上标准姿势是while read line; do ...; done file.txtread后面可以加-r参数防止文件内容里的反斜杠被当成转义符。数组方面Bash支持索引数组定义用arr(a b c)取某个元素是${arr[0]}取全部元素是${arr[]}这里一定要加引号否则含空格的元素会被拆开。批量场景里经常是for循环内嵌套if判断判断里再执行命令思路很直白回头看看生产里的脚本百分之七八十都是这种结构。2.4 函数和参数传递的底层逻辑脚本超过几十行之后函数就成了理清结构的核心工具。定义函数很简单写函数名() { ... }就行调用时直接写函数名。函数可以接收参数在函数体内用$1、$2访问用return返回退出码。值得注意的是Shell函数的return和其他语言的return完全是两码事它只能返回0到255的整数退出码不能返回字符串。那需要函数“返回”字符串怎么办常规做法是让函数把结果echo到标准输出调用方用result$(my_func)捕获输出。这个思路必须转过来否则你会看到return hello直接报numeric argument required或者return hello返回了数字0折腾半天取不到值。同时函数内部声明的变量默认是全局的这跟很多语言也不一样想局部化必须加local。写模块化脚本的时候管好变量作用域基本就管好了一半的bug。3. 可以直接保存的示例与实操过程这一章是全文的重头戏三个示例都是我从真实项目里拆出来的代码可以直接复制保存标注了用途和使用方法。每个示例我都尽量把设计理由讲清楚你拿过去改改路径和时间参数就能落到自己的环境里跑。3.1 示例一一键采集系统信息并输出报告新机器交付、环境巡检、故障复盘第一步都是采集系统信息。CPU几核、内存多大、磁盘剩多少、系统什么版本、IP地址多少这些信息靠人一条条敲费时间还容易漏。下面这个脚本把常用采集命令串成一条流水线运行后直接输出一张整齐的报告#!/bin/bash # 系统信息采集脚本 # 用法: ./sysinfo.sh echo 系统信息报告 $(date %F_%T) echo [系统版本] cat /etc/os-release | head -n 2 echo [CPU] echo 逻辑核心数: $(nproc) echo 型号: $(grep model name /proc/cpuinfo | head -n 1 | awk -F: {print $2} | sed s/^[ \t]*//) echo [内存] free -h | awk /^Mem:/ {printf 总量: %s, 已用: %s, 可用: %s\n, $2, $3, $7} echo [磁盘分区] df -h | grep -vE ^Filesystem|tmpfs|devtmpfs echo [IP地址] hostname -I echo 报告输出完成 脚本里最值得琢磨的是命令组合的思维方式。比如CPU型号这一行grep从/proc/cpuinfo里抓出第一行包含model name的内容awk按冒号分成两段取第二段再交给sed把行首的空格清掉。单看每一段都不难组合起来才是真功夫。执行前先chmod x sysinfo.sh然后./sysinfo.sh结果会按区块打印出来把输出重定向到一个文件比如./sysinfo.sh report.txt就能留档了。要采集多台机器时可以套一层for循环把每台机器的输出重定向到各自主机名命名的文件里一次巡检全搞定。3.2 示例二自动备份并清理过期日志日志文件是磁盘空间的隐形杀手应用一直运行日志一直增长炸盘事故十有八九是日志惹的祸。这个脚本实现一套完整的日志生命周期把指定目录下7天前的日志文件压缩归档归档成功后删除原文件同时把超过30天的旧备份再清掉。先备份后清理的顺序是铁律容错优先宁可多留一份备份也不能删了还没有归档的原始数据。#!/bin/bash # 日志备份与清理脚本 # 用法: ./log_cleanup.sh /path/to/logdir /path/to/backupdir LOG_DIR${1:-/var/log/app} BACKUP_DIR${2:-/var/backup/logs} KEEP_DAYS${KEEP_DAYS:-7} ARCHIVE_DAYS${ARCHIVE_DAYS:-30} if [ ! -d $LOG_DIR ] || [ ! -d $BACKUP_DIR ]; then echo 错误: 日志目录或备份目录不存在 exit 1 fi cd $LOG_DIR || exit 1 for log in *.log; do [ -f $log ] || continue if find $log -mtime $KEEP_DAYS | grep -q .; then tar -czf $BACKUP_DIR/${log%.log}_$(date %Y%m%d).tar.gz $log rm -f $log echo 已归档: $log fi done find $BACKUP_DIR -name *.tar.gz -mtime $ARCHIVE_DAYS -delete echo 备份与清理完成这个脚本里有几个细节值得重点说。第一${1:-/var/log/app}这种写法表示如果调用者没传第一个参数就使用默认值有了这层保护漏传参数也不会让脚本崩溃。第二for log in.log循环在目录里没有任何匹配文件时会把.log当成一个字符串文件名来遍历加上[ -f $log ] || continue这一行就是为了把这层假文件名过滤掉这种防护逻辑在批量脚本里是标准动作。第三落到删除动作之前先执行归档归档失败rm就不会执行环环相扣切断事故链。配合crontab定时跑比如每天凌晨2点执行日志管理就完全不用人盯了。3.3 示例三批量文件分类与空格清理下载目录和共享目录时间久了就是垃圾场各种扩展名混在一起。下面这个脚本按扩展名把文件自动分到Pictures、Documents、Archives、Others子目录里分类完成后顺便把文件名中的空格替换成下划线为后续脚本处理扫除隐患。#!/bin/bash # 文件整理脚本 # 用法: ./organize.sh ~/Downloads TARGET_DIR${1:-.} cd $TARGET_DIR || exit 1 for file in *; do [ -f $file ] || continue ext${file##*.} case $ext in jpg|jpeg|png|gif|bmp) mkdir -p Pictures mv $file Pictures/ ;; doc|docx|pdf|txt|md) mkdir -p Documents mv $file Documents/ ;; zip|rar|7z|tar|gz) mkdir -p Archives mv $file Archives/ ;; *) mkdir -p Others mv $file Others/ ;; esac done find . -type f -name * * | while read -r name; do newname$(echo $name | tr _) mv $name $newname done echo 文件整理完成case多分支语法是这一节的重点写法上接近其他语言的switch每个模式后面带一个右括号执行完一段用;;收尾。${file##.}是参数展开##表示从头部开始删掉最长匹配.匹配到最后一个点剩下的就是扩展名所以archive.tar.gz取出来的是gz而不是tar.gz但这不影响归档分类因为gz本身已经能命中归档分支。最后的find加while read循环处理空格文件名时-r参数用来保留反斜杠原样这一点对Windows路径或特殊字符文件名尤其重要。4. 常见问题与排查技巧实录写脚本最大的挫折感来源不是逻辑写不出来而是脚本不按你想的方向跑你还没地方查。遇到报错先别慌很多问题其实就在下面这四类里先对着过一遍再去翻细节。报错或现象主要原因快速解法Permission denied文件缺少可执行权限chmod x script.shbad interpreter: /bin/bash^MWindows换行符CRLFdos2unix script.sh 或 sed -i s/\r$//command not found脚本不在PATH中或命令拼写错误用./script.sh执行检查命令名[: too many arguments方括号条件写错多为空格或引号问题检查[ ]两侧空格变量用双引号包住syntax error near unexpected token括号、引号不匹配等语法错误bash -n script.sh 检查语法变量取出来是空的变量未定义或花括号边界没用脚本开头加set -u拼接时用${var}4.1 脚本执行被拒Permission denied的真相社区求助区出现频率最高的报错就是./script.sh后弹出Permission denied。原因很单纯文件没有可执行权限。解决办法是chmod x script.sh。但我从来不用chmod 777理由很简单给所有用户开放执行权限多人服务器上等于裸奔。另外还有一种“权限正确却仍报错”的情况是文件开头缺了shebang行也就是#!/bin/bash。没有这行系统不知道用什么解释器执行也可能报错。所以脚本第一行请固定写好shebang别省。还有一种常见的低级错误脚本是从Windows记事本或某些编辑器里写的上传到Linux执行后报bad interpreter问题出在Windows的换行符CRLF上这点放到4.3细说。排查这类问题时先用file script.sh看一眼文件类型它能直接提示是不是CRLF这招很管用。4.2 变量为空时最危险Shell里引用了一个不存在的变量不会像Java或Python那样抛异常它只是把变量当成空字符串继续往下跑。这在普通场景里问题不大但一旦命令涉及删除、移动空值就会产生毁灭性效果。比如rm -rf $dir/project如果$dir没有定义命令就变成rm -rf /project直接把根目录下的project文件夹端了。这种事故不是段子实操里确实发生过。防御措施我固定用两条。第一脚本开头加set -u让Shell遇到未定义变量直接报错退出而不是静默用空值顶替再加set -e让任何一条命令失败就立即中断避免错误状态继续传播。第二涉及rm、mv这类不可逆操作之前先echo打印一遍要执行的完整命令人工扫一眼确认无误再放行。批量脚本里我还会给危险删除动作加一个开关变量比如ALLOW_DELETEtrue才执行rm否则只打印不执行。这些习惯花不了几秒能在关键时刻救命。4.3 CRLF与LF跨平台脚本的隐形杀手Windows上写的脚本传到Linux跑最常见的报错是bad interpreter: /bin/bash^M。原因在于Windows用回车加换行CRLF表示一行结束Linux只认换行LF所以脚本第一行#!/bin/bash后面多了一个看不见的\r系统去/bin目录找bash\r这个解释器自然找不到。这类问题用vi打开文件根本看不出异常只有执行cat -A script.sh看到每行结尾是^M$才能确认是CRLF。修复方式有两种。命令行的做法是dos2unix script.sh如果没装这个工具用sed -i s/\r$// script.sh也是一样的效果。更治本的办法是在编辑器里统一行尾模式VS Code右下角可以一键切换LF/CRLF写脚本时务必选LF。这个坑不只在Shell脚本里写Dockerfile、.env配置、CI管道文件都可能踩到跨平台开发的人最好养成习惯。4.4 调试三板斧bash -x、语法检查和最小复现脚本逻辑复杂之后靠肉眼盯不出问题得用工具。第一板斧是bash -n script.sh只做语法检查不实际执行适合在跑之前先排除低级语法错误。第二板斧是bash -x script.sh调试模式Shell会把每一步实际执行了什么、变量展开成什么一条一条打印出来执行到哪里一目了然。看到变量在该展开的地方没有展开问题范围瞬间缩小。第三板斧其实是个习惯把出问题的函数或片段单独拉出来喂一组最小参数单独跑一遍。我调试的时候从来不带整个脚本一起跑因为生产脚本里有业务逻辑、有时间依赖整体跑起来环境噪声太大定位效率低。最小复现单位能让你把问题锁定在具体几行内再配合-x逐步观察绝大多数问题都能在十分钟内找到根源。另外补充一个小技巧在关键步骤后面加日志输出比如echo [$(date %F_%T)] 完成归档步骤脚本跑完看日志就能知道哪一步停了。配合cron跑定时脚本时这个习惯尤其重要因为没有终端输出给你看日志就是你唯一的事故现场。5. 最后说两句实操体会上面这些脚本和坑都是我这些年逐个踩过来、填过来的。Shell脚本入门门槛极低几行命令就能跑通但真正稳定、可维护的脚本拼的是细节。权限别乱放、危险命令别裸奔、变量空值要有防御、跨平台换行要心里有数这些习惯远比多记几个语法特性重要。把这几个示例跑通之后你可以沿着它们继续扩展比如把日志清理脚本挂上cron做成每天自动执行把系统信息采集脚本套一层循环变成批量巡检工具甚至把函数封装成公共库供多个脚本调用。Shell脚本这东西一旦用顺手了你就回不到手工敲命令的时代了。
返回列表