☰
Linux命令行与Shell脚本学习:从工具书到可维护脚本实践
2026/11/3 22:52:56 网站建设 项目流程

Linux 命令行与 Shell 脚本这两样东西,在圈子里一直是“会的人不当回事,不会的人觉得玄”的典型代表。这些年我带过不少新人,也看过很多团队的部署脚本,发现一个规律:真正把Linux 命令行敲成肌肉记忆、把Shell 脚本写成可维护工具的人,往往不是啃完某本厚书的人,而是把一本手册当字典、边查边用、反复折腾出来的。《Linux 命令行与 Shell 脚本编程大全》第 3 版这类书,价值就在这里——它不是用来“读完”的,是用来“查”和“对照”的。最近总能看到有人搜“PDF 全本 21MB 网盘下载”这种关键词,标题里还挂着“免费”,说实话我能理解这种心情:一本七八百页的厚书,买之前想知道值不值。但真从技术成长角度看,纠结于从哪儿搞到一份电子档,性价比极低,甚至可能把时间浪费在版本残缺、缺章节、排版错乱的文件上。这篇东西不提供任何下载链接,也不打算讨论资源渠道,我想聊的是更实际的问题:拿到这类资料之后,怎么把它用成真正能提升战斗力的东西,以及命令行和脚本这两块到底该按什么顺序啃、在哪儿容易翻车。不管你是刚接触终端的新手,还是写了几年脚本但总觉得自己写的东西“不太对味”的老手,下面这些内容应该都能直接拿去用。

1. 别急着找文件,先把“资料这件事”想明白

1.1 为什么一本厚书总被压缩成一个下载链接

技术书在传播链条里有个很尴尬的处境:它的内容密度高、生命周期长,但阅读门槛也高。一本讲命令行的书,前三分之一你可能一周就翻完了,后三分之二可能三年都用不上,直到某天要写一个定时清理任务才想起来“好像在哪一章见过”。这种“低频高价值”的属性,天然适合做成工具书而不是通读书。所以当它被压缩成“21MB PDF 全本 网盘下载”这样一行标题时,本质上是把一个长期参考物简化成了一次性获取行为,这事本身就有点错位。

我的看法很直接:如果你只是想要一份“遇到问题能 Ctrl+F 搜一下”的东西,那么官方手册、man页、发行版自带的文档,甚至命令行自带的--help,覆盖度往往比一本十几年前定稿的书更高、更新。书的优势不在于“全”,而在于结构和取舍——它告诉你哪些命令值得先学,哪些参数组合是日常高频,哪些坑前人也踩过。这种东西,你得按它的结构去读才有收益。

注意:来源不明的电子文档有实际风险。除了内容缺页、排版错乱之外,可执行文件格式的“电子书”还可能是别的东西。技术资料这件事,宁可用官方文档加一本正规渠道拿到的书,也别在来路不明的文件上赌运气。

1.2 纸质、电子、在线文档,三种载体的真实分工

我自己的组合是这样的:纸质或正版电子书用于建立结构,官方在线文档用于查最新参数,自己写的笔记仓库用于沉淀经验。三者分工明确,谁也不替代谁。

载体最适合的场景明显短板我的使用频率
纸质书系统学习、建立知识地图、通勤时翻查不动、太重、版本固定低,但学新领域时集中用
正版电子书关键词检索、跨章对照容易变成“收藏夹吃灰”中
官方在线文档参数确认、版本差异、废弃项无结构、面向查阅不面向学习极高
个人笔记仓库记录踩坑、复用脚本片段需要长期维护极高

关键在于第三、四行。我见过太多人把“拥有资料”当成“掌握技能”,硬盘里躺着几十本 PDF,man页从来没打开过。真正的分水岭是:你有没有一个自己的命令片段库和踩坑记录。这个仓库从第一天就该建,哪怕一开始只有三个文件。

1.3 拿到资料后的第一个动作,不是翻第一页

如果你手上已经有一本这类书(不管是买的还是借的),我建议第一个动作是翻目录,然后合上,自己画一张知识地图。拿张纸或者开个文档,写下你认为“Linux 命令行”包含哪几块:文件操作、权限、进程、文本处理、网络、包管理、磁盘、用户……写完之后再对照目录,你会立刻发现自己漏了什么、高估了什么。

