Linux 常用命令与文本三剑客教程:从入门到进阶

Linux 常用命令与文本三剑客教程:从入门到进阶
Photo by Lukas / Unsplash

引言:为何要精通命令行?

在图形化界面(GUI)主导的今天,为何我们仍需回归看似“原始”的命令行(CLI)?对于工程师而言,命令行不仅是操作系统暴露的底层接口,更是一种高效、可编程、可复现的通用“语言”。它能让你:

  • 自动化重复任务:通过脚本批量处理文件、部署应用、监控系统,极大提升效率。
  • 深入理解系统:直接与操作系统交互,观察进程、网络、文件系统的真实状态,培养“庖丁解牛”般的洞察力。
  • 驾驭无界面环境:在服务器、容器、CI/CD 等环境中,命令行是唯一且最高效的交互方式。
  • 构建强大工具链:将多个简单命令通过管道组合,以“流”的方式处理数据,构建出远超单个 GUI 工具能力的强大工作流。

本教程旨在帮助已有一定开发经验的工程师,系统性地掌握 Linux 环境下最高频的命令、I/O 与管道思想,以及被誉为“文本三剑客”的 grepsedawk 的核心用法与思维模型。无论你是后端、数据、还是平台工程师, mastery of these tools will prove to be a significant force multiplier in your daily work.


一、 命令行基础与通用技能

本章节覆盖了日常工作中 80% 的高频命令,掌握它们是高效操作 Linux 系统的基石。

1. 路径与文件管理

核心心法:在 Linux 中,“一切皆文件”。目录是特殊的文件,设备也是文件。理解文件系统层次结构与路径的表达方式是第一步。

命令
核心功能
常用示例
ls
列出目录内容
ls -alF:显示所有文件(含隐藏)、长格式、并标记类型。
cd
切换当前目录
cd ~ (回家), cd - (回上个目录), cd ../ (去上级目录)。
pwd
显示当前工作目录
pwd:打印绝对路径。
tree
以树状图显示目录结构
tree -L 2 -d:仅显示目录,最大深度为 2。
mkdir
创建目录
mkdir -p project/src/main:递归创建多层目录。
rm / rmdir
删除文件/空目录
rm -rf build/ (危险!强制递归删除), rmdir empty_dir
cp / mv
复制/移动或重命名
cp -r src/ build/ (复制目录), mv old.txt new.txt (重命名)。
ln
创建链接
ln -s /usr/bin/python3 python (软链接), ln file.txt hard_link (硬链接)。
通配符 (glob)
匹配文件名模式
ls *.log (*), cat data_?.csv (?), cp assets/[a-z]*.png ([]).
隐藏文件
. 开头的文件
ls -als -A (不显示 . 和 ..) 可查看。

软链接 (Symbolic Link) vs. 硬链接 (Hard Link)

  • 软链接:像一个快捷方式,存储的是指向源文件的路径。删除源文件,链接失效。
  • 硬链接:同一个文件内容的多个名字。所有硬链接共享 inode 和数据块,删除任意一个链接不影响文件实体,直到最后一个链接被删除。

2. 内容查看与文本工具

命令
核心功能
常用示例
cat / tac
查看全部内容 / 倒序查看
cat file.txttac file.txt
head / tail
查看文件头部/尾部
head -n 20 file.log, tail -f app.log (实时监控)。
less / more
分页查看文件内容
less large_file.csv (推荐, 功能更强, 可前后翻页)。
nl
添加行号
nl script.sh
wc
统计字数、行数、字节数
wc -l file.txt (仅统计行数)。
cut
按列或字段切分文本
cut -d',' -f1,3 data.csv (CSV 中以逗号分隔,取第1和3列)。
paste
按列合并文件
paste file1.txt file2.txt
tr
字符转换或删除
tr 'a-z' 'A-Z' < input.txt (转大写), tr -d '\r' < win.txt (删回车符)。
sort / uniq
排序 / 去重(相邻行)
sort data.txt | uniq -c (排序、去重并统计次数)。
tee
T 型管道,分流输出
ls -l | tee file.list | wc -l (列表存文件并统计行数)。

