Shell 这东西,说实话,刚开始接触的人容易把它想得特别简单,觉得不就是个黑乎乎的终端窗口,敲几个命令嘛。但真正用起来,你会发现它是整个 Linux 生态里最抗造、最省命的一块基石,也是让你从“只会点鼠标”走向“能用一句话让电脑干一堆活”的分水岭。这篇东西不打算写成那种能查到但读不下去的官方文档,而是想以我这些年拿 Shell 干活、踩坑、再爬起来的过程为底子,把命令行、脚本、变量、循环、后台任务、ADB 调试这些高频场景一次讲透。
先说清楚它解决什么:Shell 本质是个命令解释器,但你这个解释器四舍五入就是操作系统给你的一个遥控器。你自己写的一串命令组合在一起,就可以把文件处理、日志分析、软件部署这种重复劳动变成一行脚本,几秒钟跑完。无论你是刚入门想搞懂变量和循环的运维新手,还是做测试经常要 adb shell 操作手机,又或者想写点小工具解放双手的普通开发,这篇内容都是按实战顺序给你排好的。
1. Shell 是什么?学它到底能解决什么问题
1.1 一个命令解释器,更是一把胶水
很多人把 Shell 和终端混为一谈,其实严格区分一下会更好理解:终端是你看到的那块窗口,Shell 是窗口背后真正去执行命令的翻译官。你敲下ls -l,Shell 会把这个字符串拆开、找到对应的可执行文件、把参数传进去,再把结果回给你。这个过程听起来平平无奇,但真正的价值在于,它允许你把多个命令用管道、重定向、逻辑判断接起来,形成流水线。
举个例子,我想找出当前目录下最大(或者最占空间)的三个文件。一条命令就够了:du -ah . | sort -rh | head -3。这里du负责统计大小,sort负责排序,head负责截取前几行,中间用竖线把数据从上一个命令嘴里直接灌给下一个命令。这种“每个小工具只干一件事,但组合起来能干大事”的思路,就是 Shell 作为胶水语言的最大魅力。你不需要写一个 500 行的 Python 程序去处理临时任务,Shell 一行就搞定了。
所以我一直觉得,Shell 不是一个需要“精通”的编程语言,它更像一个工具箱。你每多掌握一个命令或者一个语法,就等于往工具箱里多放了一把趁手的家伙,拿出来就能用。
1.2 什么人最需要学 Shell
顺着这个思路往下说,学 Shell 最迫切的需求者大概分这几类,你大概率是其中之一:
- 运维和系统管理员:服务器批量部署配置、日志切割、定时任务,全是 Shell 的天下。不会 Shell 基本没法独立维护一台 Linux 服务器。
- 开发工程师:本地构建、环境变量、git 钩子、容器里的启动命令,哪怕你平时写的是 Java/Python/Go,一旦进了服务器,绕不开 Shell。
- 测试工程师:用
adb shell操作 Android 设备、自动抓日志、自动跑稳定性测试,这些脚本十有八九是用 Shell 拼出来的。 - 普通办公族:每天要把几十个文件改名、把表格导出成 CSV 再合并,或者定期备份文件夹,这些用 Shell 写个小脚本能省下大量时间。
1.3 从热搜词里看大家到底在问什么
每次我看这类热搜词的实时榜单,其实就是在看大家究竟在哪个环节卡住了。比如shell脚本for循环、linux的shell脚本变量与输入、grep在shell脚本中的常见用法,这几个词暴露的是一个典型路径:已经会敲单个命令了,但一旦要把多个命令揉进一个脚本里,就遇到了变量作用域、循环写法、条件判断这些编程基础问题。
再比如shell脚本要在&后台执行,还要交互式输入密码,这个场景就更真实了:脚本本身要跑很久,你想放到后台去,可它中途又要输密码、要交互,两边打架,不知道怎么处理。还有adb shell uiautomator dump 用不了,这是在移动端调试的时候遇到的具体报错。这些热搜词合在一起,几乎就是一枚新手从零爬到能独立写自动化脚本的地图。这章我先把地图给你铺开,后面几节照着走就行。
2. 从零开始的第一课:环境、常用命令与第一个脚本
2.1 环境准备:Linux、macOS、Windows WSL 与 Android 调试桥
学 Shell 的第一步不是背语法,是先把能敲命令的环境搭好。要是你手头是 Linux 服务器或者 macOS 电脑,直接打开终端就能干,不用装任何东西。要是你用的是 Windows,我不太建议你在 cmd 或者 PowerShell 里硬学,因为命令和语法体系不完全一致,学完容易乱。更推荐装一个 WSL(Windows Subsystem for Linux),把 Ubuntu 之类的发行版跑起来,那样你得到的就是一个标准的 Linux 环境。
这里有个容易犯迷糊的地方:你可能会搜到“Windows 也有 PowerShell”“Windows 也能跑 shell”之类的说法。PowerShell 确实是一门功能很强的脚本语言,但它不是这里的核心;类 Unix 的 Bash 才是绝大多数教程里的默认 Shell。为了少走弯路,先在 Linux 环境里把 Bash 玩明白,其他的之后再接触也不迟。
另外,如果你做 Android 开发或者测试,还需要准备好adb工具,把它加到 PATH 里,确保终端里直接敲adb devices能看到设备。后面第五部分会专门讲adb shell的实战,现在你只需要知道,这玩意儿是通往手机内部系统的另一个 Shell 入口。
2.2 先掌握这几条高频命令:cd、echo 与环境变量
我不打算把ls、cp、mv这些最基础的命令从零讲起,网上一搜一大把,我重点说说那些看着简单但其实藏了不少细节的:
cd命令。它是切换目录用的,但它有几个特殊用法值得记一下:cd -返回上一次所在的目录,cd ~回到当前用户的家目录,cd ../..往上跳两级。在脚本里我还常用cd "$(dirname "$0")"这句,它的作用是把当前工作目录切到脚本自身所在的目录。为什么非要用它?因为脚本被调用时,当前目录不一定是你脚本存放的位置,如果你在脚本里写相对路径,很可能找错文件。
echo命令,很多人只拿它打印字符串,但它对转义和变量的处理很容易踩坑。比如echo $HOME会打印家目录,但echo '$HOME'就只会原样打印$HOME四个字符,因为单引号会关掉变量展开。再比如你写echo -e "a\tb",-e 参数让\t被解析成制表符,这在生成 CSV 之类的文件时特别好用。
环境变量这个概念也不复杂,它就是一组全局的键值对。你用export MY_VAR="hello"设置之后,当前 Shell 以及它启动的子进程都能读取到$MY_VAR。有一个最常见的坑:变量赋值时等号两边不能有空格。你写MY_VAR = "hello",Shell 会认为要执行一个叫MY_VAR的命令,然后给你报command not found。这个细节我不知道见过多少人摔过,包括我自己。
2.3 写出第一个可执行脚本
环境有了,命令也懂几条了,接下来就是让脚本跑起来。第一步,新建一个文件,比如first.sh,用你喜欢的编辑器写入:
#!/bin/bash # 这是一个注释 echo "当前目录: $(pwd)" echo "当前用户: $USER" echo "今天日期: $(date +%Y-%m-%d)"第一行#!/bin/bash叫 shebang,它的意思是告诉系统,这个脚本要用 Bash 解释器来执行。第二步,给文件加可执行权限:chmod +x first.sh。第三步,运行它,注意这里要写成./first.sh,前面的./表示在当前目录查找文件,因为默认情况下 Shell 不会在当前目录搜索可执行文件,这是出于安全考虑。
如果你觉得每次都要chmod +x很麻烦,也可以退一步,直接用bash first.sh来执行,这样不要求文件有可执行权限,完全看个人习惯。但我还是建议养成加权限的习惯,因为写出来的脚本最终是要给别人在服务器上直接跑的,依赖解释器名不如依赖 shebang 更稳定。
2.4 变量和输入:脚本从“死”变“活”的关键
脚本真正开始有用,一定是从支持变量和输入开始的。变量这块,我建议你用三条规则给自己立个门槛:
- 变量名要用字母或下划线开头,别用数字开头。
- 引用变量时最好加花括号,比如
${var},尤其是在变量后面还要接字符串的情况下,像${var}_backup,不加花括号会被解析成var_backup。 - 变量默认都是字符串,别指望 Shell 帮你做算术运算,想计算得用
$(( )),比如echo $((1+2))。
然后是输入。脚本里最常用的输入来源有三种:位置参数、read 命令、命令替换。
位置参数就是跟在脚本后面的那些值。假设你执行./backup.sh /data /backup,那么脚本里$1就是/data,$2就是/backup,$#是参数个数,$@是所有参数的列表。处理多个参数时,shift命令非常有用,它能把参数整体左移一位:原本的$2变成$1,$3变成$2。这样你就可以用循环逐个消费参数,写出来的脚本更像一个正经工具,而不是一个写死的命令。
read命令则是让脚本停下来等用户输入。写法很简单:
read -p "请输入你的名字: " name echo "你好,$name"-p用来显示提示文字,后面的name是接收输入的变量名。如果你不想在输入内容里允许反斜杠转义,还可以加-r参数。这是个安全习惯,处理用户输入时都会用到。
3. 循环、文本处理与批量重命名:脚本的硬功夫
3.1 for 循环的三种常见写法
循环是脚本编程里出镜率最高的语法,而for循环的写法又最容易让人懵,因为不同写法长得完全不像。我直接把三种常用的写法整理成表格,方便对照:
| 写法 | 示例 | 适用场景 |
|---|---|---|
| 列表循环 | for i in a b c; do echo $i; done | 遍历明确给出的列表 |
| 序列生成 | for i in {1..10}; do echo $i; done | 固定范围的数字 |
| C 风格 | for ((i=0; i<10; i++)); do echo $i; done | 需要复杂变量调整时 |
用哪一种是看场景的,但我的偏好是:批量处理文件名时用列表循环,例如for f in *.txt; do echo "$f"; done;需要跑固定次数时用序列生成;需要在循环体内修改循环变量时用 C 风格。这里必须提醒一个细节:列表循环里*.txt这类通配符如果匹配不到任何文件,它会原样保留*.txt这个字符串,导致脚本把不存在的文件当真。稳妥做法是在循环开头判断一下:for f in *.txt; do [ -e "$f" ] || continue; ...; done。
3.2 shift:处理命令行参数的一把好手
单独把shift拎出来说,是因为它在搜索引擎里出现的频率真心不低,但很多初学者不知道它在干嘛。它的逻辑特别简单:每执行一次shift,所有的位置参数向左平移一位,$1变成原来的$2,$2变成原来的$3,原来的$1就没了。如果执行shift 2,就是一次平移两位。
最常见的用法是在脚本里做选项解析。比如你希望脚本支持./run.sh -n 5 -v这种形式,-n后面跟一个数字,-v是开关。这时候可以写个while循环加case来处理:
while [ $# -gt 0 ]; do case "$1" in -n) count="$2"; shift 2 ;; -v) verbose=1; shift ;; *) echo "未知选项: $1"; exit 1 ;; esac done这么做的好处是:不管参数顺序怎么变,脚本都能正确解析。处理完的剩余参数继续留在$@里,方便后续使用。很多弹跳脚本、部署脚本里你都会看到这种模式,属于必须掌握的套路。
3.3 grep 在脚本里的正确用法
grep在命令行里和在脚本里,用法其实不太一样。命令行里你可能希望过滤日志时顺便带上高亮颜色、行号,但在脚本里,你往往只想知道一件事:“有没有匹配到?”这时候grep -q是首选,因为它会自动静默,不输出任何内容,只通过退出码告诉你结果:
if grep -q "ERROR" /var/log/app.log; then echo "日志里发现 ERROR" else echo "一切正常" fi这个-q是我见过最实用的参数,没有之一。其次常用的是-E,它允许你用扩展正则表达式,比如grep -E "^[0-9]{3}-" file.txt,匹配以三位数字加横杠开头的行。再就是-A和-B,grep -A 2 "关键词" file会把匹配行的后两行也带出来。写脚本时如果你需要频繁用某个匹配结果,我建议用命令替换把结果存到变量里,而不是反复执行 grep,省下来的时间在跑大日志时非常可观。
3.4 批量重命名文件:摆脱鼠标的第一个理由
“Linux 用 Shell 重命名文件”这个搜索词背后的真实场景,八成是要给一批图片加前缀、给一批.txt文件改扩展名,或者把一批2023-01-01.log改成20230101.log。手动逐个右键重命名,二三十个还凑合,几百个就得崩溃了。这时候循环加字符串处理一出手,问题直接消失。
最简单的场景——给所有当前目录的图片加前缀:
for f in *.jpg; do mv "$f" "photo_$f" done复杂一点的场景——把文件名里的日期格式从2023-01-01改成20230101。这里可以用sed加变量替换:newname=$(echo "$f" | sed 's/-//g'),然后再mv。如果想要一个现成的批量工具,用rename命令会更省事:
# 把扩展名 .htm 改成 .html rename 's/\.htm$/\.html/' *.htm这里最要命的一个细节是:文件名千万要加引号。如果文件名里有空格,不加引号的话mv会把一整句话拆成好几个参数,命令直接执行失败。我当初在这上面吃过不少亏,现在写任何涉及变量的路径,都会习惯性加上双引号,这个习惯能替你挡掉一半以上莫名其妙的报错。
4. 后台执行与交互式输入:从脚本到稳定运行
4.1 让脚本在后台跑:&、nohup 与日志
后台执行大概是每个从“跑通脚本”迈向“部署脚本”的人都会遇到的第一道坎。你以为脚本运行几秒就结束了,结果它可能要跑半小时,而你又不想一直占着终端。最简单的后台运行方式,就是在命令最后加一个&:
./long_task.sh &加了&之后,命令会在子 Shell 里后台运行,终端会立刻返回给你。但这里有个坑:如果你直接关掉终端,这个后台进程有很大几率会被挂掉,因为终端退出时会向进程发送挂断信号。解决办法是用nohup:
nohup ./long_task.sh > run.log 2>&1 &这句话拆分一下:nohup让进程忽略挂断信号;>把标准输出导入到run.log;2>&1表示把标准错误也合并到同一个文件里,不然你看到的只有正确输出,报错信息却在终端上随关随没;末尾的&让它后台运行。这个组合是生产环境里最稳的姿势,四个元素缺一个都不保险。
4.2 脚本里要交互式输入密码怎么办
比后台跑更麻烦的是:脚本运行到一半要停下来等你输密码。比如你写了个自动备份脚本,里面要ssh到远程服务器,而远程服务器需要密码认证。你要是直接后台跑,密码就会一直等在那里没人输入,脚本看起来就像卡死了。
处理这个问题,思路分两派。第一派是不交互,优先考虑能不能用免密登录的方式,比如配置 SSH 密钥,这样压根不需要密码。我不太建议在脚本里明文写密码,除非你明确知道风险、而且只是在内网临时用。第二派是自动化交互,如果密码场景确实绕不开,比如你是为了调试方便,那就用expect来“装成一个真人”去回答密码提示。
4.3 expect 自动化交互的基础写法
expect是一个专门用来做交互自动化的小工具,它的逻辑是“等待某个输出,然后发送某个输入”。它的名字起得特别好,它就是在“等”(expect)一个东西出现,等到了就回一句话。一个最小可用的例子长这样:
#!/usr/bin/expect set timeout 10 spawn ssh user@192.168.1.100 expect "password:" send "MyPassWord123\r" interactspawn启动一个外部命令(这里是 ssh)。expect "password:"表示等待输出中出现password:字样。send把你要输入的密码发出去,注意结尾的\r是回车键。interact表示完成登录后把控制权交还给用户,这时候你怎么操作都行。
这套写法还能扩展,比如超时时间可以配,我可以处理掉第一次登录时的 “yes/no” 指纹确认问题。比如:
expect { "yes/no" { send "yes\r"; exp_continue } "password:" { send "$password\r" } }exp_continue的意思是“遇到这个情形处理完之后,继续等待下一个期待的输出”,非常实用。
4.4 后台任务与交互输入结合时的小坑
把这节连起来看,最典型的组合场景就是:一个脚本要在后台执行,执行过程中还要交互式输入密码。很多人卡住是因为没理清楚,后台和交互本质上是冲突的:后台任务不需要人管,而交互任务需要人参与。你非要同时干,就相当于一边想看电影一边让电影等你选座,最后两头都别扭。
我的经验是:先写一个expect脚本(比如do_login.exp),它专门负责处理所有交互,把密码通过参数或者环境变量传进去,然后把这个 expect 脚本本身扔到后台去跑。换句话说,真正后台运行的应当是“已经包装好交互逻辑的自动化脚本”,而不是裸的原始命令。这样既拿到了后台运行的好处,又不会卡在密码输入上。
另外,后台任务跑完之后怎么知道它成没成?我建议脚本里主动把退出码写进日志:
./task.sh >> run.log 2>&1 echo "exit code: $?" >> run.log$?是上一条命令的退出状态,0 表示成功,非 0 表示出错。这个值在一步一步排查脚本问题时,是判断故障位置的第一个灯塔。
5. ADB Shell 实战:移动端调试的常用命令
5.1 adb shell 和 Linux Shell 是什么关系
不少初学者搜adb shell的时候会疑惑:这不是手机工具吗,和前面说的 Shell 是同一个东西吗?答案是:adb shell本质上是把 Android 设备的控制台通过 USB 或者无线调试接回到你电脑上,让你在电脑终端里直接敲 Android 系统底层的 Shell 命令。Android 系统底层也是 Linux 内核,所以ls、ps、cat、grep这些命令在adb shell里全都适用,语法和你在服务器上用的完全一致。
有一点要注意:Android 设备里的 Shell 环境跟完整版 Linux 还是有差别的,它精简了很多命令,且su权限(超级用户权限)能不能拿到直接决定了你能跑哪些命令。没有 root 的设备,很多读系统文件、改系统配置的命令都会权限不足而纷纷报错。所以我的建议是,先分清楚哪些命令不需要 root 也能用,哪些必须依赖 root,然后按需测试。这样就算命令报错,你也不至于一头雾水。
5.2 高频 ADB Shell 命令:wm、locksettings、uiautomator dump
在移动端调试这一块,有几个命令几乎是天天要用到的,我把它们和用途列成表格参考:
| 命令 | 典型用途 |
|---|---|
adb shell wm size | 查看或修改屏幕分辨率 |
adb shell wm density | 查看或修改屏幕密度(dpi),常用于适配测试 |
adb shell vm | 查看虚拟内存统计信息,判断设备内存压力 |
adb shell locksettings set-disabled true | 尝试禁用锁屏密码 |
adb shell uiautomator dump | 导出当前界面的 UI 层级 XML,自动化测试常用 |
先说说锁屏那个命令,adb shell locksettings set-disabled true,它看着挺美好,一执行就能把锁屏密码干掉,省得每次测到一半设备锁屏、自动化脚本中断。但现实情况是,这个命令在绝大多数设备上直接执行会提示权限不足,因为 locksettings 服务需要系统级权限。我在测试机上反复试过,只有具备 root 权限的设备能用它,普通手机和平板建议别抱太大期望。想在自动化里绕过锁屏,更常见的套路反而是唤醒设备加滑动解锁,或者直接改系统的锁屏超时设置。
adb shell wm size是真正值得掌握的:它不仅能看分辨率,还能临时改分辨率,比如adb shell wm size 1080x1920。改完不影响系统真实参数,重启之后会自动恢复,非常适合做多分辨率适配测试。adb shell vm虽然热度不高,但当你需要快速判断设备是不是内存吃紧时,看它输出的内存统计比一个个猜强得多。
5.3 uiautomator dump 用不了的常见原因
uiautomator dump是 UI 自动化测试人员的常用工具,它的作用是把当前界面的控件树、坐标信息导成一个 XML 文件,方便后续用脚本读取和操作。它出问题的概率相当高,最常见的表现是执行完没有任何输出,或者报错提示 “ERROR: could not get idle state” 之类。
结合我自己的排查经验,原因基本集中在这么几个方向:
| 现象 | 常见原因 | 解决办法 |
|---|---|---|
| 命令提示成功但找不到 dump 文件 | 权限不足或默认路径写入失败 | 指定输出路径,如adb shell uiautomator dump /sdcard/ui.xml |
| 报错 idle state | 界面还在动画、加载中 | 先等界面稳定,或者加sleep 2再 dump |
| 输出文件为空或只有空白 | 当前界面有 FLAG_SECURE 防截屏保护 | 换一个非敏感页面,或使用录屏替代方案 |
| 一直在等待没有结果 | 设备频繁刷新 UI 导致不稳定 | 关掉动画,或重启一次 uiautomator 服务 |
还有一个很容易被忽略的坑:在某些系统版本上,uiautomator dump需要你先唤醒设备并确保屏幕处于亮着的状态,屏幕熄灭时它会一直等。所以执行前先adb shell input keyevent KEYCODE_WAKEUP把屏幕点亮,然后adb shell wm dismiss-keyguard解除锁屏,整套下来成功率会明显提高。
5.4 写一个自动化测试辅助小脚本
既然命令都到手了,我来拼一个真实可用的组合脚本。它的用途是:自动点亮屏幕、解除锁屏、导出当前 UI 层级,然后把 XML 拉到电脑本地:
adb shell input keyevent KEYCODE_WAKEUP adb shell wm dismiss-keyguard sleep 1 adb shell uiautomator dump /sdcard/ui.xml adb pull /sdcard/ui.xml ./ui_dump.xml如果uiautomator dump报错,你可以在脚本里顺手加个判断,失败时自动重试一次。这个小脚本就已经有了“先执行、再校验、后处理”的结构,比一次性敲三条命令可靠得多。
6. Shell 中的常见坑:我踩过的你别再踩
6.1 括号、空格和引号:语法层面的三巨头
Shell 脚本报错里最有意思的一点是:很多错误并不是逻辑错了,而是“长相”错了。首先是空格问题,比如if [ $a = $b ],[和]两侧必须有空格,写成[$a=$b]就会被当成一个命令去执行;同理,变量赋值等号两边不能有空格。其次是括号问题,()会启动子 Shell,{}是代码块,$()是命令替换,$(( ))是算术运算,长得都像但语义完全不同,初学者最容易把$( )和$(( ))搞混。
引号方面有一条铁律:只要变量可能包含空格或特殊字符,引用它就必须加双引号。单引号和双引号的差异前面提过,双引号允许变量展开,单引号是完全原文。我自己在排查别人脚本时,十次里有七八次,报错现场都有没加引号的变量。
6.2 环境变量与 PATH 问题
“明明装了某个软件,脚本里执行却提示 command not found”,这是命中最常见的坑之一。背后的原因通常是:脚本启动时用的 PATH 环境变量和你当前终端里看到的不一样。比如你用 cron 定时跑脚本,环境变量会退化到极简状态,/usr/local/bin根本不在里面。解决方案有两条:在脚本里写明全路径,比如/usr/local/bin/python;或者干脆在脚本开头重新定义 PATH,比如export PATH="/usr/local/bin:/usr/bin:$PATH"。
这里要插一个经验:别在脚本里硬编码当前用户的 Home 路径。正确写法是用$HOME或者~,比如backup_dir="$HOME/backup"。否则你把这脚本发给别人,他用户名和你不一样,脚本直接罢工。
6.3 管道、变量作用域和命令找不到
管道和变量的冲突也值得单独说说。你可能会写一条echo "hello" | read name; echo "$name",然后发现输出是空的。原因在于,管道右边的命令是在子 Shell 里执行的,子 Shell 里用 read 赋的变量,不会回传到父 Shell。如果你需要在管道里修改变量,建议改用进程替换(< <(命令))或者干脆把整个操作写进一个块级重定向里。
再看一个command not found的隐蔽情形:你写了一个叫test.sh的脚本,执行./test.sh时报错bad interpreter。这通常是你脚本第一行 shebang 写错了,比如写成了#!/bin/bash\r,原因无他,就是 Windows 编辑器换行符没关干净。解决办法是用sed -i 's/\r$//' test.sh把回车去掉。这种问题特别气人,因为从内容上完全看不出哪里不对。
6.4 常见坑速查表
我把这些年碰到的高频坑浓缩成一张速查表,建议你直接存下来:
| 症状 | 根因 | 对策 |
|---|---|---|
变量赋值报command not found | 等号两边有空格 | 去掉空格 |
if判断报语法错误 | [两侧没留空格 | 保持括号与内容有空格 |
| 文件名带空格被拆成两个参数 | 变量没加引号 | 改成"$var" |
| 文件不存在时循环还在跑 | 通配符没匹配到文件 | 加 `[ -e "$f" ] |
adb shell权限不足 | 设备未 root 或系统服务限制 | 确认命令是否需要 root |
| 后台任务随终端关闭而消失 | 缺少nohup | 使用nohup cmd & |
| 脚本里 cron 跑不到命令 | PATH 环境变量不完整 | 脚本内手动 export PATH |
uiautomator dump卡住 | 屏幕熄灭/界面不稳定 | 先唤醒屏幕,等界面稳定 |
这张表不追求面面俱到,只把日常脚本中出现频率最高的情形列出来。你只要照着检查一遍,大部分问题都能在五分钟内定位。
7. 继续进阶:从 100 例到身边的 Shell
7.1 怎么用“Shell 脚本编程 100 例”这类资料
网上流传着不少“Shell 脚本编程 100 例”“Shell 面试题合集”之类的资料,很多人拿到手就从头开始刷,刷到第 20 个就放弃了。我不建议这么干,因为那些例子覆盖面很广,但你真正用到的可能就集中在文件处理、进程管理、网络连通性这几个方向。我的用法是把它当成字典:拿到一个不熟悉的脚本时,先去里面查同类结构。比如你看到awk的用法不太熟,就去例子里搜 awk 相关的例子,对照着运行一遍,再改改参数试一次。这种“按需检索+动手改”的方式,比顺着目录一个一个背要有效得多,也更能把知识留在脑子里。
7.2 那些名字里带 Shell 的东西:GNOME、EDA 与 Windows 管理 Shell
搜索热词里还有一些不那么“标准”的 Shell,比如tiling shell 增强型平铺,它其实是一个 GNOME 桌面扩展,作用是让桌面窗口具备平铺式布局,这里的 Shell 指的是桌面环境外壳,不是我们前面一直在聊的命令解释器。再比如quest activeroles management shell for active directory,这是 Windows 生态里用来管理 Active Directory 的命令环境,名字里也带 Shell,而且确实需要写命令、处理变量,概念上和 Bash 有共通之处。
还有一个我提过不少次的方向,如何从零开始学 synopsys dc shell。如果你做数字 IC 设计,DC(Design Compiler)工具里的 DC Shell 虽然外表也是命令交互环境,但它本质上是基于 Tcl 语法的工具壳。学这类工具型 Shell 时,你最需要迁移的是“变量+循环+条件判断+命令调用”这套思维,语法细节则以官方手册为准。总之,别因为都叫 Shell 就觉得全都会,也别因为外形陌生就不敢碰,底层逻辑是共通的。
7.3 我的学习顺序建议
到了这一节,我给一个我自己带人时经常用的学习路线,希望对你有参考价值。第一步,先把ls、cd、cp、mv、rm、cat、less、grep、find、awk、sed这些高频命令混个脸熟,不用全部精通,但要做到看到名字能想象到它是干嘛的。第二步,学会把两个命令用管道组合起来,比如查某个进程:ps -ef | grep java。第三步,开始写 20 行以内的小脚本,只做一件事:批量处理文件名、输出一段日志、定时备份目录。第四步,再把循环、变量、参数、函数揉进去,试着写一个带选项的完整小工具。第五步,去解决真实问题,比如配合 ADB 做自动化、配合 SSH 做远程部署,这时候你已经具备了“自学任何 Shell 变种”的底子。
我现在做事情的习惯是,能用一个 Shell 脚本解决的事,绝不动手一步步做;五分钟以内能临时手敲的命令,也绝不多留垃圾文件。每次有同事惊讶地说“你刚才这几步怎么这么快”,我心里想的就是:这些快感,都是被当初那些到处踩坑的夜晚换来的。你如果照着上面这几章的思路走,该踩的坑我都替你列完了,剩下的就只有一个任务:打开终端,真的去写它一遍。你会发现,Shell 远比想象中直接、强大,也远比想象中更好玩。