这个动作的价值在于暴露认知盲区。大多数人第一次画这张图,都会漏掉“进程与信号”“重定向与管道”这两块,而恰恰是这两块决定了你后面写脚本的天花板。我见过不少能熟练敲ls、cd、cp的人,一遇到kill -HUP或者2>&1就卡壳,根源就是当初跳过了那一章。

这张图还有个副作用:它让你知道哪些章节可以暂时跳过。比如你日常工作不碰磁盘阵列和逻辑卷,那部分先放着完全没问题,等真遇到再说。工具书的意义就在于此,不必有“没读完”的心理负担。

2. 知识地图拆解:命令行和脚本该按什么顺序啃

2.1 命令行部分的三层结构,别在表层反复打转

我把命令行能力分成三层,这个划分帮过不少人理清学习顺序:

第一层是路径与文件。ls、cd、cp、mv、rm、find、ln、mkdir。这层的特点是“会用不等于用对”。举个最常见的:rm -rf后面跟一个没打全的变量,一旦变量为空,你删的就是当前目录往上。正确写法是先echo确认,或者用${VAR:?变量为空}让它直接报错退出。这类细节书上有,但多数人扫一眼就过去了。

第二层是权限与所有权。chmod、chown、umask。这层的难点不是命令本身,而是数字权限的心算和umask 的反向逻辑。前者多练几次就顺了(755=rwxr-xr-x,644=rw-r--r--,600是私钥的标准权限);后者容易绕晕,umask 022意味着新建文件默认是666 - 022 = 644,目录是777 - 022 = 755,记住这个减法就通了。

第三层是进程、管道与文本流。ps、top、kill、grep、sed、awk、sort、uniq、xargs,加上重定向和管道。这一层是真正的分水岭——能不能把多个小工具串成一条解决问题的流水线,直接决定了你是“会敲命令”还是“会用命令行工作”。

我的建议是把 70% 的精力压在第三层。前两层是体力活,第三层是思维方式的改变。

2.2 脚本部分的分水岭:从“能跑”到“能维护”

Shell 脚本有个特点:入门极易,上限不高但下限极低。写一个能跑的脚本,可能只需要十分钟;写一个半年后自己还敢改的脚本,需要的是另一套习惯。

我总结的分水岭大概在这几个点上:

  • 有没有错误处理。没有set -e或者显式检查$?的脚本,出错之后会继续往下跑,把错误状态传播到更远的地方。
  • 有没有日志。echo满天飞的脚本,出问题只能靠猜。有个统一的log()函数,带上时间戳和级别,排查效率差好几倍。
  • 有没有参数校验。一上来就$1、$2直接用,用户少传一个参数,脚本的行为就完全不可预测。
  • 有没有把可变的配置抽出来。路径、阈值、超时时间硬编码在脚本中间,改一次要全文搜索。

这四条,书里可能分散在不同章节,但我建议你在学脚本的第一周就把它们当成强制规范。因为习惯这东西,早期养成了就是肌肉记忆,后期纠正成本极高。

2.3 一份非线性的阅读顺序

按书页顺序从第一页读到最后一页,对这类工具书来说是最低效的读法。我给一个更实用的顺序:

  1. 先读文本处理相关章节(grep / sed / awk / 正则)。理由:这是日常收益最高的一块,学完当天就能用在日志分析上,正反馈来得快。
  2. 再读重定向、管道、子 shell。理由:这是把前面工具串起来的基础,理解不了2>&1、$(...)、|三者的区别,后面的脚本会写得很别扭。
  3. 然后读变量、条件判断、循环、函数。这部分是纯语法,顺序读没关系。
  4. 最后回头补进程管理、信号、定时任务。这部分和实际运维场景绑定紧,有具体需求时学得更牢。

至于包管理、网络配置、磁盘管理这些偏系统管理的章节,完全可以当成字典按需查。别让“必须读完”的执念拖住你。

3. 环境搭建:不折腾虚拟机也能把例子跑通

3.1 三种练习环境的取舍

学命令行最怕的是“不敢乱敲”。在生产机上敲错一个命令的代价,足以让人形成心理阴影,从此只敢用ls和cd。所以第一件事是搞一个随便折腾也不会出事的环境。

环境类型上手成本隔离程度适合人群主要限制
本机终端(macOS / 桌面发行版)极低无纯语法练习命令参数与服务器有差异
容器低高大多数场景需要理解挂载与网络
虚拟机中完全系统管理类练习占资源、启动慢

