ARTICLE DETAIL

资讯详情

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

Shell 脚本入门:编写你的第一个可执行脚本

Shell 脚本入门:编写你的第一个可执行脚本 1. 引言为什么每个运维和开发者都该会一点 Shell凌晨两点线上服务突然报出「磁盘空间不足」。你登录服务器发现/var/log下堆积了上百个日志文件。如果一个个手动删除不仅慢还容易误删。此时一个有经验的同学只敲了两行命令就把清理和压缩全部搞定。你问他怎么做到的他轻描淡写地回了一句「写了个小脚本。」Shell 脚本就像运维工作中的「瑞士军刀」——它把一条条零散的命令打包成可重复执行的自动化流程。无论是批量重命名文件、定时备份数据库、监控服务状态还是自动化部署应用Shell 脚本都能帮你把繁琐的手工操作变成一键完成的任务。对 Linux 开发者和运维工程师来说掌握 Shell 脚本不仅是加分项更是提升效率、减少人为失误的基本功。所谓Shell字面意思是「外壳」它是用户与操作系统内核之间的接口。我们日常在终端里敲的命令ls、cd、grep……本质上就是通过 Shell 交给内核去执行的。而Shell 脚本则是把这些命令按照一定逻辑写进一个文本文件让 Shell 一次性、按顺序、有条件地执行它们。打个比方Shell 本身像是一位翻译官你每次说一句话它就翻译给操作系统听而 Shell 脚本就好比一份提前写好的「任务清单」翻译官照着清单逐条执行不需要你一句一句地催。本文将带你从零开始编写并运行自己的第一个可执行脚本理解变量、判断、循环、函数等核心语法并掌握排错与规范化的最佳实践。请打开你的终端跟着一起动手。2. 环境准备认识你的 Shell2.1 常见的 Shell 类型Linux 系统上有多种 Shell它们大同小异但语法略有差异。下表列出了几种最常见的 ShellShell 名称可执行文件特点Bourne Shell/bin/sh最早的 Unix Shell语法基础兼容性强Bourne Again ShellBash/bin/bashLinux 默认 Shell功能丰富是事实标准C Shell/bin/csh语法接近 C 语言历史较久Korn Shell/bin/ksh兼具 sh 与 csh 优点Z Shellzsh/bin/zsh交互体验优秀拥有强大的补全和主题生态本文以Bash为例这也是大多数 Linux 发行版和 macOS 的默认 Shell。2.2 查看当前 Shell 和版本打开终端执行以下命令查看当前使用的 Shellecho$SHELL预期输出类似/bin/bash查看 Bash 版本bash--version预期输出示例GNU bash, version 5.1.16(1)-release (x86_64-pc-linux-gnu)注意事项不同 Linux 发行版的 Bash 版本可能不同但本文涉及的基础语法在 4.x 及更高版本中均适用。若需编写兼容性更强的脚本可将解释器声明为/bin/sh但会失去部分 Bash 扩展特性。3. 创建你的第一个脚本3.1 编写脚本文件用任意文本编辑器vi、vim、nano或图形界面的编辑器创建一个名为hello.sh的文件vimhello.sh输入以下内容#!/bin/bash# 这是我的第一个 Shell 脚本echoHello, Shell!echo当前日期是$(date%Y-%m-%d)第 1 行#!/bin/bash称为Shebang发音类似「谢帮」告诉系统用哪个解释器来执行这个脚本。第 2 行# 这是我的第一个 Shell 脚本是注释以#开头不会被执行。echo用于向屏幕输出文本。$(date %Y-%m-%d)是命令替换会先执行date %Y-%m-%d并把结果嵌入字符串中。保存并退出vim 中按Esc输入:wq回车。3.2 赋予可执行权限并运行脚本默认是普通文本文件没有执行权限。需要先赋予执行权限chmodx hello.sh然后运行./hello.sh预期输出Hello, Shell! 当前日期是2026-09-08这里的./表示「当前目录」因为 Linux 出于安全考虑默认不会在当前目录中查找命令必须显式写出路径。你也可以用以下方式直接运行bashhello.sh这种方式不依赖文件的执行权限因为脚本被当作参数传给bash解释器执行。常见错误直接敲hello.sh会报command not found。这是因为当前目录通常不在PATH环境变量中必须用./hello.sh指定路径。3.3 Shebang 的原理Shebang 其实是给内核的「提示」。当你执行./hello.sh时内核读取文件前两个字节若发现是#!就会调用后面的程序这里是/bin/bash并把脚本文件作为参数传给它。因此这一行必须位于文件第一行且#!之后不能有多余空格。4. 变量与数据类型4.1 定义和使用变量Shell 变量赋值时等号两边不能有空格nameAliceage25echo我是$name今年$age岁。预期输出我是 Alice今年 25 岁。使用变量时在变量名前加$。若变量名与后续字符可能混淆可用花括号明确边界wordLinuxecho我喜欢${word}Shell4.2 只读变量与删除变量readonlycompanyAcmeecho$company# companyOther # 取消注释会报错readonly variablereadonly声明的变量只能读取不能修改。普通变量可以用unset删除tmp临时数据echo删除前$tmpunsettmpecho删除后[$tmp]# 输出空4.3 环境变量环境变量是可以在子进程中使用的变量。用export将其导出exportMY_APP_HOME/opt/myapp./child_script.sh# 子脚本中可以读到 $MY_APP_HOME查看所有环境变量可用env或printenv。4.4 特殊变量脚本运行时Shell 会自动注入一些特殊变量对编写脚本非常有用变量含义$0脚本本身的名称$1~$9第 1 到第 9 个位置参数$#位置参数的个数$所有位置参数每个参数独立$*所有位置参数作为一个整体$?上一条命令的退出状态码0 表示成功$$当前脚本的进程号 PID示例脚本args.sh#!/bin/bashecho脚本名称$0echo第一个参数$1echo第二个参数$2echo参数个数$#echo所有参数$echo上一条命令退出码$?运行./args.sh one two three预期输出脚本名称./args.sh 第一个参数one 第二个参数two 参数个数3 所有参数one two three 上一条命令退出码0注意事项$与$*不加引号时表现相同加双引号后$会把每个参数当作独立字符串而$*会把所有参数合并成一个字符串。迭代参数时建议用$。5. 条件判断5.1 数值比较使用[ ]等价于test命令或[[ ]]进行条件测试。数值比较运算符如下运算符含义-eq等于-ne不等于-gt大于-ge大于等于-lt小于-le小于等于示例check_score.sh#!/bin/bashscore85if[$score-ge90];thenecho优秀elif[$score-ge80];thenecho良好elif[$score-ge60];thenecho及格elseecho不及格fi预期输出良好5.2 字符串与文件判断字符串判断常用、!、-z长度为零、-n长度非零#!/bin/bashnameTomif[-z$name];thenecho名称为空elif[$nameTom];thenecho欢迎你Tomelseecho你好$namefi文件测试运算符非常实用常用于备份、部署脚本运算符含义-e文件或目录存在-f是普通文件-d是目录-r/-w/-x可读 / 可写 / 可执行-s文件非空-nt/-ot比另一个文件新 / 旧示例check_log.sh#!/bin/bashlogfile/var/log/syslogif[!-f$logfile];thenecho日志文件不存在$logfileelif[-s$logfile];thenecho日志文件存在且非空。elseecho日志文件为空。fi5.3 逻辑组合用与、||或、!非组合多个条件#!/bin/bashage30cityShanghai# 注意 [ ] 内部使用 -a / -o[[ ]] 内部可使用 / ||if[[$age-gt18$cityShanghai]];thenecho符合条件elseecho不符合条件fi最佳实践[ ]是命令[[ ]]是 Bash 关键字后者功能更强支持、||、正则匹配等且对空变量更安全推荐在 Bash 脚本中优先使用[[ ]]。但若要兼容/bin/sh则只能用[ ]。6. 循环结构6.1 for 循环最常用的遍历写法#!/bin/bashforlanginPython Java Bash Go;doecho我会使用$langdone预期输出我会使用 Python 我会使用 Java 我会使用 Bash 我会使用 GoC 语言风格的数值循环#!/bin/bashfor((i1;i5;i));doecho第$i次done6.2 while 和 until 循环while在条件为真时循环until在条件为假时循环即直到条件为真才停止#!/bin/bashcount1while[$count-le3];doechowhile 循环$countcount$((count1))donenum1until[$num-gt3];doechountil 循环$numnum$((num1))done预期输出while 循环1 while 循环2 while 循环3 until 循环1 until 循环2 until 循环36.3 遍历文件与命令输出这是 Shell 脚本在实际工作中最常用的场景之一#!/bin/bash# 批量备份当前目录下所有 .txt 文件forfilein*.txt;docp$file$file.bakecho已备份$file-$file.bakdone也可以遍历命令输出#!/bin/bashforuserin$(cut-d:-f1/etc/passwd);doecho系统用户$userdone注意事项$(...)返回的内容会按空格、制表符、换行进行分词若文件名包含空格会被拆开。更稳妥的做法是使用while read逐行读取例如#!/bin/bashfind.-name*.log|whileread-rfile;doecho处理文件$filedone7. 函数函数让脚本结构更清晰、代码可复用。定义与调用如下#!/bin/bash# 定义函数greet(){localname$1echo你好$name}# 调用函数并传参greet小明greet小红预期输出你好小明 你好小红函数参数通过$1、$2传递与脚本的位置参数相互独立。local关键字将变量限制在函数内部避免污染全局命名空间。一个更贴近实际的示例——计算目录大小与文件数#!/bin/bashdir_info(){localdir$1localcountlocalsizecount$(find$dir-typef|wc-l)size$(du-sh$dir|cut-f1)echo目录$dir文件数$count总大小$size}dir_info/etcdir_info/var/log最佳实践函数内部尽量使用local声明局部变量函数先定义后调用脚本主逻辑可封装在main()中便于阅读和维护。8. 输入输出与重定向8.1 标准输入、输出与错误Linux 中每个进程默认打开三个「流」流文件描述符默认目标标准输入 stdin0键盘标准输出 stdout1终端屏幕标准错误 stderr2终端屏幕重定向符号将标准输出写入文件覆盖将标准输出追加到文件2将标准错误写入文件21将标准错误合并到标准输出从文件读取标准输入或同时重定向标准输出和标准错误示例#!/bin/bashecho正常信息output.logecho追加信息output.logls/不存在的目录2error.log# 把正确和错误输出都写入同一个文件ls/etc /not_existall.log运行后查看catall.log预期输出近似/etc: ...目录内容列表... ls: 无法访问 /not_exist: 没有那个文件或目录8.2 静默输出与 /dev/null有些脚本如定时任务不需要输出任何内容可以把输出「丢进黑洞」/dev/null#!/bin/bashping-c1192.168.1.1/dev/null21if[$?-eq0];thenecho主机在线elseecho主机不可达fi注意事项21必须写在 /dev/null之后表示把 stderr 也指向 stdout 当前指向的目标。顺序颠倒则结果不同。9. 常见错误与调试技巧9.1 五类高频错误等号两边有空格name Tom会被 Shell 理解成「执行命令name参数是和Tom」。正确写法是nameTom。变量未加引号if [ -f $file ]在$file为空或含空格时可能出错应写成if [ -f $file ]。Shebang 拼写错误写成#/bin/bash或第一行有 BOM 头会导致脚本无法按预期解释器运行。忘记执行权限报Permission denied时先chmod x script.sh。||与|混淆|是管道传递数据||是逻辑或命令失败才执行。9.2 调试三件套在脚本开头添加以下选项可以大幅提升排错效率#!/bin/bashset-euopipefailset -e任何命令返回非零状态就立即退出避免错误被忽略。set -u使用未定义变量时报错防止拼写错误。set -o pipefail管道中任一命令失败则整个管道返回失败。set -x执行每条命令前先打印出来适合逐步调试。也可以在不修改脚本的情况下临时开启调试bash-xhello.sh预期会看到每条命令及其展开结果例如 echo Hello, Shell! Hello, Shell! date %Y-%m-%d最佳实践重要脚本推荐始终开启set -euo pipefail仅在开发排错时临时使用set -x避免正式日志噪音过大。10. 知识扩展Shell 的工作机制浅析许多初学者以为 Shell 脚本是「一行一行原样执行」其实 Shell 在真正执行之前做了不少准备工作。一次完整的命令解析大致经历以下阶段词法分析把整条命令拆分成单词识别引号、转义符、变量等。参数扩展展开$var、${var}、$(...)、$((...))等。通配符展开将*.txt等模式匹配成实际文件列表称为 globbing。重定向处理识别、、|等建立进程间数据通道。命令查找与执行判断是内置命令、函数、外部命令还是别名再交由内核fork/exec执行。了解这一流程能解释很多「反直觉」现象。例如为什么下面的脚本输出为空#!/bin/bashfind.-name*.txt|whileread-rf;don$((n1))doneecho文件数$n原因在于管道会启动一个子 Shell来运行while循环循环内对n的修改只存在于子进程中循环结束后父进程的n仍为空。解决办法是使用进程替换或把统计写进文件。这段历史也很有趣1971 年Ken Thompson 在贝尔实验室开发了第一版 Unix ShellThompson shell1977 年 Bourne Shell 随 Unix Version 7 发布奠定了现代 Shell 脚本的基础而 Bash 诞生于 1989 年由 Brian Fox 为 GNU 项目编写如今已成为开源世界最广泛使用的 Shell。理解这些底层机制和历史能帮助你更从容地驾驭 Shell 的各种「怪脾气」。11. 实战编写一个实用的系统巡检脚本综合运用以上知识我们来编写一个生产环境可用的小脚本system_check.sh用于检查磁盘、内存、CPU 负载和服务状态#!/bin/bashset-euopipefail# 阈值设置DISK_THRESHOLD80MEM_THRESHOLD80echo 系统巡检报告 echo巡检时间$(date%Y-%m-%d %H:%M:%S)echo主机名称$(hostname)echo# 1. 根分区使用率echo---- 磁盘使用情况 ----df-h/|awkNR2 {print $5, $6}# 2. 内存使用率echo---- 内存使用情况 ----free-m|awkNR2 {printf 已用 %.0f%%\n, $3/$2*100}# 3. 系统负载echo---- 系统负载 ----uptime|awk-Fload average:{print 近1分钟负载$2}# 4. 检查关键服务echo---- 服务状态 ----forsvcinsshnginx;doifsystemctl is-active--quiet$svc2/dev/null;thenecho$svc运行中elseecho$svc未运行或不存在fidoneechoecho 巡检结束 给它加上执行权限并运行chmodx system_check.sh ./system_check.sh预期输出示例 系统巡检报告 巡检时间2026-09-08 12:47:24 主机名称web-server-01 ---- 磁盘使用情况 ---- 23% / ---- 内存使用情况 ---- 已用 56% ---- 系统负载 ---- 近1分钟负载 0.35, 0.28, 0.21 ---- 服务状态 ---- ssh运行中 nginx运行中 巡检结束 你还可以把它加入crontab每天定时执行并把结果写入日志# 编辑定时任务crontab-e# 每天 8:30 执行并记录日志308* * * /home/user/system_check.sh/var/log/system_check.log2112. 最佳实践小结注释要适可而止关键逻辑写清楚「为什么」而不仅仅是「做了什么」。变量加引号绝大多数情况下使用$var防止空值或含空格出错。开启安全选项正式脚本使用set -euo pipefail。优先[[ ]]在 Bash 中比[ ]更安全、功能更强。路径不写死可用变量或参数传入提高可移植性。退出码规范脚本成功返回 0不同错误返回不同非零码便于调用方判断。命名清晰脚本、变量、函数名应见名知义如backup_database.sh、log_dir。13. 总结与练习本文从环境准备出发带你走完了「创建脚本 → 运行脚本 → 变量 → 条件判断 → 循环 → 函数 → 输入输出重定向 → 调试排错 → 实战巡检」的完整路径。Shell 脚本的精髓不在于记住所有语法而在于用自动化思维去审视重复性工作凡是要敲两遍以上的命令都可以考虑写进脚本。练习题批量改名工具编写脚本将当前目录下所有.html文件重命名为.htm例如a.html变为a.htm。提示使用for循环遍历*.html配合${file%.html}去除后缀。简易计算器编写支持 - x /四则运算的脚本要求./calc.sh 3 5输出8除数为 0 时给出友好提示。提示使用$1、$2、$3接收参数用case或if判断运算符除法注意-eq 0判断。备份脚本编写脚本将指定目录打包成以日期命名的.tar.gz文件并自动创建备份目录若不存在。提示使用date %Y%m%d、mkdir -p、tar -czf和$1参数。磁盘告警编写脚本当根分区使用率超过 80% 时输出警告信息否则输出「空间充足」。提示用df -h /配合awk提取百分比去除%后与 80 比较。日志清理编写脚本删除/var/log/myapp/下 7 天前的.log文件并输出删除了哪些文件。提示使用find /var/log/myapp -name *.log -mtime 7先打印再删除避免误删。建议从「磁盘告警」和「批量改名」这类输入输出明确的小任务开始逐步挑战「备份脚本」和「日志清理」在动手实践中真正内化 Shell 的思维模式。
返回列表