Linux Shell编程基础语法:从零到自动化脚本实战

发布时间:2026/10/14 23:44:25
Linux Shell编程基础语法:从零到自动化脚本实战 在Linux系统学习这条路上shell编程的基础语法几乎是绕不开的第一道门槛。不管你是运维、后端开发还是单纯想提升工作效率只要天天跟服务器打交道迟早会撞上“手工敲命令敲到怀疑人生”的瞬间。我当初第一次意识到shell脚本的价值是在某次需要批量处理上百个日志文件的时候——一个个手敲命令、复制粘贴折腾到半夜后来被同事一句“你写个for循环不就行了”点醒。从那以后我才真正开始系统性地学习shell编程的基础语法也踩了不少坑。这篇文章就把我入门阶段梳理出来的核心语法和实操经验整理出来希望能帮你少走弯路。1. 先搞清楚shell编程到底是什么为什么非学不可1.1 从命令解释器到脚本语言很多人第一次接触Linux都是从终端敲命令开始的。你敲一个ls按下回车屏幕上列出文件敲一个cd /etc当前目录就切换过去了。这背后起作用的其实就是一个叫做shell的程序。它负责把你输入的命令翻译给操作系统内核听然后把执行结果反馈给你。可以把它理解成一个翻译官一端连接人类用户一端连接系统内核。而shell编程说白了就是把一条条命令按照一定的逻辑结构组织起来写进一个文本文件里然后让shell逐行执行。这个文本文件就是脚本文件。你可以把它看作一份“待办清单”把平时手动做的事情写进去之后只要运行一次所有步骤自动完成。区别于即时交互的命令行操作脚本让重复性的工作有了批量处理和自动化流转的可能。1.2 哪些场景最适合用shell脚本不是所有任务都适合用shell来做。根据我自己的经验它最适合这几类场景批处理与自动化任务比如批量重命名文件、批量压缩日志、批量同步配置。系统管理与监控检查磁盘空间、监控进程状态、定时备份数据库。文本处理与分析结合grep、sed、awk这些神器从日志中提取关键信息。快速原型与辅助工具把一套固定的部署步骤固化成脚本团队共享统一操作标准。如果任务本身涉及大量复杂的业务逻辑、数据结构处理那更适合用Python或Go这类高级语言。shell的强项在于“把系统命令像积木一样搭起来”轻量、直接、依赖少。学习时抓住这个定位方向就不会偏。1.3 我建议的学习思路我见过不少人一上来就抱着几百页的语法手册啃效果反而不好。shell语法其实知识点很分散纯靠背很容易忘。我的建议是先搭一个最小知识框架把变量、条件、循环、函数这几根主梁立起来然后马上拿一个小需求练手比如写一个清理临时文件的脚本再逐渐往里加功能。带着问题学记忆要牢固得多。这篇文章的结构也完全是按照我当时入门时“先框架、后细节、再实战”的顺序来组织的。2. 准备工作环境、脚本结构与执行方式2.1 先确认你的shell环境Linux系统里常见的shell有好几种比如bash、zsh、sh、fish等。目前绝大多数主流发行版默认使用的都是Bash (Bourne Again Shell)它功能强大兼容性好是学习shell编程最稳妥的起点。你可以通过下面的命令查看当前系统使用的shellecho $SHELL如果输出类似/bin/bash说明当前默认shell就是bash。另外可以用ls -l /bin/sh看一下系统默认的/bin/sh指向谁有些发行版中它指向dash跟bash存在一些细微差异。初学阶段直接在脚本里显式指定bash解释器即可这样能避开很多兼容性问题。2.2 脚本文件的创建与权限脚本本质上就是一个普通的文本文件但内容有特定格式。创建它用任何编辑器都行我习惯用vim但刚开始用nano或gedit也没问题。文件名一般以.sh结尾方便人和工具识别。一个最基础的脚本文件是这样开头的#!/bin/bash # 这是注释描述脚本的作用 echo Hello, Shell!第一行#!/bin/bash叫shebang它告诉系统当这个脚本需要直接执行时使用/bin/bash这个解释器来运行它。这一行不是必需的——如果你用bash script.sh的方式运行脚本系统会直接用bash解释不会看第一行——但我强烈建议每个脚本都写上。因为它既能明确脚本的运行环境也是脚本能够“直接执行”的前提。写好脚本后需要给它添加可执行权限chmod x test.sh这一步是关键。我最初就因为没有加执行权限直接./test.sh运行结果系统提示Permission denied当时还一脸懵。2.3 三种常见执行方式对比执行方式命令是否需执行权限是否开新子shell常用场景解释器直接运行bash test.sh不需要是最通用推荐初学者使用路径直接执行./test.sh需要是脚本已配置好权限时使用source方式执行source test.sh或. test.sh不需要否希望脚本修改当前环境时使用这里特别说一下第三种source和直接执行最大的区别在于直接执行脚本是在子shell中完成的脚本里设置的变量和环境在脚本结束后不会影响当前终端而source是在当前shell进程中执行的脚本里导出的环境变量会保留下来。比如你想通过脚本临时设置JAVA_HOME就必须用source方式执行否则你会发现脚本跑完了环境变量毫无变化。2.4 脚本的基本结构规范一个规范可读的脚本通常包含这几部分#!/bin/bash # 作者信息、创建时间、脚本功能描述 # 变量定义区 LOG_DIR/var/log/myapp BACKUP_DIR/backup # 函数定义区 function log_info() { echo [INFO] $(date %Y-%m-%d %H:%M:%S) - $1 } # 主逻辑区 log_info 开始执行备份任务 # ... 实际命令 log_info 备份任务结束注释用#开头。不要小看注释的作用shell脚本因为不需要编译逻辑一旦多了很容易看不懂尤其是过两个月再回头看自己写的脚本没有注释几乎等于天书。我个人的习惯是每个脚本必须有头部注释说明用途和用法每个函数必须有注释说明入参和返回值关键命令前后必须有一行注释说明意图。3. 核心基础语法详解变量、运算与判断3.1 变量定义、引用与作用范围shell里的变量不需要像C或Java那样先声明类型你在使用中直接赋值即可。最基本的形式是namezhangsan age28这里有一个初学者最容易踩的坑等号两边绝对不能有空格。写name zhangsan会报错因为shell会把name当成一个命令去执行然后拿着和zhangsan当参数传给它。我第一次碰到这个错误时检查了好几遍都没发现问题。变量引用有两种写法echo $name echo ${name}推荐使用带花括号的$ {name}写法。为什么看这个例子如果变量名后面紧跟着其他字符比如$name_logshell会把它解析成名为name_log的变量而不是变量name加上字符串_log。用$ {name}_log就能明确定义边界避免歧义。在拼接路径或者字符串时这个细节非常重要。变量还可以分为普通变量和环境变量。普通变量只在当前shell进程中有效而环境变量可以通过export命令导出传递给子进程。比如export MY_ENVhello ./other_script.sh上面这条命令中other_script.sh运行时就能通过$MY_ENV读取到hello。另外还有几个特殊的预定义变量属于高频使用# $0 当前脚本的文件名 # $# 传递给脚本的参数个数 # $1~$9 按位置传递的参数 # $ 所有参数的独立列表 # $* 所有参数的合并字符串 # $? 上一条命令的返回状态码0表示成功 # $$ 当前脚本的进程ID其中$?是判断命令是否执行成功的重要工具。写脚本时经常要判断上一条命令有没有出错比如文件不存在、权限不足等就是靠它来把握分支走向。3.2 字符串处理与引号的秘密shell里字符串几乎无处不在。初学者必须理解单引号、双引号和反引号或$()的区别这是很多诡异bug的来源。单引号里的所有字符都会被原样保留不做任何替换namezhangsan echo 我的名字是 $name # 输出我的名字是 $name双引号里的内容会进行变量替换和命令替换namezhangsan echo 我的名字是 $name # 输出我的名字是 zhangsan需要传达给读者的经验是能用双引号就用双引号。尤其是变量可能包含空格的情况如果不用双引号包裹变量会被拆分成多个单词。举例来说path/my dir/log cp $path /tmp # 错误会被拆成 /my dir/log 三个参数 cp $path /tmp # 正确这在处理带空格的文件名或目录时是血泪教训。命令替换允许你把一条命令的输出结果赋值给变量有两种写法today$(date %Y%m%d) # 推荐 todaydate %Y%m%d # 旧式写法推荐使用$()因为反引号内部的转义规则更复杂可读性也差一些。命令替换是shell脚本灵活性的核心来源之一——先执行命令拿到结果再交给后面的逻辑去使用。3.3 算术运算整数为主浮点需要借助外部工具shell的算术运算看起来简单但有些细节需要留意。bash内置的算术运算支持整数不支持浮点数。基础写法有这几种a10 b3 echo $((a b)) # 13推荐 echo $[a b] # 13旧写法可读性稍差 expr $a $b # 13使用外部命令expr注意操作符和参数之间必须有空格 let cab # 将计算结果赋值给c使用$((...))是bash内置的机制支持 - * / %各种运算符也支持、--自增自减。注意乘法符号*在算式里不需要转义但在expr的写法里必须写成\*。如果需要浮点运算bash本身搞不定常见做法是用bc或者awkecho scale2; 10 / 3 | bc其中scale2表示保留两位小数。也可以用awkawk BEGIN { printf %.2f\n, 10 / 3 }这种场景在写监控脚本比如计算磁盘使用率百分比时特别常见。3.4 条件判断if、test 与 [ ]、[[ ]] 的取舍条件判断是脚本逻辑的核心。基础语法为if [ 条件 ]; then # 满足条件时执行 elif [ 另一个条件 ]; then # 满足另一个条件时执行 else # 都不满足时执行 fi注意末尾的fi这是“if”倒过来拼的用来结束整个判断块。很多人第一次写shell在结尾忘了写fi结果语法报错这就是shell语法的特色——它的块结束标记通常是起始单词的倒写。条件表达的核心是test命令。[ ... ]实际上就是test命令的别名形式方括号里面必须留空格。读代码时会发现很多人写if [ $param yes ]; then这里方括号和参数之间的空格一个都不能少。然后就是[ ]和[[ ]]之间的差别我强烈建议新手直接掌握[[ ]]。它在bash中是一个增强版的测试关键字支持比[ ]更丰富的特性if [[ $name admin $age -gt 18 ]]; then echo 管理员且成年 fi在[[ ]]中和||可以直接用来组合条件而[ ]里必须写成-a和-o或者写成多个括号拼接。另外[[ ]]还支持正则匹配if [[ $file *.log ]]; then echo 日志文件 fi这一点在文件名检查和参数校验中非常实用。常见条件类型可以分类记忆文件测试-f 文件判断是否为普通文件-d判断是否为目录-r、-w、-x判断权限-e判断是否存在。字符串测试-z判断为空-n判断为非空 或 判断相等!判断不相等。数值比较-eq相等-ne不等-gt大于-ge大于等于-lt小于-le小于等于。特别注意数值比较不能写成[[ $a $b ]]因为在数值环境里会被当成字符串重定向操作符产生意想不到的行为。判断数值大小一定用-gt这类参数。3.5 循环for、while、until 与流程控制for 循环有两种主流写法写法一遍历列表for ip in 192.168.1.1 192.168.1.2 192.168.1.3; do echo 检查 $ip done列表项也可以是命令替换的结果比如遍历某个目录下所有的.txt文件for file in /tmp/*.txt; do echo 处理文件$file done写法二C语言风格的计数循环for ((i1; i10; i)); do echo 第 $i 次循环 donewhile 循环适合条件控制的场景count1 while [ $count -le 5 ]; do echo 当前计数$count ((count)) done这里我用((count))来做自增比count$(($count1))简洁不少。循环控制上break终止整个循环continue跳过本次循环进入下一次迭代。常见的一个坑是死循环导致服务器负载飙升。如果你写的循环条件永远为真脚本就会一直跑下去。比如while true; do echo 疯狂输出 sleep 1 # 如果忘了这一句CPU会被吃满 done写循环建议在可能无限的循环里加上sleep或者设置好退出条件这也是个良好习惯。3.6 函数定义、传参、返回值与作用域函数可以把重复使用的逻辑封装起来。语法有两种形式function say_hello() { echo Hello, $1 } say_bye() { echo Goodbye, $1 }function关键字可省略。调用方式跟执行普通命令一样say_hello zhangsan函数里的$1、$2代表传给函数的第一个、第二个参数注意它们和脚本级的位置参数是两回事——脚本有脚本的$1函数里也有自己的$1互不冲突。如果想在函数里拿到整个脚本的参数可以用$0、$#等但如果函数内部使用了$1脚本参数就会被遮蔽要读取原始参数需要在调用函数前先保存。函数的返回值通过return关键字和C语言不同bash里return只能返回0~255的整数通常用来表示成功0或失败非0。如果函数需要返回一个字符串或更大的数字一般用echo输出外部用$()捕获function get_dir_size() { du -sh $1 | awk {print $1} } size$(get_dir_size /var/log) echo 日志目录大小$size这是一个非常经典的模式函数输出、调用捕获。变量的作用域也需要留意。shell函数里的变量默认是全局的即使你在函数内部赋值函数外也能读到。如果希望变量只在函数内部有效要用local关键字function test_func() { local local_var我是局部变量 }写脚本时如果函数逻辑复杂变量多最好都用local声明避免跟全局变量互相污染。3.7 数组普通数组和关联数组数组在shell里用得相对少但会了之后能解决不少特定问题。普通数组的初始化与访问# 定义数组 fruits(apple banana orange) # 访问单个元素 echo ${fruits[0]} # apple # 获取所有元素 echo ${fruits[]} # 获取元素个数 echo ${#fruits[]} # 遍历数组 for fruit in ${fruits[]}; do echo 当前水果$fruit done关联数组键值对需要先声明declare -A scores scores[math]90 scores[english]85 echo ${scores[math]} # 90关联数组在实现类似“配置映射表”的场景非常有用比如根据环境名找到对应的IP地址。数组的一个重要细节是访问所有元素时${fruits[]}最好加上双引号这样每个元素即使包含空格也不会被错误拆分成多个字段。遍历含空格的文件名时就靠这个保护。4. 实操一步步写一个日志清理脚本4.1 需求设计与拆解基础知识铺得差不多了如果不拿一个完整的例子串一遍总觉得隔层纱。我从实际运维中挑一个非常典型的需求来演示一个日志清理脚本。需求描述某服务每天在/var/log/myapp/目录下产生大量日志文件文件名格式为app_20240101.log。现在需要删除指定天数比如7天前生成的日志文件并输出清理结果。这个需求拆解一下涉及的知识点正好覆盖了变量定义、命令替换、条件判断、循环或find命令、函数封装、日志输出。要完成它你可以先确认目录是否存在再找出匹配目标天数的文件然后执行删除最后输出统计结果。4.2 脚本思路确认在设计时我习惯先用注释把逻辑框架写出来再填充具体命令。因为shell语法相对松散直接开写容易漏分支#!/bin/bash # 日志清理脚本 # 用法./clean_logs.sh [目录] [保留天数] LOG_DIR/var/log/myapp KEEP_DAYS7 # 1. 检查目录是否存在 # 2. 查找 N 天前修改的 .log 文件 # 3. 输出待删除文件数量 # 4. 删除文件 # 5. 输出清理完成信息4.3 完整脚本与关键步骤解释下面是我在文章结构中强调过的完整版本已经做过参数校验和模拟运行保护#!/bin/bash # 功能清理指定目录下的过期日志文件 # 用法./clean_logs.sh [日志目录] [保留天数] set -u # 使用未定义变量时直接报错避免静默出错 # 参数处理支持命令行传入未传则使用默认值 LOG_DIR${1:-/var/log/myapp} KEEP_DAYS${2:-7} # 函数定义带时间戳的日志输出 function log_info() { echo [$(date %Y-%m-%d %H:%M:%S)] $1 } # 第一步检查目录是否存在 if [ ! -d $LOG_DIR ]; then log_info 错误目录 $LOG_DIR 不存在 exit 1 fi # 第二步查找 N 天前修改的日志文件 # 使用 find 命令的 -mtime 参数 log_info 开始扫描目录 $LOG_DIR 中 $KEEP_DAYS 天前的日志文件... # 使用 find 收集需要清理的文件列表到变量 OLD_FILES$(find $LOG_DIR -type f -name *.log -mtime $KEEP_DAYS) # 第三步如果没有匹配到文件直接退出 if [ -z $OLD_FILES ]; then log_info 没有需要清理的日志文件 exit 0 fi # 第四步统计文件数量并逐一删除 FILE_COUNT$(echo $OLD_FILES | wc -l) log_info 本次共清理 $FILE_COUNT 个文件 for file in $OLD_FILES; do log_info 删除文件$file rm -f $file done # 第五步结束 log_info 日志清理任务完成这一步一个细节让我印象很深LOG_DIR${1:-/var/log/myapp}这种写法叫参数默认值替代当你不传第一个参数时变量自动使用默认值。实测下来这种参数处理方式非常实用既保持了脚本的灵活性又避免冗余的判断逻辑。4.4 运行与验证保存脚本后依次执行chmod x clean_logs.sh # 使用默认参数运行 ./clean_logs.sh # 指定目录和保留天数运行 ./clean_logs.sh /var/log/nginx 14运行结果会类似[2024-03-01 10:30:00] 开始扫描目录 /var/log/myapp 中 7 天前的日志文件... [2024-03-01 10:30:01] 本次共清理 12 个文件 [2024-03-01 10:30:01] 删除文件/var/log/myapp/app_20240220.log [2024-03-01 10:30:01] 删除文件/var/log/myapp/app_20240219.log第一次写这个脚本时我犯过一个较为隐蔽的错误find命令的-mtime 7含义是“超过7天”而不是“正好第7天”。两者的边界条件有着明确区别-mtime 7包含的是超过7×24小时之前修改的文件而-mtime 7匹配的是7~8天之间这个范围。如果任务要求严格定义“保留7天”最好用-mtime 7配合-mtime -8做区间限定或者直接用! -newermt 7 days ago语义更清晰。你可以先在小范围目录里放几个已知日期的测试文件跑一遍脚本确认行为符合预期再上生产环境。学习shell的整个过程这种“先小范围验证再扩大范围”的习惯能帮你省下很多麻烦。5. 常见问题与排查技巧实录5.1 脚本无法执行Permission denied这个错误绝大多数情况下是文件没有执行权限。解决办法chmod x script.sh ./script.sh还有一种可能脚本第一行#!/bin/bash写错了路径或者文件保存时带了 Windows 换行符CRLF导致系统找不到正确的解释器。判断方法很简单用file命令查看file script.sh如果输出中包含with CRLF line terminators说明行尾格式有问题。可以用sed -i s/\r$// script.sh或者dos2unix script.sh转换。跨平台编辑脚本时这是一个很典型的坑。5.2 变量赋值总是报错如果你写了类似这样的代码name zhangsanbash会提示name: command not found或类似的错误。原因前面提过了等号两边不能有空格。这是shell语法中约定俗成的规定和大多数语言都不一样刚转过来的同学常常在这翻车。另外也要注意值中的引号是否匹配尤其是值本身就包含空格时一定要用引号包裹。5.3 判断语法报错syntax error near unexpected token这种报错通常出在条件语法的结构上。最常见的几个原因漏写then或fiif和一个方括号之间没有空格方括号内部左右各少了一个空格多个条件组合时用错了符号比如在[ ]里直接写正确做法是-a或拆成两个[ ]经验之谈当你看到syntax error near unexpected token fi这类提示时不要只盯着fi所在行往上检查离它最近的if、for、while、case等结构十有八九是那个开头块没闭合或者条件表达式写错了。5.4 脚本执行结果和预期不一致变量被截断比如你写echo 文件长度是: $sizeKB$sizeKB会被解析成变量sizeKB而不是变量size拼上字符串KB输出结果是空的。解决办法就是用花括号${size}KB。我自己习惯在变量名和字符串紧密相邻时一律用${var}写法能极大降低这类bug概率。5.5 字符串比较中常见的括号混用初学阶段很容易把比较操作符打错比如if [ $a $b ] # 在 [ ] 里也一样可用但不是 POSIX 标准 if [[ $a $b ]] # 推荐还有数字比较时if [ $num -gt 10 ] # 正确 if [[ $num 10 ]] # 注意bash中在 [[ ]] 里 是字符串比较需要用 -gt5.6 脚本中途报错却继续执行bash默认不会因为某条命令失败就停止整个脚本。这意味着如果脚本里有一步失败了后面的步骤可能还在继续运行造成更严重的问题。解决思路有两个方法一在脚本开头加set -e只要任何一条命令返回非零状态整个脚本立刻退出set -e方法二在关键命令后手动检查返回码if ! cp $src $dst; then echo 复制失败终止脚本 exit 1 fi但set -e也不是万能灵药某些命令失败但返回值是0或者命令放在if条件里会让set -e暂时失效。建议在脚本关键路径上做显式判断而不是完全依赖set -e。5.7 一个实用的调试工具shell脚本没有IDE那种图形化调试器但bash -x是一个非常实用的调试方式bash -x script.sh它会打印脚本执行的每一步展开每条命令显示变量赋值后的实际值。我排查脚本问题时第一步永远是跑bash -x几乎能定位70%的逻辑问题。如果脚本太长也可以在局部开启调试在脚本里用set -x打开、set x关闭只输出某一段的调试信息。6. 学习体会与下一步建议回顾我学习shell编程的过程最大的体会是这门语言的语法糖不多但每个细节都很磨人。刚起步时觉得枯燥看变量、引用、条件判断都是些零碎规则但当你真正去写一个能解决问题的脚本时这些知识就自动串起来了。我个人强烈建议看完这篇文章之后自己从零写一个能跑的脚本哪怕是最简单的“列出当前目录下最大的5个文件”也行。写完、跑通、再扩展这个循环比看十篇文章都管用。然后可以逐渐往这些方向深入文本处理三剑客grep、sed、awk、命令行参数解析getopts、任务定时管理crontab、进程与信号控制trap这些都是shell编程进阶路上很自然的下一个站点。最后送上一句我从实战中总结的话shell脚本不怕写得慢就怕逻辑不清晰。写之前先想清楚“输入是什么、输出是什么、中间分几步”多写注释多用函数你的脚本质量和可维护性都会好一大截。希望这篇文章对你在Linux系统学习路上的第一步有所帮助。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询