我的选择是:日常语法练习直接在本机终端,因为反馈最快;涉及系统目录、服务、权限的练习用容器或虚拟机。容器在这一块的优势很明显——起一个基础镜像,几秒钟就能拿到一个干净的 root 环境,搞坏了大不了删掉重来,这种“零成本失败”的体验对学习非常重要。

要注意的一点是:容器里的进程管理、systemd 相关的操作和真实主机差别很大,看到systemctl报错不用怀疑自己。这类练习老实上虚拟机。

3.2 练习目录该怎么组织

我见过很多人的练习目录是这样:test.sh、test2.sh、test_new.sh、test_new_final.sh。一周之后自己都不知道哪个是哪个。这不是小问题,混乱的练习目录会让你重复解决已经解决过的问题。

我的组织方式是按主题分目录,每个目录一个有意义的命名:

~/shell-lab/ ├── 01-text/ # 文本处理练习 ├── 02-flow/ # 条件与循环 ├── 03-func/ # 函数与模块化 ├── 04-args/ # 参数解析 ├── 05-log/ # 日志与错误处理 ├── data/ # 固定的测试数据,只读 └── lib/ # 可复用的公共片段

关键在data/和lib/两个目录。前者放固定的测试数据,比如一份两百行的模拟访问日志、几个大小不一的文件,这样每次练awk都有稳定的输入,结果可对比。后者放你沉淀下来的公共函数,比如log()、die()、need_cmd()。写到第三个月你会发现,lib/里那几十行才是真正的资产。

3.3 一个可以直接抄的脚本骨架

下面这个骨架我用了很久,几乎每个新脚本都从这个结构开始。它解决了前面说的“四条分水岭”里的三条:

#!/usr/bin/env bash set -euo pipefail readonly SCRIPT_NAME="$(basename "$0")" readonly LOG_FILE="${LOG_FILE:-/tmp/${SCRIPT_NAME}.log}" log() { local level="$1"; shift printf '%s [%s] %s\n' "$(date '+%F %T')" "$level" "$*" >>"$LOG_FILE" printf '%s [%s] %s\n' "$(date '+%T')" "$level" "$*" >&2 } die() { log ERROR "$*" exit 1 } need_cmd() { command -v "$1" >/dev/null 2>&1 || die "缺少依赖命令: $1" } main() { need_cmd awk need_cmd find log INFO "开始执行" # 主逻辑 log INFO "执行完成" } main "$@"

逐行说一下几个容易忽略的点:

  • #!/usr/bin/env bash比#!/bin/bash更可移植,因为某些系统上 bash 不在/bin下。但要注意,这会让脚本走PATH里找到的第一个 bash,如果你依赖某个特定版本的新特性,这个写法有风险。
  • set -euo pipefail是三个开关的组合,下面单独讲,这是全文最值得记住的一行。
  • readonly修饰的变量在后续被误赋值时会报错,这是给未来的自己上保险。
  • log函数同时输出到文件和 stderr,前者用于事后追查,后者用于实时观察。重定向目标分开是好习惯——如果只写文件,交互式运行时你会一脸茫然。
  • main "$@"这个结构的好处是:函数定义顺序不影响执行,而且所有逻辑在一个函数作用域里,不会污染全局变量。

4. 命令行核心操作拆解:把书上的例子变成肌肉记忆

4.1 set -euo pipefail 到底在防什么

这一行值得单独拿出来说,因为它是 Shell 脚本里性价比最高的三个字符。

set -e:任何命令返回非零退出码,脚本立刻退出。默认行为是继续往下跑,这就导致错误被静默吞掉。但要注意它的坑:-e在某些上下文里不生效,比如命令出现在if条件里、&&/||的左侧、或者前面加了!。知道这个的人不多,导致很多时候“明明加了 -e 还是没退出”。

set -u:引用未定义变量时报错。默认行为是展开成空字符串,这就是rm -rf "$DIR"/变成rm -rf /的根本原因。这个开关能拦下绝大多数“变量没赋值就往下跑”的惨案。

set -o pipefail:管道的退出码取最后一个非零的。默认行为是只取最后一个命令的退出码,所以some_cmd | grep xxx里some_cmd失败了你也发现不了。