3. 搜索与查找

命令
核心功能
常用示例与参数
grep
在文本中搜索模式
grep -rni 'ERROR' /var/log/ (递归、忽略大小写、显示行号搜索)。后续详述。
find
按条件查找文件
find . -type f -name "*.java" (找Java文件)
find / -size +1G (找大于1G的文件)
find . -mtime -7 (7天内修改过的)
find . -path "./target" -prune -o -name "*.rs" -print (排除target目录)
find . -name "*.log" -exec rm {} \; (找到即删,低效)
find . -name "*.log" -exec rm {} + (批量删除,高效)
locate / updatedb
基于数据库快速查找文件
sudo updatedb && locate my_config.conf
xargs
将标准输入转为命令行参数
find . -name "*.tmp" -print0 | xargs -0 rm (安全处理含空格文件名)
cat urls.txt | xargs -I {} curl -O {} (逐行下载URL)
ls *.gz | xargs -P 4 -n 1 tar -xzf (4进程并发解压)

4. 压缩与归档

命令
核心功能与常见组合
常用示例
tar
归档工具 (打包/解包)
tar -cvf archive.tar dir/ (打包)
tar -xvf archive.tar (解包)
tar -czvf archive.tar.gz dir/ (打包并用 gzip 压缩)
tar -xzvf archive.tar.gz (用 gzip 解压并解包)
tar -cjvf archive.tar.bz2 dir/ (bzip2 压缩)
tar -xjvf archive.tar.bz2 (bzip2 解压)
tar -cJvf archive.tar.xz dir/ (xz 压缩, 压缩率最高)
tar -xJvf archive.tar.xz (xz 解压)
gzip / bzip2 / xz
压缩/解压单个文件
gzip file.log -> file.log.gz; gunzip file.log.gz
zip / unzip
跨平台压缩/解压
zip -r archive.zip dir/; unzip archive.zip

5. 进程与作业

命令
核心功能
常用示例
ps
查看进程快照
ps aux (BSD 风格) 或 ps -ef (System V 风格)。
top / htop
实时监控进程动态
htop (推荐, 交互式,色彩丰富)。
pgrep / pkill
按名查找/杀死进程
pgrep -u mojucheng java; pkill -9 -f 'data-process'
kill / killall
发送信号给进程 (默认 SIGTERM)
kill 12345; kill -9 12345 (强制终止); killall nginx
jobs / fg / bg
管理后台作业
ctrl+z (暂停), bg (转后台), jobs (查看), fg %1 (调回前台)。
nice / renice
调整进程优先级
nice -n 10 ./my_job; renice -n 5 -p 12345

6. 磁盘与文件系统

命令
核心功能
常用示例
df / du
查看磁盘/目录空间占用
df -h (人类可读); du -sh * (当前目录各文件/夹大小)。
lsblk / blkid
列出块设备/查看 UUID
lsblk; sudo blkid
mount / umount
挂载/卸载文件系统
sudo mount /dev/sdb1 /mnt/data; sudo umount /mnt/data
chmod / chown / chgrp
修改权限/所有者/所属组
chmod 755 script.sh; chmod u+x script.sh; chown -R user:group dir/
umask
设置默认文件权限掩码
umask 022 (文件 644, 目录 755)。
setfacl / getfacl
设置/获取文件访问控制列表 (ACL)
setfacl -m u:otheruser:rwx file; getfacl file

7. 用户与身份

命令
核心功能
常用示例
useradd / userdel / usermod
增/删/改用户
sudo useradd -m -s /bin/bash newuser; sudo usermod -aG docker user
passwd
修改密码
passwd (修改自己); sudo passwd user (修改他人)。
id / groups / sudo
查看身份/所属组/以 root 执行
id mojucheng; sudo -l (查看可执行的 sudo 命令)。

