ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Shell脚本编程入门:从命令行基础到自动化实战

2026/9/12 3:01:33 拓冰建站 浏览量
Shell脚本编程入门:从命令行基础到自动化实战 我最早接触 Shell纯粹是被逼的。那时候天天要在一台服务器上部署项目点鼠标点得手指头都快抽筋了后来一个老同事看不过去丢给我一句话“你把这串命令粘进去就行。”从那以后我就发现命令行这玩意儿虽然第一次见面黑漆漆的挺吓人但熟悉之后它比任何图形界面都来得干脆利落。特别是当你能把一大串操作装进一个 Shell 脚本里敲一下回车就自动跑完的时候那种掌控感是鼠标点一万次都换不来的。这篇内容就是围绕“Shell 基础与命令行操作”来展开的我会把概念、常用命令、脚本写法、调试技巧、跨平台玩法一次讲透。无论你是刚开始接触 Shell 的小白还是已经会敲几条命令但想系统补一补的进阶选手这篇都能给你一些直接能用的东西。我也会把实际干活时踩过的坑、总结出来的经验混在里面讲争取让你看完就能上手少走一些弯路。1. 先把概念捋清楚Shell、终端、命令行到底是什么关系很多新手一上来就懵终端窗口、命令行、Shell、Bash 这几个词反复出现到底谁是谁我先把这层窗户纸捅破后面再讲命令就不会发怵。1.1 Shell 就是个“翻译官”Shell 的中文意思是“壳”它包在操作系统内核外面负责接收你敲进去的命令然后翻译给内核去执行。你可以把内核想象成一个只会说机器语言的老板Shell 就是那个把老板的话翻译成员工能听懂的指令的秘书。没有这个秘书普通用户根本没法直接操作计算机。其实图形界面也是一种“壳”只不过它把操作变成了点击和拖拽。而命令行 Shell 是直接用文本交互好处是精确、可重复、可批量。你点十次鼠标做的事可能一行命令就搞定了而且这行命令还能写进脚本里下次继续用。1.2 常见 Shell 种类与选择市面上常见的 Shell 有好几种但绝大多数 Linux 发行版默认用的是 BashBourne Again Shell。除此之外还有 Zsh、Fish、Sh、Ksh、Tcsh 等。Bash最通用、最保守的选择教程和网上的资料最多几乎所有 Linux 都预装。Zsh对 Bash 做了大量增强有自动补全、拼写修正、主题插件macOS 现在默认就是它。但写脚本时还是建议按 Bash 的语法来跨机器兼容性更好。Fish交互体验更好语法更友好但不完全兼容 Bash写脚本的话容易被环境卡住。Sh比较老的 Shell很多系统里其实是指向 Bash 的符号链接但在某些精简系统里行为可能不太一样。对于刚入门的人来说直接用系统默认的 Bash 就好等熟练了再折腾 Zsh 也不迟。我在文章里聊到的命令和脚本默认都是以 Bash 环境为准。1.3 打开一个能用的命令行环境在 Linux 桌面上一般是按Ctrl Alt T打开终端或者直接在应用菜单里找 Terminal。在 macOS 上是打开“终端”应用。Windows 的话我后面单独讲那边有自己的一套玩法。打开之后你会看到一个提示符类似于userhostname:~$这串东西告诉你的信息其实不少当前登录的用户名是 user机器名是 hostname~表示当前目录是你的家目录$表示你现在是普通用户权限。如果你是 root 用户那个$会变成#。看到#的时候要小心这时候任何命令都是最高权限手误打错一个字就可能把系统搞坏。2. 基础命令这块地基打牢了后面才不慌命令行操作层面其实不需要记几百条命令真正高频的就是文件、目录、查看内容、查找过滤这几类。把这些练熟了日常干活基本就不慌了。2.1 文件与目录操作的“肌肉记忆”命令我总结了下面这一批最常用的命令建议新手动不动就敲一遍直到形成肌肉记忆命令作用典型用法备注pwd打印当前所在目录pwd迷路时第一反应就是敲它cd切换目录cd /etc、cd ..、cd ~不加参数直接回用户家目录ls列出目录内容ls -lah-l 长列表-a 显示隐藏文件-h 人性化大小mkdir创建目录mkdir -p a/b/c-p 自动创建父目录touch创建空文件或更新时间戳touch test.txt常用在快速建文件cp复制文件/目录cp -r dir1 dir2-r 复制目录时必带mv移动或重命名mv old.txt new.txtLinux 里重命名就是移动rm删除文件/目录rm -rf dir-r 递归-f 强制慎用tree树状显示目录结构tree -L 2需要单独安装但太好用了这里必须重点提醒一句rm -rf是无数人的噩梦。它不会进回收站删了就真没了尤其在家目录或 root 权限下一个路径拼写错误就可能把重要数据抹掉。我个人的习惯是尽量用mv把暂时不用的东西移到一个临时目录里确认没问题再真正删除给自己留条退路。2.2 查看与检索文件内容没那么难文件内容查看这边我最想推荐你掌握的是四个命令cat适合看小文件。直接cat xxx.log内容就哗啦啦全出来了。文件大了就不推荐刷屏刷到你怀疑人生。less适合看大文件。less xxx.log进去之后可以用方向键上下翻按/搜索关键词按q退出。文件再大也不会卡因为它是分页加载的。tail适合看日志尾部。最常用的是tail -f xxx.log它可以持续跟踪文件新增内容调试程序时跑起来新日志一行一行往外蹦比图形界面爽多了。grep在文件或输出里过滤关键词。比如grep ERROR app.log就能把所有包含 ERROR 的行列出来。加-i忽略大小写加-n显示行号加-r递归搜索目录。这几个配合起来排查问题的效率是成倍往上走的。比如你想看某个服务今天的报错一条命令就搞定了grep 2026-01-01 /var/log/service.log | grep ERROR | tail -n 50这里还看到了一个高频操作——管道符|。它的作用是把左边命令的输出当作右边命令的输入继续处理。这是命令行最核心的组合拳思路单个命令能力有限但串起来就威力无穷。2.3 通配符、重定向与管道组合拳的核心光会单个命令不行Shell 的爽点在于组合。我拿几个真实场景来说。场景一批量清理日志假设一个目录下有一堆按日期命名的日志文件你想把上个月的都删掉rm -f app-2025-11-*.log这里的星号*是通配符表示匹配任意字符串。上面的命令会把所有以app-2025-11-开头、以.log结尾的文件全删掉。注意这种命令执行前最好先ls app-2025-11-*.log看一下到底匹配了哪些文件确认无误再删。场景二把命令输出保存到文件ls -lah filelist.txt这里的是重定向符号它的作用是把原本要打印到屏幕的内容写到文件里去。如果文件不存在会自动创建如果存在会覆盖。要追加而不是覆盖用。场景三统计某个关键词出现次数grep -c ERROR app.log-c就是统计计数。如果想更花哨一点统计每个状态码出现的次数可以用cut加sort加uniq组合grep HTTP access.log | awk {print $9} | sort | uniq -c | sort -rn这句的意思是过滤出包含 HTTP 的行取第 9 列一般是状态码排序统计去重后的次数再按次数从大到小排序。你没猜错awk是按列切分的利器它在后面的脚本部分还会频繁出场。3. Shell 脚本编程才是真正的“生产力工具”如果说命令行是手工单杀那 Shell 脚本就是大招连招。把一系列命令按逻辑写进一个文件加上变量、条件、循环、函数让它自动处理复杂任务这才是 Shell 的精髓。3.1 第一个脚本和它的执行方式写一个脚本非常简单。新建文件hello.sh写入#!/bin/bash echo Hello, Shell! echo 当前时间: $(date)第一行#!/bin/bash叫 shebang用来告诉系统用哪个解释器来执行这个文件。echo是打印输出$(date)是命令替换意思是先执行括号里的 date 命令把结果拼到字符串里。执行脚本有三种常见方式# 方式一直接指定解释器执行 bash hello.sh # 方式二给文件加执行权限后执行 chmod x hello.sh ./hello.sh # 方式三用 sh 执行 sh hello.sh有人会问为什么方式二要用./hello.sh不能直接hello.sh因为系统在当前目录找命令时默认不会把.当前目录加进搜索路径。./就是在告诉 Shell别去系统路径里找了就在当前目录执行这个文件。3.2 变量、特殊变量和 shift 命令Shell 脚本里的变量没有类型的概念一切按字符串处理。赋值时等号两边不能有空格这一点是新手踩坑重灾区。nameTom echo Hello, $name花括号的用法也要会尤其是变量后面紧跟其他字符时需要用花括号把变量名边界隔开prefixlog echo ${prefix}_2026-01-01.txt特殊变量是脚本里的隐藏神器变量含义$0脚本本身的名字$1、$2第 1 个、第 2 个位置参数$#参数个数$所有参数列表$?上一条命令的退出码0 表示成功$$当前进程的 PID这里特别讲一下shift命令它在处理不定量参数时非常好用。shift的作用是把位置参数整体左移一位原来的$2变成$1$1被丢弃。举个例子写一个累加脚本sum.sh#!/bin/bash total0 while [ $# -gt 0 ]; do total$((total $1)) shift done echo 总和: $total执行bash sum.sh 1 2 3 4 5它会依次把参数移出来累加最后输出 15。这就是shift核心用法的经典演示。在解析命令行参数时shift还可以配合case做比较优雅的参数解析。3.3 条件判断与循环让脚本有了逻辑条件判断最常见的格式是if [ 条件 ]; then ... fi。注意中括号两边必须有空格这是另一个高频坑。常见的比较运算符有数字比较-eq等于、-ne不等于、-gt大于、-lt小于、-ge大于等于、-le小于等于字符串比较、!、-z空字符串、-n非空字符串文件判断-f是普通文件、-d是目录、-e存在、-r可读、-w可写、-x可执行来看一个实际点的例子检查一个目录是否存在不存在就创建#!/bin/bash backup_dir/data/backup if [ ! -d $backup_dir ]; then mkdir -p $backup_dir echo 目录不存在已创建: $backup_dir else echo 目录已存在: $backup_dir fi这里!表示取反-d是判断目录是否存在。命令执行后$?也可以直接当作条件来用因为 if 判断的本质就是看命令的退出码。循环这块for循环是使用频率最高的。最经典的遍历文件用法#!/bin/bash for file in *.log; do echo 处理文件: $file # 这里可以写你想对每个文件做的操作 done带步长的循环可以用seq或者花括号展开for i in $(seq 1 10); do echo 第 $i 次 done for i in {1..10}; do echo 第 $i 次 donewhile循环则适合“不知道循环多少次”的场景比如实时读取文件每一行进行处理。经典写法while IFS read -r line; do echo 读取到: $line done input.txt这里的IFS是为了保留行首行尾的空格-r是为了防止反斜杠被转义。如果你处理的行内容里可能有特殊字符这两个参数建议固定带着。3.4 函数和返回值让代码优雅起来脚本长了之后重复代码多了就得用函数来封装。定义函数有两种写法# 写法一 function log_info() { echo [INFO] $(date %Y-%m-%d %H:%M:%S) $1 } # 写法二 log_error() { echo [ERROR] $(date %Y-%m-%d %H:%M:%S) $1 2 }调用函数就是直接写函数名log_info 开始备份 # 中间干点啥 log_info 备份完成函数里$1、$2是函数自己的参数跟脚本的参数互不干扰。函数返回值比较特殊它不支持像别的语言那样直接返回字符串返回值只能是一个整数表示成功或失败check_file() { if [ -f $1 ]; then return 0 else return 1 fi } if check_file /etc/passwd; then echo 文件存在 else echo 文件不存在 fi如果想从函数里返回字符串标准做法是用echo输出调用时用命令替换接住get_version() { echo 1.2.3 } version$(get_version) echo 当前版本: $version4. 实战写一个批量文件重命名脚本说了这么多基础咱们动手做一个稍微完整点的东西。假设你有一堆照片文件命名乱七八糟有些带了空格有些后缀是大写的.JPG你现在想统一改成photo_001.jpg这种格式。这个需求很常见非常适合用来练手。4.1 脚本整体设计与关键点拆解先想一下思路遍历目录下所有.jpg和.JPG文件去掉文件名里的空格把后缀统一成小写再按顺序编号。这个需求里有两个关键点必须处理文件名含空格时必须给变量加双引号否则 Shell 会把名字切成多段。需要维护一个计数器保证编号是连续的。最终脚本写出来长这样#!/bin/bash count1 for file in *.JPG *.jpg; do # 跳过不存在的文件匹配项 [ -e $file ] || continue # 去掉空格 new_name$(echo $file | tr -d ) # 小写化并构造带编号的新名字 base$(echo $new_name | tr A-Z a-z | sed s/\.jpg$//) target$(printf photo_%03d.jpg $count) # 如果目标名和原文件名不同才执行重命名 if [ $file ! $target ]; then mv $file $target echo 已重命名: $file - $target fi count$((count 1)) doneprintf photo_%03d.jpg $count会生成photo_001.jpg这样的固定宽度数字%03d表示用 0 补齐到三位。tr -d 用来删除空格tr A-Z a-z用来大写转小写sed s/\.jpg$//用来去掉结尾的.jpg以便重新构造名字。4.2 手册之外的经验补充实际跑这个脚本之前还有几个建议第一先在一个临时目录里放几个测试文件跑一遍。写脚本最重要的习惯就是先在安全环境验证再放到真实数据上执行。第二执行前把mv换成echo先打印一遍看看逻辑对不对确认没问题再真正执行。第三批量处理文件时一定做好备份至少确保操作可回退。我还喜欢在脚本里加一个“调试开关”比如用环境变量控制是否打印详细过程[ -n $DEBUG ] echo 准备重命名: $file - $target这样在正式执行时不需要改代码只需要在跑脚本时加个DEBUG1前缀就能看到中间信息DEBUG1 bash rename.sh这个习惯在写更复杂脚本时能救大命因为很多脚本问题是出在“中间状态跟我想的不一样”。4.3 扩展结合 find 处理子目录上面脚本只处理当前目录如果文件分散在子目录里就需要配合find命令。find本身就很强大它不仅能找文件还能对找到的每个文件执行命令find . -type f -name *.jpg -exec basename {} \;更高阶的玩法是用while read逐行接住find的输出然后做处理find . -type f \( -name *.jpg -o -name *.JPG \) | while read -r file; do echo 找到文件: $file # 这里可以做你想做的任意处理 done这里要注意用管道接到while时while是在子 Shell 里执行的里面的变量修改不会带出到外部。如果你需要在循环里汇总结果应该用进程替换while read ... done (find ...)或者把逻辑都写进循环体内别依赖循环外变量。5. 常见坑和排查技巧都是拿时间换出来的Shell 脚本写起来灵活坑也不少。我把这些年踩过、帮别人排查过的高频问题整理一下按“现象→原因→解决”的方式写方便你遇到问题直接对着查。5.1 高频入坑清单坑现象根本原因解决办法$name输出为空或错位变量赋值等号两边有空格严格写成namevalue中括号判断报错[: too many arguments变量没加引号被拆成了多个词条件里统一加引号[ $var a ]文件名字带空格处理时被拆开没给变量加引号所有引用变量的地方一律用双引号脚本在 Linux 上正常Windows 上跑报错Windows 的换行符是\r\nLinux 只认\n用sed -i s/\r$// script.sh清理执行./script.sh提示 Permission denied脚本没有可执行权限chmod x script.shfor 循环遍历时*.log没有匹配项会保留字面量Shell 默认把未匹配的通配符原样传给命令加 [ -e $file ]crontab里跑脚本不生效但手动执行正常cron 的环境变量和交互式终端不一致脚本开头export PATH/usr/local/bin:/usr/bin:/bin其中换行符这个坑我特别有感触。Windows 下用记事本写的脚本传到 Linux 上经常出现“命令未找到”的诡异报错因为\r被当成命令的一部分了。用cat -A script.sh能看到行尾有^M$$是行尾^M就是多余的\r。5.2 调试三板斧我在调试 Shell 脚本时基本靠三个工具第一是bash -n它只检查语法不执行命令。用来抓缺fi、少done、括号不匹配这类低级错误非常快。bash -n script.sh如果语法没毛病什么都不会输出。有错会直接报行号。第二是bash -x执行时把每一步展开打印。这个是我最常用的调试手段它会显示每条被执行的命令以及变量的实际值能直观看到脚本执行到哪一步、哪一步出了问题。bash -x script.sh执行结果类似于 count1 for file in *.JPG *.jpg [ -e *.JPG ] continue每行开头的表示这是被展开后的命令你一眼就能看出变量值是什么逻辑卡在哪。看到continue跳过了说明目录里根本没有匹配到的文件。第三是加打印语句。写复杂逻辑时我习惯在关键节点加echo [DEBUG] 当前变量值为: $var跑完确认没问题再删掉。这个办法土但最直接。5.3 一个真实问题的排查示范有一次同事写了个脚本部署到服务器上后日志总是不输出。我过去看了一眼脚本大概长这样#!/bin/bash source config.env echo 开始执行任务 # 下面是一堆业务逻辑报错信息是config.env: No such file or directory这个是最典型的坑之一source相对路径依赖当前工作目录。脚本在哪个目录下执行它就在哪个目录下找config.env。如果从别的目录敲bash /opt/script/deploy.sh当前工作目录就不是/opt/script自然找不到配置文件。解决方法是用脚本所在目录拼出绝对路径#!/bin/bash script_dir$(cd $(dirname $0) pwd) source $script_dir/config.envdirname $0获取脚本所在目录cd进去后再pwd得到绝对路径。这样不管从哪里执行脚本都能准确找到同目录下的配置文件。这种问题用bash -x一扫就能看到source那一步实际用了什么路径定位很快。6. 跨平台与场景延伸不止 Linux 需要命令行很多人以为命令行是 Linux 专属其实 Windows、Android 调试、甚至日常办公都能用上。我把热词里提到的几个场景一并说一说。6.1 Windows 下的命令行生态Windows 上有几种命令行环境传统的 CMD、现代的 PowerShell以及 WSLWindows Subsystem for Linux。CMD 比较古老批处理语法也跟 Linux Shell 差异很大。现在新装机推荐直接用 PowerShell它比 CMD 强太多可以直接调 .NET对象管道比文本管道高级不少。比如把当前目录下所有文件按大小排序Get-ChildItem | Sort-Object Length -Descending | Select-Object -First 10如果你主要工作在 Linux 生态建议装个 WSL在里面直接用熟悉的cd、ls、grep、管道这些体验几乎和真实 Linux 一样。用 PowerShell 安装 WSL 的命令是wsl --install不过这个东西在国内环境下有时候下载很慢等它跑完就好别中途关窗口。Windows 命令行里还有一个比较常用的操作按分隔符按行输出。比如你有一堆用逗号分隔的文本想变成一行一个。在 PowerShell 里可以这样Get-Content input.txt | ForEach-Object { $_ -split , } | Where-Object { $_ -ne }这个用法在处理导出的数据文件时非常实用比用 Excel 手工拆分要高效得多。6.2 adb shell 与 Android 调试热词里频繁出现adb shell这是 Android 调试桥的产物。连接手机或模拟器后可以用它进入一个精简版 Linux Shell 环境直接操作 Android 系统内部。常用操作比如# 查看设备是否在线 adb devices # 进入设备 Shell adb shell # 在设备上执行单条命令 adb shell ls /sdcard/ adb shell ping -c 4 1.1.1.1adb shell后面可以直接跟命令也可以不带命令直接进入交互式 Shell。进入之后很多 Linux 命令都能用但因为 Android 的 Shell 环境精简过有些命令可能不存在或者参数受限。比如没有grep时可以用toybox内置的命令替代busybox v1.30.1 built-in shell也是这类精简环境的常见提示。需要注意Android 的/storage/emulated/0/是内置存储的路径里面是模拟的 SD 卡目录备份文件、推送文件经常会用到这个路径。常见操作# 从电脑推文件到手机 adb push local.txt /sdcard/Download/ # 从手机拉文件到电脑 adb pull /sdcard/Download/remote.txt . # 在设备上执行脚本 adb shell sh /sdcard/test.sh这个场景对做自动化测试、App 逆向分析、设备维护的人来说是绕不开的基本功。6.3 命令行在开发工具链里的地位命令行不只是自己直接敲很多开发工具本身就是命令行的形态或者跟命令行结合非常紧密。Java 项目的 Maven最常用的就是mvn clean install mvn clean install -DskipTests第一句会清空旧的编译产物重新编译、测试、打包。第二句跳过测试适合本地快速构建。真实项目中命令行参数还能指定 profilemvn clean install -Pprod这样就能按不同环境打包出不同的配置完全是命令行掌控的节奏。音视频处理的 FFmpeg 也是典型的命令行王者。比如你想把视频片尾裁掉先用ffmpeg -i input.mp4查看时长再用-ss和-to参数精准切出想要的区间ffmpeg -i input.mp4 -ss 00:00:00 -to 01:30:00 -c copy output.mp4这个命令的意思是从第 0 秒到 1 小时 30 分直接复制流不做重新编码速度飞快。命令行参数化的好处在这里体现得淋漓尽致同样的逻辑可以套用在批量文件上写个 for 循环就能一键处理整个文件夹。Windows 命令行还有一些实用小技巧比如用 7z 加密压缩7z a -pyour_password -mheon archive.7z folder/-p后跟密码-mheon加密文件头这样列出压缩包内容也需要密码。注意-p后面不要有空格直接把密码跟上。6.4 GUI 工具与命令行的关系不是对立最后想说个观念问题命令行和图形界面不是非此即彼。现代 Linux 桌面环境做得很好文件管理器、文本编辑器都很成熟日常操作完全可以用 GUI。比如在 UOS 这样的国产 Linux 桌面版上用 DDE 文件管理器就能直接管理文件权限、连接 FTP。右键属性权限标签页下把“所有者”“群组”“其他”的读写执行权限勾一勾就行。对刚接触 Linux 的人来说这种方式比记一串chmod参数要友好得多。但我还是要说命令行是底层能力GUI 是表层便利。当你需要批量处理、自动化、远程服务器维护时GUI 就不好使了。打个比方GUI 像自动挡车好开命令行像手动挡你的操作空间更大。自动挡车主不一定需要学手动挡但想真正玩转系统、做深度运维命令行的掌控力和表达力 GUI 替代不了。我自己实际用下来的体会是命令行跟 GUI 混着用效率最高。能用鼠标快速浏览的时候不排斥 GUI一旦发现操作重复出现三次以上就开始想怎么用命令行一次解决。这个思路就是学习 Shell 最好的起点。