注意:-e不是万能的。它管不到函数内部某些情况,也管不住while read循环里的失败。所以关键位置还是要显式|| die "..."。把-e当安全带,不是当刹车。

我个人的习惯是,只要是会被别人执行或者会被定时任务调用的脚本,这三行是强制项。自己临时在终端里敲的一行命令反而不加,因为交互场景下你希望看到错误后继续观察。

4.2 文本处理三件套的最小可用集

grep、sed、awk三个工具,学全了能写一本书,但日常 90% 的场景只需要一小撮用法。我列一下自己实际用到的部分:

grep的高频选项就五个:-r递归、-n显示行号、-i忽略大小写、-v反向匹配、-E扩展正则。组合起来最实用的是:

grep -rn --include='*.conf' -E 'timeout|retry' /etc/myapp/

这行的意思是:在/etc/myapp/下递归搜索,只看.conf结尾的文件,找出包含 timeout 或 retry 的行并显示行号。--include这个参数能省掉大量后续过滤,很多人不知道。

sed我基本只用替换和删除:

sed -i.bak 's/^#\?\(max_conn\)=.*/\1=200/' app.conf

这行做的是“把max_conn那行改掉,不管前面有没有注释符号,同时留个.bak备份”。-i.bak这个写法比裸-i安全得多——GNU sed 和 BSD sed 对-i的处理不一样,macOS 上裸-i会报错,加后缀两边都能跑。

awk我用得最多的是按列统计:

awk '{sum+=$9} END {printf "总流量: %.2f MB\n", sum/1024/1024}' access.log

以及分组计数:

awk '{print $1}' access.log | sort | uniq -c | sort -rn | head -20

这条流水线是“Top 20 来源 IP”的经典写法,几乎每个做过日志分析的人都写过。拆解一下:取第一列 → 排序(uniq只处理相邻重复,所以必须先排)→ 计数 → 按数值倒序 → 取前 20。这条链路的顺序不能换,换了结果就不对,这也是理解管道思维最好的例子。

4.3 管道和重定向的思维模型

很多人对2>&1、>/dev/null、&>这些是背下来的,不是理解的。我给一个能一次性理清的模型:文件描述符是数字,重定向是复制指针。

标准输入是 0,标准输出是 1,标准错误是 2。2>&1的字面意思是“把 fd 2 指向 fd 1 当前指向的地方”,注意是复制指针,不是复制内容。所以顺序极其重要:

cmd > out.log 2>&1 # 正确:先让 1 指向文件,再让 2 指向 1 指向的文件 cmd 2>&1 > out.log # 错误:先让 2 指向 1 当前的位置(终端),再让 1 指向文件

第二种写法里,stderr 还是输出到终端,只有 stdout 进了文件。这个区别在排查问题时会造成很大困扰,因为你看日志文件发现“怎么什么都没记录”,其实错误都打屏上了。

常用组合我给几个:

cmd >/dev/null 2>&1 # 全部丢弃,只关心退出码 cmd 2>/dev/null # 只要正常输出,错误丢掉 cmd &> out.log # bash 专有,标准输出和错误都进文件 cmd | tee out.log # 同时看屏幕和存文件,排查时超好用

tee这个工具被严重低估了。跑一个耗时的命令,你既想实时看进度又想事后翻记录,| tee -a run.log就是答案。

4.4 用 man 和 --help 替代搜索

一个反直觉的事实:绝大多数命令问题,man页里都有答案,而且比大部分教程准确。难的是很多人不会用man。

几个关键操作:man -k keyword按关键词搜索所有手册页(等价于apropos),这个在你只知道功能不知道命令名时极其有用;man 5 passwd看第 5 类(文件格式)而不是第 1 类(命令);man 页里按/搜索,按n跳下一个匹配。

还有一个习惯值得养:先看--help,再看man,最后才上网搜。因为--help给出的是选项速览,man给出的是完整语义,而网上的答案往往是针对某个特定版本的,照抄容易翻车。尤其是涉及find、tar、rsync这类参数众多的命令,不同版本的行为差异能让你怀疑人生。

提示:tar的参数顺序问题(tar -cvfvstar cvf)在 GNU tar 里都能跑,但在某些 Unix 系统上不加横线会出错。跨平台脚本里老老实实加横线。

5. Shell 脚本进阶:几个决定成败的设计决策