8. 网络与诊断

命令
核心功能
常用示例
ip
现代网络配置工具 (替代 ifconfig)
ip addr show; ip route
ss / netstat
查看网络连接/套接字统计
ss -tuln (推荐, 更快); netstat -anp (经典)。
ping / traceroute
网络连通性测试 / 路由追踪
ping baidu.com; traceroute baidu.com
nc (netcat)
网络工具中的“瑞士军刀”
nc -zv host 80 (端口扫描); echo "hello" | nc host 1234 (发数据)。
curl / wget
HTTP 请求与文件下载
curl -L http://example.com; wget -c http://example.com/file.zip
dig / nslookup
DNS 查询
dig A baidu.com; nslookup baidu.com

9. 日志与系统服务

命令
核心功能
常用示例
journalctl
查询 systemd 日志
journalctl -u nginx.service -f (实时看 Nginx 日志)。
systemctl
管理 systemd 服务
systemctl status nginx; systemctl start/stop/restart/enable/disable nginx
timedatectl
管理系统时间与时区
timedatectl set-timezone 'Asia/Shanghai'
crontab / at
计划任务 / 一次性任务
crontab -e (编辑); at 2pm + 3 days (指定未来时间)。

10. Shell I/O 与组合

核心心法:这是命令行的精髓所在!将命令视为处理“数据流”的节点,通过 I/O 操作将它们灵活地“焊接”在一起。

  • 标准输入/输出/错误 (stdin/stdout/stderr)
    • stdin (文件描述符 0): 默认是键盘输入。
    • stdout (文件描述符 1): 默认是屏幕输出。
    • stderr (文件描述符 2): 默认是屏幕输出,用于错误信息。
  • 重定向 (Redirection)
    • >: 将 stdout 重定向到文件 (覆盖)。command > file.out
    • >>: 将 stdout 重定向到文件 (追加)。command >> file.log
    • <: 将文件内容作为 stdincommand < file.in
    • 2>: 将 stderr 重定向到文件。command 2> file.err
    • 2>&1: 将 stderr 重定向到 stdout
    • &>: (bash 简写) 将 stdoutstderr 都重定向。command &> file.all
  • 管道 (Pipe |)
    • 将前一个命令的 stdout 连接到后一个命令的 stdin
    • 示例: cat access.log | grep '404' | wc -l (统计访问日志中 404 错误的数量)。
  • 子进程与命令替换
    • $(command)`command`: 将命令的 stdout 作为变量值或参数。
    • 示例: echo "Today is $(date)"
  • 导出与引用
    • export VAR=value: 设置环境变量,使其在子 shell 中可用。
    • 单引号 '': 强引用,内部所有字符都视为字面量。echo 'Hello $USER' -> Hello $USER
    • 双引号 "": 弱引用,$\``、\` 会被解析。echo "Hello $USER" -> Hello mojucheng

二、 文本三剑客系统讲解:grep, sed, awk

如果说管道是命令行的“龙骨”,那么 grep, sed, awk 就是龙骨上最锋利的“三齿”。它们分别代表了文本处理的三个核心能力:筛选 (Searching)编辑 (Editing)报告 (Reporting)

1. grep:文本筛选大师 (Global Regular Expression Print)

grep 的核心任务是从文本流中筛选出匹配特定模式的行。

参数/概念
核心功能
常用示例
-E, -P
使用扩展正则(ERE) / Perl兼容正则(PCRE)
grep -E 'warn|error' (匹配 warn 或 error), grep -P '\d{3}' (匹配三位数字)。
-w, -x
匹配整个单词 / 匹配整行
grep -w 'is' (不匹配 this), grep -x 'root:.*' (精确匹配以 root: 开头的行)。
-o, -n, -r, -l
仅显示匹配部分 / 显示行号 / 递归搜索 / 仅显示文件名
grep -o '[0-9.]\+' access.log (提取 IP), grep -rl 'TODO' . (查找含 TODO 的文件)。
-v
反向匹配 (不包含模式的行)
ps aux | grep -v 'grep' (过滤掉 grep 进程本身)。
-A, -B, -C
显示匹配行的后/前/上下文 N 行
grep -C 2 'Exception' app.log
--include, --exclude
递归搜索时包含/排除特定文件
grep -r --include='*.py' --exclude='*_test.py' 'import os' .

正则表达式方言 (BRE/ERE/PCRE)

  • BRE (Basic): 默认模式,?, +, {}, |, () 等需转义,如 \\(a\\|b\\)
  • ERE (Extended, -E): 无需转义 ?, +, {}, |, ()。更常用,推荐。
  • PCRE (Perl-compatible, -P): 功能最强,支持 \d, \s, look-around 等高级语法,但并非所有 grep 版本都支持。

常见正则速查

  • ^: 行首, $: 行尾
  • .: 任意单字符, *: 前项 0 或多次, +: 前项 1 或多次, ?: 前项 0 或 1 次
  • [abc]: 字符集, [^abc]: 排除字符集
  • {n}, {n,}, {n,m}: 精确/范围重复次数
  • (a|b): 分组或, \b: 单词边界

2. sed:流编辑器 (Stream Editor)

sed 是一种非交互式的行编辑器,它逐行读取文本,应用指定的编辑命令,然后输出结果。核心是 地址{命令} 的模式。核心心法sed 的世界里,你是一位“流水线工人”,面对传送带上(标准输入)源源不断的产品(文本行),你根据图纸(脚本命令)对其进行修改、丢弃或复制,然后放到另一条传送带上(标准输出)。

命令/概念
核心功能
常用示例
地址与范围
指定命令作用的行
sed '3d' file (删第3行), sed '/^#/d' file (删注释行), sed '1,10s/foo/bar/' file (1-10行替换)。
s/regexp/replacement/flags
替换 (substitute)
sed 's/localhost/127.0.0.1/g' (全局替换)
sed 's/\([0-9]\{4\}\)-\([0-9]\{2\}\)/\2-\1/' (用 () 分组并后向引用 \1, \2)
sed 's/USER=.*/USER=admin/i' (忽略大小写)
d
删除 (delete)
sed '/^$/d' file (删除空行)。
p
打印 (print)
sed -n '/ERROR/p' file (与 -n 配合,仅打印匹配行)。
y/source/dest/
字符变换 (transform)
sed 'y/abc/ABC/' (a->A, b->B, c->C)。
a / i / c
追加 / 插入 / 更改
sed '/start/a \-- new line after --' file
h/H/g/G/x
保持/交换空间 (hold/get/exchange)
sed -n '1h; ${G;p;}' file (高级用法: 调换首末行)。
-n, -e, -i
静默模式 / 多脚本 / 原地修改
sed -n 'p' (取消默认输出), sed -e 's/a/A/' -e 's/b/B/', sed -i.bak 's/old/new/' file (修改并备份)。

安全替换直接使用 sed -i 有风险,推荐使用 sed -i.bak 创建备份,或者先预览结果再决定是否覆盖:sed 's/old/new/g' file > file.tmp && mv file.tmp file

3. awk:全能文本报告生成器

awk 不仅仅是一个工具,它本身就是一门编程语言。它将每一行文本按指定分隔符切分为字段,然后让你基于模式对这些字段进行计算、处理和格式化输出。核心心法awk 的世界里,你是一位“数据分析师”,面对一张张报表(文本行),你读取每个单元格(字段),进行计算、判断和聚合,最终生成一份新的、结构化的报告。awk 的基本模型是 PATTERN { ACTION }。对每一行,如果匹配 PATTERN,则执行 { ACTION }核心变量与概念

变量/概念
解释与示例
FS, RS
字段分隔符 (Field Separator), 记录分隔符 (Record Separator)。默认 FS 是空格/Tab,RS 是换行符。awk -F, (CSV)。
OFS, ORS
输出字段/记录分隔符。awk -F, 'BEGIN{OFS="\t"} {print $1,$2}' (输入逗号分隔,输出Tab分隔)。
NF, NR, FNR
当前行的字段数 (Number of Fields), 已处理的总行数 (Number of Records), 当前文件的行数 (File Number of Records)。
$0, $1, $2...
$0 代表整行,$1, $2 代表第 1、2 个字段。
BEGIN, END
BEGIN 在处理第一行前执行 (初始化),END 在处理完最后一行后执行 (汇总)。 awk 'BEGIN{print "START"} {print} END{print "END"}'

内建函数与编程结构

  • 字符串函数: length(), substr(), split(), match(), gsub()/gensub()
  • 算术运算: + - * / %
  • 控制流: if/else, while, for
  • 数组 (关联数组): awk 的数组是关联数组 (类似 Python 的 dict 或 Java 的 Map)。awk '{counts[$1]++} END{for(ip in counts) print ip, counts[ip]}' access.log (统计各 IP 访问次数)

典型 awk 用法

场景
示例
列选择与重排
ls -l | awk '{print $9, $5}' (打印文件名和大小)。
条件筛选
awk '$3 > 100' data.txt (打印第3列大于100的行)。
计算与聚合
cat scores.txt | awk '{sum+=$1} END{print "Average:", sum/NR}' (计算平均分)。
格式化输出
df -h | awk 'NR>1 {printf "Usage of %s is %s\n", $6, $5}'
处理 CSV
awk -F, '$2 == "admin" {print $1, $4}' users.csv

GNU awk (gawk) 进阶特性

  • gensub(): 更强大的替换函数,可指定替换第几次出现。
  • strftime(): 格式化时间戳。
  • @include: 包含其他 awk 脚本。
  • ENVIORN: 访问环境变量。
  • 真正的多维数组。

三、 进阶范式与高效实践

掌握单个命令是基础,将它们组合成优雅、高效、健壮的工作流,才是真正拉开差距的地方。

1. 管道化思维:构建你的数据处理流水线

忘掉繁琐的 GUI 点击操作,将你的任务分解为标准的“生产-消费”流程。这种思维模型具有极强的复用性和扩展性。核心流程产生数据 → 筛选 → 转换 → 聚合 → 格式化 → 输出暂时无法在飞书文档外展示此内容

  • 产生数据: cat, ls, curl, journalctl, ps ... 任何能产生文本输出的命令。
  • 筛选: grep 找出你关心的行。
  • 转换: sed 清洗、归一化数据;awk 提取、重排字段;tr 转换字符。
  • 聚合: sort | uniq -c 排序、计数;awk 使用数组进行复杂聚合。
  • 格式化输出: awk '{printf ...}' 生成报告;tee 分流保存中间结果。

2. find + xargs: 海量文件处理的黄金搭档

当需要对大量文件执行同一操作时(如删除、修改、压缩),find -exec ... {} \; 方式效率低下,因为它为每个文件都启动一个新进程。正确姿势:

  • find ... -exec ... {} +: find 会将文件名凑成一批,然后一次性传给 exec 后面的命令,大大减少进程创建开销。
  • find ... -print0 | xargs -0 ...: 终极方案,完美解决文件名中包含空格、换行符等特殊字符的问题。
    • -print0: find 以 NULL 字符 (\0) 分隔文件名输出。
    • -0: xargs 以 NULL 字符作为输入分隔符。
# 安全高效地删除所有临时文件
find . -type f -name "*.tmp" -print0 | xargs -0 rm

3. sort/uniqLC_ALL=C

sort 的行为受本地化设置(Locale)影响,这可能导致排序结果不符合预期或性能下降。

  • LC_ALL=C: 强制 sort 使用最原始的、基于字节值的排序规则。
    • 优点 1: 加速。字节排序比按字典序快得多。
    • 优点 2: 稳定。结果唯一确定,不受系统语言环境影响。
# 对大数据集进行排序和去重计数时,推荐使用
export LC_ALL=C
cat large_log.txt | sort | uniq -c | sort -nr # 找出出现频率最高的行

4. 大文件与流式处理

  • 避免读入内存: 命令行工具天然善于流式处理,逐行读取,内存占用极低。多用管道,避免将大文件内容存入 shell 变量。
  • tee 分支: 在长管道中,有时需要保存中间结果用于调试或存档。tee 在此时非常有用。... | tee stage1.log | grep ... | tee stage2.log | ...
  • 并发处理: xargs -P 可以轻松实现并发。cat image_list.txt | xargs -P 8 -I {} convert {} {}.webp (使用 8 个进程并发转换图片格式)注意: 确保任务之间是独立的,没有共享状态或资源竞争。

5. 可见性与边界(概念类比)

虽然 Linux 没有像 Bazel 那样强制的包边界和可见性规则,但我们可以借鉴其思想来组织项目和脚本,形成逻辑上的“模块”。

  • 目录结构: 将功能相关的脚本、配置文件放在同一目录下。
  • 接口脚本: 在目录顶层提供一个或少数几个入口脚本 (类似 BUILD 文件中的 xx_library),内部实现细节对外部隐藏。
  • 依赖管理: 尽量通过参数传递依赖(如配置文件路径),而不是依赖硬编码的全局路径或环境变量。这使得你的脚本更具可移植性和可测试性。

四、 实战示例与对照表

理论结合实践,才能真正掌握。

1. 三剑客速查对照表

这张表帮助你快速映射同一个需求在三剑客中的不同实现方式。

功能/概念
grep
sed
awk
核心模型
行匹配与打印
地址+命令,流式编辑
模式+动作,字段处理
匹配/筛选
grep 'pattern'
sed -n '/pattern/p'
awk '/pattern/{print}'awk '$N ~ /pattern/'
替换
(不直接支持)
sed 's/old/new/g'
awk '{gsub(/old/, "new"); print}'
字段/列操作
grep -o + 其他工具
复杂正则分组 s/(\w+):(\d+)/\2:\1/'
awk '{print $2, $1}'
分组与聚合
grep ... | sort | uniq -c
(不擅长, 需高级技巧)
awk '{counts[$1]++} END{...}'
格式化输出
(有限)
(有限, a/i/c 命令)
printf 函数
脚本能力
无 (仅正则)
有限 (分支、循环)
完整编程语言 (变量, 数组, 函数, 控制流)

2. 高频命令速查表

类别
命令
核心参数
一行示例
文件
ls
-a, -l, -h, -t, -r
ls -ltra:按时间倒序列出所有文件的详细信息。
find
-name, -type, -mtime, -size, -exec, -prune
find . -type f -name "*.py" -mtime -1:查找一天内修改过的 Python 文件。
cp
-r, -p, -a, --parents
cp --parents src/com/app/Main.java dest/:连同父目录结构一起复制。
文本
grep
-i, -r, -n, -v, -o, -E, -C
grep -Eiron 'fail|denied' /var/log/:递归搜日志中 fail 或 denied。
sed
-i, -n, -e, s///, d, p
sed -i 's/api.example.com/api.prod.com/g' config.yaml:原地替换配置文件域名。
awk
-F, BEGIN, END, NR, NF, print, printf
awk -F: '{print $1}' /etc/passwd:打印所有用户名。
sort
-n, -r, -k, -u, -t
sort -t',' -k3 -nr data.csv | head -n 10:按第3列数字倒序排CSV,取Top 10。
进程
ps
aux, -ef, -o
ps -eo pid,ppid,cmd,%mem,%cpu --sort=-%mem | head:按内存使用率看进程。
top/htop
(交互式)
htop -u www-data:只看 www-data 用户的进程。
pkill
-f, -u, -9
pkill -9 -f "stale-worker":强制杀死名字含 "stale-worker" 的进程。
网络
ss
-t, -u, -l, -n, -p
ss -tlpn | grep ':80':查看谁在监听 80 端口。
curl
-X, -H, -d, -s, -o, -L
curl -s -X POST -H "Content-Type: application/json" -d '{"key":"val"}' http://api/v1/resource

3. 两套可复制的最小实践

a) 日志分析:统计 Nginx 日志 Top 10 访问 IP

目标:从原始 Nginx access.log 中,找出访问量最高的 10 个 IP 地址。日志格式 (示例):192.168.1.1 - - [19/Apr/2026:10:00:00 +0800] "GET /index.html HTTP/1.1" 200 1234 ...一步到位管道命令:

cat access.log | awk '{print $1}' | sort | uniq -c | sort -nr | head -n 10

分解步骤:

  1. cat access.log: 产生数据源。
  2. awk '{print $1}': awk 默认以空格为分隔符,$1 就是 IP 地址。(转换)
  3. sort: 对 IP 地址进行排序,为 uniq 做准备。
  4. uniq -c: 对已排序的 IP 进行去重,并统计每个 IP 出现的次数。(聚合)
  5. sort -nr: -n 按数值排序,-r 倒序。即按访问次数从高到低排序。(聚合/排序)
  6. head -n 10: 取出前 10 行,即 Top 10 结果。(格式化/输出)

b) CSV 处理:清洗并统计数据

目标:有一个 sales.csv 文件,统计每个城市的总销售额,并忽略无效数据行。sales.csv (示例):

ID,City,Product,Amount
1,Shanghai,Book,100
2,Beijing,Pen,10
# This is a comment
3,Shanghai,Book,150
,Invalid,Data,
4,Beijing,Paper,20

一步到位管道命令:

cat sales.csv | sed '/^#/d;/^,/d' | awk -F, 'NR > 1 {sales[$2]+=$4} END {for(city in sales) print city","sales[city]}' > city_sales.csv

分解步骤:

  1. cat sales.csv: 产生数据源。
  2. sed '/^#/d;/^,/d': 使用 sed 清洗数据。/^#/d 删除注释行,/^,/d 删除以逗号开头的无效行。(转换/清洗)
  3. awk -F, 'NR > 1 {sales[$2]+=$4} END {for(city in sales) print city","sales[city]}'
    1. -F,: 设置字段分隔符为逗号。
    2. NR > 1: 跳过表头。
    3. {sales[$2]+=$4}: (核心聚合) 以城市名 ($2) 为 key,累加销售额 ($4) 到关联数组 sales
    4. END {...}: 所有行处理完毕后,遍历 sales 数组,打印 "城市,总销售额" 格式的结果。(格式化/输出)
  4. > city_sales.csv: 将最终报告输出到新文件。

4. 易错点与替代方案

  • grep -P 可用性: grep -P 依赖 PCRE 库,在某些极简系统或 macOS 上可能默认不安装。可使用 grep -E 替代多数场景,或安装 pcre 包。
  • BSD/GNU 差异: macOS 默认的命令行工具 (如 sed, awk) 是 BSD 版本的,参数和行为与 Linux 上常见的 GNU 版本有差异。例如,BSD sed-i 必须提供备份文件后缀 (sed -i '.bak' ...)。可通过 brew install gnu-sed 安装 GNU 版本,并用 gsed 调用。
  • 安全替换: 重申,直接 sed -i 是危险操作。善用备份 sed -i.bak,或预览 sed ... file > file.tmp && mv file.tmp file。对于复杂替换,有时 awk 更安全可控。
  • 文件名中的空格: 老生常谈,但极易出错。在 for 循环或管道中处理文件名时,始终考虑使用 find ... -print0 | xargs -0 ... 或在 shell 脚本中设置 IFS=$'\\n'