尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Linux Shell脚本零基础实战指南:从Bash概念到调试排查全解析

Linux Shell脚本零基础实战指南:从Bash概念到调试排查全解析 刚接触 Linux 的时候我第一个想搞明白的事情就是“怎么把一串命令存下来下次一键执行”。那时候看同事在终端里敲几下就自动完成一堆部署操作羡慕得不行。后来才知道那不是什么黑魔法就是 Shell 脚本说白了就是把命令按顺序写进一个文件里然后让 Bash 帮你批量执行。这篇教程就是给真正零基础的人准备的。我带你从“什么是 Shell 脚本”开始一路写到能创建、赋权、运行、调试自己的 Bash 脚本中间会穿插大量我实际踩过的坑和一些藏在文档之外的小技巧。适合刚装好 Linux 虚拟机、想提升工作效率的开发者也适合正在准备面试、需要系统补一遍脚本基础的朋友。1. 动手之前先搞清楚 Shell 脚本到底是什么很多教程上来就让你vim test.sh然后写个echo就跑结果读者一头雾水。咱不那样先花几分钟把基础概念捋顺后面的路会好走很多。1.1 内核、Shell 和终端的关系你打开终端窗口看到的那个黑色界面叫终端模拟器比如 GNOME Terminal、Konsole它只是个门面。真正的“壳”是 Shell它是用户和 Linux 内核之间的翻译官。你敲的每一条命令都要经过 Shell 解释翻译成内核能理解的系统调用内核才去操作硬件来完成工作。BashBourne Again Shell是目前几乎所有 Linux 发行版默认的 Shell也是我们这篇教程的主角。你可以敲echo $SHELL看看当前用的什么绝大多数人输出的都是/bin/bash。除了 Bash还有 Zsh、Fish、Sh 这些但万变不离其宗Bash 学会了你就是走到哪儿都能上手干活。1.2 什么是 Shell 脚本它解决了什么问题Shell 脚本就是把多条要在终端执行的命令按逻辑顺序写进一个纯文本文件里。它可以包含普通命令也能包含变量、条件判断、循环、函数、算术运算可以说是“揉进了编程语法的命令清单”。为什么要用脚本我打个比方你每天上班都要干的固定流程——打卡、开电脑、打开浏览器、打开开发工具、拉取最新代码。如果你把这一串动作录成一个脚本一条命令全搞定每天能省下几分钟。放到服务器运维里部署应用可能要敲几百条命令一条条手工敲又慢又容易出错敲错一个字符可能就崩了写成脚本一次执行、随时复用效率和准确性直接天翻地覆。1.3 Bash 脚本与直接敲命令的区别直接敲命令就像打电话时一字一句地说话脚本则像提前写好一封信。区别在于可复用性脚本是文件存着不会丢下次直接跑可维护性改了文件再跑一遍不用重敲所有命令逻辑能力终端里敲命令只能一条条来脚本里可以用if、for、while控制执行流程有判断、有循环自动化能力脚本可以被定时任务cron调用到点自动执行不用人守着。2. 创建你的第一个 Bash 脚本全流程实操现在开始动真格的。我以一个“查看系统状态”的脚本为例子顺带把目录切换、文件编辑这些基本功也过一遍。2.1 准备工作打开终端确认环境先打开终端执行bash --version确认 Bash 可用。如果提示找不到命令那你的系统可能比较特殊用包管理器装一个就行Debian/Ubuntu 用sudo apt install bashRHEL/CentOS 用sudo yum install bash。不过实话说Linux 上没装 Bash 的概率极低。建议先建一个专门放练习脚本的目录mkdir -p ~/shell_practice cd ~/shell_practicemkdir -p的意思是“创建目录如果父目录不存在就一并创建”cd是切换目录。这俩命令以后你会天天用。2.2 用 vim 创建脚本文件遇到 vim 别慌创建文件的方式很多touch能建空文件cat也能重定向写入我推荐直接用 vim/nano 一步到位。Vim 初次上手容易懵记住核心三步就行vim mysystem.sh按i进入插入模式左下角会出现 INSERT 字样然后输入以下内容#!/bin/bash # 第一个脚本查看系统基本信息 echo 当前用户: $(whoami) echo 当前目录: $(pwd) echo 系统运行时间: $(uptime -p) echo 内存使用情况: free -h echo 磁盘使用情况: df -h | grep ^/dev/输完后按Esc退出插入模式输入:wq回车保存退出。:w是写入:q是退出连起来就是“保存并退出”。如果你中途改主意不想存了:q!是强制不保存退出。2.3 脚本第一行#!的讲究每个 Bash 脚本第一行基本都是#!/bin/bash这一行叫shebang也叫释伴行。它告诉系统“这个文件要用哪个解释器来执行。”你写#!/bin/bash系统就调用 Bash 执行这个文件写#!/usr/bin/env python3就调用 Python 3。有人会问写作#!/usr/bin/env bash和#!/bin/bash哪个好env的好处是它会去PATH环境变量里找 bash 的位置兼容性更好直接写路径则少一层查找速度略快。在绝大多数 Linux 发行版上两者效果一样新手用#!/bin/bash就够了。2.4 即时执行 vs 给文件加执行权限写完脚本后最直接的方式是交给 Bash 解释器执行bash mysystem.sh这样不用管文件有没有执行权限因为你是显式调用 Bash 来读取并执行这个文件。对应地还有另一种方式chmod x mysystem.sh ./mysystem.shchmod x给文件加上执行权限然后通过./前缀执行当前目录下的脚本。刚才我一直强调要加./这是个新手特别容易踩的坑——如果你直接敲mysystem.sh系统会报command not found因为它只会在PATH环境变量指定的目录里找命令不会搜索当前目录这是安全设计不给当前目录特殊待遇防止恶意脚本被执行。./就是明确告诉 Shell“在当前目录下找这个文件”。两种执行方式的选择我的建议是调试期用bash方式方便且不用管权限正式使用时给它加执行权限并用./执行这样脚本更像一个“真正的程序”也能让 shebang 行起到作用。2.5 三种执行方式对比bash、source、执行权限这里再展开讲一个细节。执行脚本常见的有三种方式很多人混淆方式命令示例是否开启新子Shell能否修改当前Shell的变量直接执行./script.sh是否Bash执行bash script.sh是否Source执行source script.sh或. script.sh否是前两种都会启动一个新的子 Shell 去跑脚本脚本里设置的环境变量、切换的目录只对子 Shell 生效脚本跑完就没了。source则是在当前 Shell 进程内直接执行脚本里的cd真的会改变你当前的目录定义的变量也会留在当前 Shell 里。这个特性特别有用。比如你改了.bashrcShell 启动时加载的配置文件直接在当前会话里让它生效的经典操作是source ~/.bashrc如果这时候笨笨地执行~/.bashrc并且它有执行权限大概率不会生效因为它在子 Shell 里跑了改不了当前会话。3. 把脚本写“活”变量、输入与命令行参数的实战用法只学echo那不叫编程。一个能解决问题的脚本必然能接收输入、保存中间结果、根据情况执行不同分支。这一节是 Bash 脚本的核心语法地带我挑最常用的讲。3.1 变量定义、引用和常见坑Bash 里定义变量非常随意name张三 echo 你好, $name注意两点。第一等号两边不能有空格name 张三是错的Shell 会把name当成一个命令去执行。第二引用变量加$最好用花括号包住边界比如fruitapple echo 我最喜欢 ${fruit}s如果你写成$fruitsShell 会去找名叫fruits的变量结果当然是空的。加花括号是最安全的习惯尤其是变量后要紧跟着其他字符时。3.2 位置参数像命令一样接收输入脚本可以像命令一样接收参数。$1是第一个参数$2是第二个$0是脚本本身的名字$#是参数个数$是所有参数列表。写个示例#!/bin/bash echo 脚本名: $0 echo 第一个参数: $1 echo 第二个参数: $2 echo 参数总数: $#保存运行bash args_demo.sh hello world输出脚本名: args_demo.sh 第一个参数: hello 第二个参数: world 参数总数: 2有个正经的场景你想写个备份脚本备份路径和备份文件名每次不一样那就让它接收参数#!/bin/bash # usage: ./backup.sh /home/user/data /backup src_dir$1 target_path$2 tar -czf $target_path.tar.gz $src_dir echo 备份完成: $target_path.tar.gz这样你就不需要为每个备份任务改代码传参就行。3.3 与用户交互read 命令有时候脚本需要现场问用户问题用read#!/bin/bash read -p 请输入你的名字: username echo 欢迎你, $username-p是 prompt 的缩写后面跟提示文本读到的值存进username变量。这在写交互式安装脚本时非常常用比如确认“是否继续[y/N]”。3.4 条件判断 if让脚本有“脑子”Bash 里的条件判断写法略微古怪容易让新手吓一跳。基础结构是if [ 条件 ]; then 分支1 else 分支2 fi注意[和]两边必须有空格then前要有分号或换行最后必须用fi结尾倒着写的 if。完整的例子#!/bin/bash if [ -f /etc/os-release ]; then echo 这个系统有 os-release 文件 else echo 没找到 os-release 文件 fi-f是“是不是普通文件”的测试条件还有一堆常用的测试操作符我这儿列几个现在就能记住的-f 文件是否是普通文件-d 路径是否是目录-e 路径路径是否存在-z 字符串字符串为空-n 字符串字符串非空字符串1 字符串2判断相等数字1 -gt 数字2数字大于-lt 小于-eq 等于条件还是多条件组合的话用与和||或比如if [ -d $dir ] || [ -w $dir ]; then echo 目录存在或可写 fi3.5 for 循环处理列表数据的利器这是被问得最多、也是自动化里最核心的内容之一。比如要批量给一组服务器目录建日志文件#!/bin/bash for server in web01 db01 cache01; do echo 正在处理: $server mkdir -p /var/log/${server} done循环遍历当前目录下的所有.txt文件for file in *.txt; do echo 找到文件: $file done经典的“1 到 10”循环用到大括号展开for i in {1..10}; do echo 第 $i 次 done还有 C 语言风格的 for 写法for ((i1; i10; i)); do echo count: $i done我在实际中用的最多的场景是“批量重命名文件”“批量为多个 IP 发 ping 探测”“批量检查远程端口”。3.6 while 循环和函数进一步抽象逻辑while适合你不知道具体循环次数、靠条件来控制的场景counter1 while [ $counter -le 5 ]; do echo 计数器: $counter ((counter)) done((counter))是算术语法让变量自增 1。新手经常忘记 Bash 的算术运算要用$(( ))或(( ))包裹直接写counter$counter1会得到字符串拼接而不是相加。函数则能把常用逻辑包起来让脚本更清晰#!/bin/bash function log_info() { echo [$(date %Y-%m-%d %H:%M:%S)] $1 } log_info 开始执行 log_info 完成执行这里$1是函数参数不是脚本参数date命令配合%Y-%m-%d %H:%M:%S格式化当前时间输出的日志自带时间戳排查问题会很舒服。4. 脚本跑不起来的那些糟心事常见 Bug 和排查心得这一节是大家最喜欢的“排雷”环节。说真的我见过太多人脚本逻辑写对了却被一些奇奇怪怪的小问题卡一下午最后发现是个不可见字符的锅。4.1 经典错误/bin/bash^M: bad interpreter: No such file or directory这个错误极其经典。你在 Windows 上用记事本编辑了脚本然后传到 Linux 上执行大概率就报这么一句。原因是 Windows 的换行符是\r\nLinux 的是\n脚本第一行变成了#!/bin/bash\r系统找不到叫bash\r的解释器。解决方案sed -i s/\r$// yourscript.sh这行命令会把每行末尾的\r清理掉。-i是原地修改文件s/\r$//是替换掉行尾的\r。要是你经常要处理 Windows 和 Linux 之间的文本转换装个dos2unix工具一键转换也行sudo apt install dos2unix dos2unix yourscript.sh4.2 报错command not found但你明明装了这个分两种情况。第一种脚本里调用的命令确实不在PATH环境变量里。比如你刚刚自己编译安装了一个软件它的可执行文件放在/usr/local/bin而PATH没包含这个目录自然找不到。查看 PATHecho $PATH临时添加export PATH$PATH:/usr/local/bin永久生效就把这行写进~/.bashrc再source ~/.bashrc。第二种情况是执行crontab -e却报-bash: crontab: command not found。这有两个可能一是你装的系统是精简版连 cron 都没装Debian/Ubuntu 用sudo apt install cronCentOS/RHEL 用sudo yum install cronie二是你登录的用户 PATH 设置不完整。如果安装完还是找不到可以排查一下是不是 PATH 的问题用绝对路径/usr/bin/crontab试试。类似的还有lsusb: command not found——最小化安装的系统连usbutils都没装sudo apt install usbutils就能解决。回头想想“真的没装”还是“PATH 里没有”这个排查思路能救你好多次。4.3 权限被拒Permission denied这个报错很好懂就是文件没有执行权限。解决chmod x yourscript.sh如果你非要用bash yourscript.sh来跑那不需要执行权限也可以因为只是把脚本文件当作 Bash 的输入数据来读取。4.4 脚本“不生效”source 和执行的坑前面讲过的老问题很多人执行了修改 PATH 的脚本发现当前 Shell 里 PATH 没变以为是脚本写错了。其实只是因为执行方式不对——记得用source或.来执行修改环境变量的脚本。这是 Bash 初学者第一个“灵异事件”我当年也卡过一晚。4.5 中文乱码为什么 grep 出来是花的解压一个 Windows 那边打包的 zip里面的中文文件名和文件内容全是乱码。原因同样是编码差异Windows 常用 GBK/GB18030Linux 默认 UTF-8。处理办法# 乱码文件名 ls -b # 解压时指定编码 unzip -O GBK files.zip内容乱码就需要注意文件编码用iconv -f GBK -t UTF-8 input.txt output.txt转换。这个坑在我处理从客户那儿传来的“古董压缩包”时几乎每次都会踩。4.6 玄学“不报错也不干活”检查路径和引号有时候脚本没任何报错但结果就是不对。我踩得最多的是引号问题。路径里有空格的话不加引号就会被拆成多个参数。比如file_path/home/user/My Documents/data.txt cat $file_path # 错误被拆成3个参数 cat $file_path # 正确整体作为一个参数所以涉及路径和参数传递时我习惯性地给变量加上双引号。这个习惯能消灭掉九成以上的“幽灵 bug”。5. 进阶实用技巧让脚本更专业、更健壮如果你已经能把前面那些语法组合起来写小工具了这节的内容能让你的脚本从“能跑”升级到“能拿得出手”。5.1 调试利器bash -x脚本跑不出来、不知道卡在哪一步用调试模式跑一下bash -x mysystem.sh它会逐行显示每一条实际执行的命令和展开后的变量值前面带个号相当于代码里自动给你打印日志。比如变量传着传着变空了一眼就能看出来。还有个偷懒技巧在脚本第一行 shebang 后面加个-x#!/bin/bash -x以后直接执行也自动开调试。调试完毕记得改回来。5.2 用 set 命令提前挡坑脚本默认是很“宽容”的某条命令执行失败它不会中断还会继续往下跑。这在很多场景下会引发连锁问题——上一步失败了下一步却基于失败的结果继续。推荐在脚本开头加这两行set -e # 只要任何命令返回非0退出码脚本立即终止 set -u # 使用未定义的变量直接报错而不是当空值处理set -e相当于“出问题就停下来别硬跑”set -u能帮你抓到拼错变量名的低级 bug。再加一个set -o pipefail能让管道命令中任何一环失败时整体返回失败。我的所有正式脚本开头都是这三行。5.3 写日志、做备份构建你的工具库随着你写的脚本越来越多你会发现有些代码段反复出现。我建议建一个自己的脚本工具库把“打印带时间戳的日志”“优雅退出函数”“检查依赖命令是否存在”这些通用函数放进去需要用的时候source进来就行#!/bin/bash source ~/shell_lib/common.sh log 脚本开始执行 confirm 确定要覆盖原文件吗? || exit 1这样日积月累你就像攒乐高积木一样写新脚本会越来越快。5.4 别忽视脚本和系统的第一道护栏如果你写的脚本要处理关键数据记得两个铁律第一改动重要文件前先备份或加“干跑模式”dry-run脚本里用--dry-run参数只打印将要执行的动作而不真正执行第二危险操作前让用户输入yes确认而不是按个回车就默认通过。这些习惯能让那些“一把梭”的脚本不至于哪天把服务器搞崩。6. 写在最后的几个小建议从纯粹在终端里敲命令到开始写第一个脚本再到能熟练使用if、for、函数这个过程的提升是跨越式的。我现在看到重复工作下意识就会想“能不能写个脚本自动完成”。根据我个人经验还有几个建议值得分享。首先刻意练习“拆解重复操作”。你不需要一上来就写什么百行大脚本今天觉得“切换目录、激活环境、运行测试”这套动作重复了三遍那就可以写成一个小脚本。每天积累一点技能和脚本库一起成长。其次习惯用man命令查手册。比如man bash、man grep。刚开始看不懂很正常但只要学会在手册里搜关键词你解决问题的能力会自动提升一大截。搜索引擎当然有用但系统自带的文档永远是更可靠的。还有个小技巧是给你的脚本写注释。这词听起来像废话但你一个月后回来看脚本绝对想不起来当时那个诡异的参数为什么要这么传。注释有两大核心解释“这一步在干什么”更要解释“为什么这样干”特别是那些绕过某个坑的处理不写上注释过阵子自己都会看不懂。最后提醒一下Shell 脚本不是万能的。如果发现逻辑越来越复杂、处理的数据结构越来越庞大别硬撑着及时换 Python 或者 Go 来写效率和可维护性都会更好。判断标准很简单当你的 Bash 脚本里出现“在 for 循环里再套好几个 if 再处理多维数组”这种苗头就该考虑换语言了。但日常工作里Bash 依然是连接所有 Linux 工具链最轻巧高效的胶水这个基本功学好了从此终身受益。
返回列表