5.1 引号:Shell 里最容易出错的语法

Shell 的引号规则说起来简单,但真正写起来,我见过的脚本里十有八九在某个地方漏了引号。核心就一条:变量展开永远加双引号,除非你明确需要分词。

name="my file.txt" cp $name /tmp/ # 错误:被拆成两个参数 my 和 file.txt cp "$name" /tmp/ # 正确

单引号和双引号的区别也要清楚:单引号里一切原样,双引号里$、反引号、\仍然生效。所以'$HOME'输出字面量$HOME,"$HOME"输出路径。写含正则的sed表达式时,这个区别直接决定能不能跑通。

还有一个高频坑:数组和$@。"$@"和$@差一层引号,行为完全不同:

for f in "$@"; do echo "[$f]"; done # 每个参数作为一个整体 for f in $@; do echo "[$f]"; done # 参数会被再次分词,含空格的路径直接报废

我的规范是:脚本里看到的每一个变量展开都加双引号,只有三种例外——明确要分词、在[[ ]]里做模式匹配、算术上下文。这条规则简单粗暴但有效,能挡掉八成以上的诡异 bug。

5.2 参数解析:别自己数 $1 $2

脚本超过三个参数之后,$1、$2这种写法就开始失控了。标准解法是getopts,虽然它只支持短选项,但够用且不需要任何依赖:

usage() { echo "用法: $0 -d 目录 -r 保留天数 [-n 试运行]"; exit 1; } dir=""; keep=7; dry=0 while getopts ":d:r:nh" opt; do case "$opt" in d) dir="$OPTARG" ;; r) keep="$OPTARG" ;; n) dry=1 ;; h) usage ;; \?) echo "无效选项: -$OPTARG"; usage ;; :) echo "选项 -$OPTARG 需要参数"; usage ;; esac done [[ -n "$dir" ]] || usage [[ "$keep" =~ ^[0-9]+$ ]] || { echo "保留天数必须是正整数"; exit 1; }

几个设计细节值得说:getopts前面的冒号(:d:r:nh)表示“静默模式”,这样遇到错误时不会自动打印,而是把?或:交给你自己处理,这样错误提示风格能统一。d:后面的冒号表示这个选项需要参数。参数校验那两行是必须的——永远不要相信调用方传进来的东西,尤其是这个脚本将来会被定时任务调用的时候。

5.3 函数化与日志:让脚本具备可维护性的两件事

Shell 脚本不需要写得像应用代码,但该抽的函数必须抽。判断标准很简单:同一段逻辑出现第二次,就该抽成函数。最常见的是这几类:

  • log/die:日志与错误退出
  • need_cmd:依赖检查
  • confirm:交互确认
  • retry:带重试的操作

retry这个函数值得单独提,因为在脚本里处理瞬时失败是刚需,而很多人是直接用sleep硬等的:

retry() { local max=$1 delay=$2; shift 2 local n=0 until "$@"; do n=$((n + 1)) (( n >= max )) && { log ERROR "重试 $max 次仍失败: $*"; return 1; } log WARN "第 $n 次失败,${delay}s 后重试: $*" sleep "$delay" done }

用法是retry 3 5 curl -fsS http://...。这个模式比写三层嵌套if干净得多,而且"$@"保证了命令里的空格不会被拆散。

日志方面,我的底线是带时间戳,带级别,输出到 stderr。不要用纯echo,因为一旦你要把脚本的 stdout 拿去做数据处理(比如./report.sh > data.csv),日志混进去就把数据污染了。日志走 stderr 是 Unix 的约定,遵守它能让你的脚本和别的工具无缝组合。

5.4 一个完整的实战脚本:日志清理与统计

把上面的东西串起来,写一个“按保留天数清理日志并输出统计”的脚本。这个场景很典型,几乎每个跑服务的环境都会遇到。

#!/usr/bin/env bash set -euo pipefail readonly LOG_DIR="${1:?用法: $0 <日志目录> [保留天数]}" readonly KEEP_DAYS="${2:-7}" readonly DRY_RUN="${DRY_RUN:-0}" log() { printf '%s [%s] %s\n' "$(date '+%F %T')" "$1" "$(shift; echo "$*")" >&2; } die() { log ERROR "$*"; exit 1; } [[ -d "$LOG_DIR" ]] || die "目录不存在: $LOG_DIR" [[ "$KEEP_DAYS" =~ ^[0-9]+$ ]] || die "保留天数必须是数字" # 统计清理前的总占用 before_kb=$(du -sk "$LOG_DIR" | awk '{print $1}') # 找出过期文件:用 -print0 + xargs -0 处理含空格的路径 count=0 while IFS= read -r -d '' f; do count=$((count + 1)) if [[ "$DRY_RUN" == "1" ]]; then log INFO "[试运行] 将删除: $f" else rm -f -- "$f" fi done < <(find "$LOG_DIR" -type f -name '*.log' -mtime "+$KEEP_DAYS" -print0) after_kb=$(du -sk "$LOG_DIR" | awk '{print $1}') freed_mb=$(( (before_kb - after_kb) / 1024 )) log INFO "处理文件数: $count, 释放空间: ${freed_mb} MB"

这段代码里有几个点是刻意设计的:

  • ${1:?...}是“参数为空就报错退出”的写法,比if [ -z "$1" ]简洁,而且错误信息里能带用法提示。
  • ${DRY_RUN:-0}让试运行模式可以通过环境变量开启,这样同一份脚本既能演练又能真跑,不用改代码。
  • find ... -print0配合while IFS= read -r -d '',是处理含空格和特殊字符文件名的标准解法。用for f in $(find ...)的话,文件名里有空格就完蛋,这是新手最常见的坑之一。
  • < <(...)是进程替换,让while循环在当前 shell 里执行。如果写成find ... | while read,循环是在子 shell 里跑的,count变量出了循环就归零,很多人被这个坑过。
  • rm -f -- "$f"里的--表示“后面不再解析为选项”,防止文件名以横线开头时被当成参数。

注意:删除类脚本上线前,务必先用DRY_RUN=1跑一遍,把输出重定向到文件看一眼。我见过不止一次因为-mtime参数写反导致删错范围的事故,而这类事故是不可逆的。

6. 常见问题与排查实录

6.1 “我机器上能跑,换台机器就废”的四类原因

这是脚本类问题里出现频率最高的一类,我统计下来基本逃不出四种原因:

第一是解释器路径不对。#!/bin/bash在某些系统上不存在,脚本直接报“bad interpreter”。解法是用#!/usr/bin/env bash,或者干脆确保脚本用sh兼容写法。但注意,env方案依赖PATH,在 cron 这种精简环境下会出问题,要在 crontab 里显式设置PATH。

第二是命令版本差异。最典型的就是sed -i和date的参数,GNU 和 BSD 两个体系的写法不同。跨平台脚本要么检测系统类型分支处理,要么用更通用的写法绕开。

第三是环境变量缺失。交互式 shell 里~/.bashrc被加载了,所以PATH里有你自己加的目录;cron 执行时只有一个极简环境,你的脚本找不到命令。这是新手踩得最多的坑,排查方法是在脚本开头echo "$PATH"打出来对比。

第四是换行符。Windows 上编辑过的脚本带\r\n,在 Unix 上执行会报$'\r': command not found这种让人摸不着头脑的错误。解药是dos2unix,或者用编辑器统一设置。

现象最可能的原因快速验证
bad interpreter: No such fileshebang 路径错head -1 脚本看第一行
$'\r': command not foundCRLF 换行file 脚本或cat -A
交互式能跑,cron 报 command not foundPATH 不同脚本里打印$PATH对比
Permission denied缺执行位或目录权限ls -l看权限位
管道命令失败但脚本继续跑没开 pipefail检查set -o pipefail

6.2 权限、编码、路径三类“玄学”问题的定位思路

权限问题的定位顺序是固定的:ls -l看文件权限 →namei -l 路径看整条路径上每一级目录的权限 →id看当前用户和所属组。很多人只看了文件本身,忘了父目录没有执行位的话,里面的文件再开放也访问不了。namei -l这个命令知道的人不多,但排查“明明权限对却读不了”这类问题时几乎是神器。

编码问题最容易被误判成逻辑错误。比如用awk处理含中文的文件,结果列对不齐,第一反应是 awk 写错了,实际上是LANG没设成 UTF-8。我的习惯是脚本开头显式声明export LC_ALL=C(处理纯 ASCII 数据)或者export LANG=en_US.UTF-8(处理多语言),这样行为可预期。LC_ALL=C还有个副作用:sort会按字节序而不是本地化顺序排,处理数字和日志时反而更符合预期。

路径问题的核心是相对路径的基准。脚本里用cd $(dirname $0)切目录是常见做法,但$0在某些调用方式下不靠谱(比如被source或者通过符号链接调用)。更稳的写法是:

readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

BASH_SOURCE[0]比$0可靠,cd ... && pwd能拿到解析后的绝对路径。这三行值得背下来。

6.3 用一个静态检查工具兜住低级错误

shellcheck是我推荐的唯一一个“必装”工具。它把很多你意识不到的坑直接标出来:未加引号的变量、无用的cat、[ ]里用==、$?检查位置的错误、可能的 glob 展开问题等等。

shellcheck -S warning myscript.sh

-S是严重级别过滤,warning以上只报真正可能出问题的。新手可以直接从error级别开始修,一条条改下去,改完一轮,你对 Shell 的理解会上一个台阶。我要说的是,这个工具的价值不只是“修 bug”,而是通过它的提示学习惯用法——每一条警告背后都是一个具体的踩坑场景。

提示:不要盲目按 shellcheck 的建议改。有些提示是针对特定 Shell 方言的,比如read -r在某些场景下确实不需要。理解提示背后的原因再改,否则会改出一堆为了消除警告而写的冗余代码。

7. 学习节奏:把一本厚书变成三年的日常积累

7.1 每天二十分钟比周末四小时有效

命令行和脚本这种技能,本质是肌肉记忆加模式识别。肌肉记忆靠重复,模式识别靠场景多样性。这两样都不适合集中突击。

我的建议是每天二十分钟,但要有明确的当天的目标:今天只练find的-newer、-size、-mtime三种时间条件;明天只练awk的多文件处理和NR/FNR的区别。一天一个点,当天就把这个点用在自己机器的真实数据上跑一遍。周末再花一小时把这周的碎片整理进lib/或用例库。

反过来,周末集中四小时的模式,问题在于遗忘曲线。这周学的sed高级用法,两周不碰基本归零。每天碰一下,哪怕只敲几条,保持接触频率才是关键。

7.2 一个可以立刻开始的练手清单

如果你现在不知道从哪儿下手,照这个顺序做,每一步都用真实数据:

  1. 统计本机某个目录下所有文件按扩展名分组的大小分布(find+awk)
  2. 找出一周内修改过的配置文件,按修改时间排序(find -mtime+ls+sort)
  3. 从一份日志里提取出现频率最高的 20 个错误码(grep+awk+sort+uniq)
  4. 写一个把目录下所有.txt批量重命名为带日期前缀的脚本,先DRY_RUN再真跑
  5. 给第 4 步的脚本加上参数解析、日志、失败回滚

走到第 5 步,你大概就理解为什么前面啰嗦那么多“规范”了——当脚本开始处理真实数据、开始被重复调用的时候,那些看似繁琐的写法全都是省时间的。

7.3 我自己的几个长期习惯

最后说几个我坚持了很多年的做法,都不复杂,但复利效应明显。

第一个是命令片段库。我有个文本文件,按主题记录那些“每次都要想一下”的命令组合,比如du -sh * | sort -rh | head -10(当前目录下最大的十个)、ps aux | sort -k3 -rn | head(CPU 占用前十)。这个文件三年积累了两百多条,是我用得最多的“资料”,比任何一本书都高频。

第二个是错误笔记。每次踩坑解决了,花两分钟记下“现象、原因、解法”三行。这个习惯让我在同类问题上基本不重复犯错,因为人的记忆是靠检索线索工作的,记下来才有线索。

第三个是读别人的脚本。系统里/etc/init.d/下的老脚本、开源项目里的安装脚本、同事写的部署脚本,全是免费的教材。看到不认识的写法就查一下,看到写得烂的就想想为什么烂。这个输入源比任何教程都贴近真实场景。

至于那些“PDF 全本 21MB 网盘下载”的搜索结果,我的态度一直很明确:技术资料的获取渠道有很多种,正规渠道、官方文档、开源项目的文档站,都能满足学习需求,而且不会在关键时刻掉链子。真正决定你水平的,从来不是硬盘里存了什么,而是过去三个月你亲手敲过多少条命令、写过多少行能跑起来的脚本、踩过多少个自己填平的坑。这个账,只能自己一笔一笔算。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询