核心概念:标准流与管道原理
理解 Linux 文本处理的基础,是理解标准流(Standard Streams)的概念。每个进程在启动时默认打开三个文件描述符,这是管道和重定向的底层基础。
< file 重定向为文件输入,或通过管道从另一个进程的 stdout 接收数据。grep、awk、sed 等命令既能接受文件参数,也能从 stdin 读取。> file 重定向到文件(覆盖),>> file 追加,或通过管道传给下一命令的 stdin。| 默认不传递 stderr。可用 2>/dev/null 丢弃错误,2>&1 将 stderr 合并到 stdout。cmd1 | cmd2 创建一个内核缓冲区,cmd1 的 stdout 写入缓冲区,cmd2 的 stdin 从缓冲区读取。两个命令并行执行,不是 cmd1 执行完才执行 cmd2。管道满了写端阻塞,管道空了读端阻塞。# 重定向操作符速查
cmd > file.txt # stdout 重定向到文件(覆盖)
cmd >> file.txt # stdout 重定向到文件(追加)
cmd < file.txt # stdin 从文件读取
cmd 2> error.log # stderr 重定向到文件
cmd 2>&1 # stderr 合并到 stdout(顺序很重要!)
cmd > output.txt 2>&1 # stdout 和 stderr 都写入文件(常见写法)
cmd >& output.txt # bash 简写:等同于 cmd > output.txt 2>&1
cmd 2>/dev/null # 丢弃错误输出(/dev/null 是"黑洞")
cmd >/dev/null 2>&1 # 完全静默(stdout 和 stderr 都丢弃)
# Here Document:多行输入(不用写临时文件)
cat <<EOF
Hello World
第一行内容
第二行内容
EOF
# Here String:单行字符串作为 stdin
grep "pattern" <<< "some string to search"
# tee:同时输出到屏幕和文件("T形管道")
./build.sh 2>&1 | tee build.log # 屏幕实时看,同时保存日志
ls -la | tee /tmp/list.txt | wc -l # 保存列表的同时统计行数
cmd > file 2>&1(正确)和 cmd 2>&1 > file(错误)的行为不同。Shell 从左到右处理重定向:正确写法先把 stdout 指向文件,再把 stderr 指向 stdout(此时 stdout 已指向文件),所以两者都进文件。错误写法先把 stderr 指向 stdout(此时 stdout 还是终端),再把 stdout 指向文件,导致 stderr 仍然打印到终端。
查看文件内容
-f 跟踪文件描述符(日志轮转后失效),-F 跟踪文件名(日志轮转后自动重新打开新文件)。# cat:连接并输出文件内容
cat file.txt # 显示完整内容(小文件用)
cat -n file.txt # -n:显示行号
cat -A file.txt # -A:显示不可见字符($ 表示行尾,^I 表示 Tab,排查编码问题)
cat -s file.txt # -s:压缩连续空行为单个空行
cat file1.txt file2.txt # 合并多个文件输出
cat file1.txt >> file2.txt # 将 file1 追加到 file2(注意 >> 不是 >)
# less:分页查看大文件
less /var/log/syslog
# 操作快捷键:
# j/k 或 ↓/↑ 逐行滚动
# f/b 或 PgDn/Up 翻页(forward/backward)
# g / G 跳到开头/结尾
# /pattern 向后搜索(Enter确认,n 下一个,N 上一个)
# ?pattern 向前搜索
# &pattern 只显示匹配行(过滤模式)
# :100 跳到第 100 行
# q 退出
less +F /var/log/app.log # 以 tail -f 模式打开(Ctrl+C 切换到普通模式)
less +G /var/log/syslog # 打开时直接跳到文件末尾
less -S file.txt # 长行不折行(横向滚动),查看宽 CSV 时有用
less -N file.txt # 显示行号
# head/tail:查看文件头尾
head -n 20 file.txt # 前 20 行(默认 10 行)
head -c 100 file.txt # 前 100 字节(-c = bytes)
tail -n 50 file.txt # 最后 50 行
tail -f /var/log/syslog # 实时追踪日志(运维必备!)
tail -F /var/log/app.log # -F 即使文件被轮转也继续追踪新文件
tail -n +100 file.txt # 从第 100 行开始显示(+N 表示从第N行起)
# 组合:实时过滤日志
tail -F /var/log/nginx/error.log | grep --line-buffered "ERROR"
# --line-buffered:强制行缓冲(管道默认块缓冲,会延迟显示)
grep:文本搜索利器
grep(Global Regular Expression Print)是 Linux 最常用的文本搜索工具。它逐行扫描输入,将匹配正则表达式的行打印到 stdout。grep 有三种正则引擎:BRE(基础正则,默认)、ERE(扩展正则,-E)、PCRE(Perl 兼容,-P)。
+ ? | () 需要用 \ 转义才有特殊含义;扩展正则(ERE,-E)中这些字符直接有特殊含义,\+ 反而是字面量加号。现代脚本建议统一使用 grep -E,更直观。^ 匹配行首(行起始位置),$ 匹配行尾(行结束位置)。^$ 匹配空行。注意 ^ 在字符集 [^...] 内是"取反",含义完全不同。\b(ERE/PCRE)匹配单词边界,用于精确匹配单词(如 \broot\b 不会匹配 "uprooted")。# ─────── 基础搜索 ────────────────────────────
grep "error" /var/log/syslog # 搜索包含 error 的行(大小写敏感)
grep -i "error" /var/log/syslog # -i:大小写不敏感
grep -v "debug" /var/log/app.log # -v:反向匹配(排除含 debug 的行)
grep -n "error" file.txt # -n:显示匹配行的行号
grep -c "error" file.txt # -c:只统计匹配行数(不显示内容)
grep -l "import os" *.py # -l:只显示包含匹配的文件名(列表)
grep -L "import os" *.py # -L:显示不含匹配的文件名
grep -w "root" /etc/passwd # -w:精确单词匹配(不匹配 "rootfs" 或 "groot")
grep -x "exact line" file.txt # -x:整行完全匹配
grep -m 5 "error" app.log # -m 5:找到5处即停止(大文件提速)
# ─────── 递归搜索 ────────────────────────────
grep -r "TODO" ./src/ # -r:递归搜索目录
grep -r --include="*.py" "TODO" . # 只搜索 .py 文件
grep -r --exclude="*.min.js" "func" . # 排除压缩文件
grep -r --exclude-dir=".git" "API_KEY" . # 排除 .git 目录(避免搜索版本历史)
# ─────── 上下文显示 ──────────────────────────
grep -A 3 "ERROR" app.log # 显示匹配行及之后 3 行(After)
grep -B 3 "ERROR" app.log # 显示匹配行及之前 3 行(Before)
grep -C 3 "ERROR" app.log # 显示匹配行及前后各 3 行(Context)
# ─────── 正则表达式 ──────────────────────────
# BRE(默认)特殊字符:. * ^ $ [] \
grep "^ERROR" app.log # ^ 行首锚定
grep "\.log$" filelist.txt # $ 行尾锚定(. 需转义)
grep "err.r" app.log # . 匹配任意单字符(包括空格、数字)
grep "colou*r" text.txt # * 前一字符 0 次或多次(u 可以出现 0 次)
# ERE(-E 或 egrep)额外支持 + ? | () {} 无需转义
grep -E "error|warning|critical" app.log # | 或运算
grep -E "colou?r" text.txt # ? 前一字符 0 次或 1 次(color 或 colour)
grep -E "[0-9]+" numbers.txt # + 前一字符 1 次或多次
grep -E "[0-9]{3}-[0-9]{4}" phones.txt # {} 精确重复次数
grep -E "^\s*$" file.txt # 匹配空白行(\s 匹配空格/Tab)
grep -E "^[A-Z][a-z]+" names.txt # 首字母大写的单词
grep -E "https?://[^\s]+" urls.txt # 匹配 URL(http 或 https)
# PCRE(-P)支持 \d \w \s 等 Perl 正则
grep -P "\d{4}-\d{2}-\d{2}" log.txt # \d 匹配数字(比 [0-9] 更简洁)
grep -P "(?<=GET )/\S+" access.log # 向后查找(lookbehind),提取 URL 路径
# 实用组合:统计日志中各级别错误数量
grep -cE "^\[ERROR\]" app.log; grep -cE "^\[WARN\]" app.log
grep 遇到二进制文件(如编译产物 .pyc、图片)时默认输出 "Binary file matches",而不显示匹配内容。用 grep -a(--text)将二进制文件当文本处理,或用 strings binary_file | grep pattern 提取可打印字符后再搜索。在递归搜索代码目录时,应加 --exclude="*.pyc" 排除编译产物,否则可能得到噪音输出。
sed:流编辑器
sed(Stream EDitor)按行读取输入,对每行执行编辑操作(替换/删除/插入/提取),然后输出。sed 内部有一个模式空间(Pattern Space)——当前处理的行的缓冲区,和一个保持空间(Hold Space)——可跨行存储数据的缓冲区。大多数日常使用只需掌握替换和删除。
# ─────── 替换操作 ────────────────────────────
# 基本语法:sed 's/旧文本/新文本/标志'
# s = substitute(替换命令)
# 标志:g = global(全行),i = 忽略大小写,数字N = 替换第N次出现
sed 's/foo/bar/' file.txt # 每行只替换第一次出现
sed 's/foo/bar/g' file.txt # g:全局替换所有出现
sed 's/foo/bar/gi' file.txt # g+i:全局且大小写不敏感
sed 's/foo/bar/2' file.txt # 只替换每行第 2 次出现
# -i:直接修改文件(in-place),不输出到 stdout
sed -i 's/foo/bar/g' file.txt # Linux 直接修改(GNU sed)
sed -i.bak 's/foo/bar/g' file.txt # 修改文件并保留 .bak 备份(推荐!)
# 使用不同分隔符(替换内容含 / 时避免转义地狱)
sed 's|/old/path|/new/path|g' config.txt # 用 | 替代 /
sed 's#http://old.com#https://new.com#g' urls.txt # 用 # 替代 /
# 引用捕获组(& 代表整个匹配,\1 代表第1个括号)
sed 's/\(hello\) \(world\)/\2 \1/' file.txt # BRE:交换两个单词的顺序
sed -E 's/(hello) (world)/\2 \1/' file.txt # ERE(-E):同上,括号不需转义
sed 's/[0-9]*/(&)/' file.txt # & 代表匹配内容:给数字加括号
# 多命令(-e 或分号)
sed 's/foo/bar/g; s/baz/qux/g' file.txt
sed -e 's/foo/bar/g' -e 's/baz/qux/g' file.txt
# ─────── 删除操作 ────────────────────────────
# d 命令:删除模式空间中的内容(跳过该行不输出)
sed '/^#/d' config.txt # 删除注释行(# 开头)
sed '/^\s*$/d' file.txt # 删除空白行(只含空格/Tab 的行)
sed '2,5d' file.txt # 删除第 2 到第 5 行
sed '$d' file.txt # $ 代表最后一行,删除最后一行
sed '1d' file.txt # 删除第一行(如 CSV 的标题行)
sed '/^$/d; /^#/d' file.txt # 同时删除空行和注释(两个条件)
# ─────── 提取操作 ────────────────────────────
# -n 抑制默认输出,p 命令打印模式空间(常见组合:-n + p 只输出匹配行)
sed -n '10,20p' file.txt # 只显示第 10-20 行
sed -n '/ERROR/p' app.log # 只显示包含 ERROR 的行(等同 grep "ERROR")
sed -n '/START/,/END/p' file.txt # 显示 START 到 END 之间的行
# ─────── 实战示例 ────────────────────────────
# 批量替换配置文件中的 IP(.bak 保留备份)
sed -i.bak 's/192\.168\.1\.10/10\.0\.0\.5/g' /etc/hosts
# 去除文件中所有行尾空格
sed -i 's/[[:space:]]*$//' *.txt
# 在第 3 行之后插入新行
sed '3a\新增的一行内容' file.txt # a = append(在指定行后追加)
sed '3i\插入在第3行之前' file.txt # i = insert(在指定行前插入)
macOS 自带的 BSD sed 的 -i 选项必须提供备份后缀(即使是空字符串):sed -i '' 's/foo/bar/g' file。而 Linux 的 GNU sed 允许 -i 不带后缀。如果脚本需要跨平台运行,可以安装 GNU sed(brew install gnu-sed)或统一使用 sed -i.bak(兼容两个平台)。
awk:文本数据处理
awk 是一种完整的文本处理语言(得名于设计者 Aho、Weinberger、Kernighan 的姓氏首字母)。它将输入分割为记录(Records,默认按行)和字段(Fields,默认按空格/Tab),非常适合处理结构化文本(日志、CSV、命令输出)。
BEGIN { } 块(初始化,仅一次);② 逐行读取输入,对每行执行匹配的 pattern { action } 块;③ 运行 END { } 块(汇总,仅一次)。BEGIN/END 中无输入行,不能使用 $1 等字段变量。$0 = 整行;$1~$NF = 第 1 到第 NF 列;NF = 当前行字段总数;NR = 全局行号;FNR = 当前文件的行号(处理多文件时有用);FS = 输入分隔符(默认空白字符);OFS = 输出分隔符(默认空格);RS = 记录分隔符(默认换行)。-F:)时,连续分隔符会产生空字段,行首/行尾的分隔符也会产生空字段。# ─────── 基础列提取 ──────────────────────────
awk '{print $1}' file.txt # 打印第 1 列
awk '{print $1, $3}' file.txt # 打印第 1 和第 3 列(空格分隔)
awk '{print $NF}' file.txt # 打印最后一列($NF = $字段数)
awk '{print $(NF-1)}' file.txt # 打印倒数第 2 列
awk -F: '{print $1}' /etc/passwd # -F 指定输入分隔符(冒号分割用户名)
awk -F, '{print $2}' data.csv # CSV 处理:取第 2 列
awk -F'\t' '{print $1, $2}' data.tsv # Tab 分隔
# 控制输出分隔符(OFS)
awk -F: 'BEGIN{OFS=","} {print $1,$3}' /etc/passwd # 用逗号分隔输出(: 输入 → , 输出)
# ─────── 条件过滤 ────────────────────────────
awk '$3 > 100 {print $0}' data.txt # 第 3 列大于 100 的行
awk '$1 == "alice" {print}' file.txt # 第 1 列等于 "alice" 的行
awk '/ERROR/ {print NR": "$0}' app.log # 匹配 ERROR 的行(附行号)
awk 'NR>=10 && NR<=20' file.txt # 显示第 10-20 行(无 action 默认 print)
awk 'NR%2 == 0' file.txt # 只打印偶数行
awk '$0 !~ /^#/' config.txt # 排除注释行(!~ 不匹配)
# ─────── 统计计算 ────────────────────────────
awk '{sum += $3} END {print "总和:", sum}' data.txt
awk '{sum += $3; count++} END {
printf "总和: %d\n平均: %.2f\n", sum, sum/count
}' data.txt
awk '{if($1 > max) max=$1} END {print "最大值:", max}' data.txt
# ─────── BEGIN / END 块 ──────────────────────
awk '
BEGIN {
print "===== 报告开始 =====" # 处理前执行,可初始化变量、打印标题
FS=":" # 在 BEGIN 中设置 FS(等同 -F:)
count=0
}
/bash$/ { # 只处理以 bash 结尾的行(bash 用户)
count++
print $1, "使用 bash"
}
END {
print "===== 共", count, "个用户使用 bash ====="
}
' /etc/passwd
# ─────── 实战:分析 Nginx 访问日志 ──────────
# 统计 TOP5 访问 IP
awk '{print $1}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head -5
# 统计各 HTTP 状态码数量
awk '{print $9}' /var/log/nginx/access.log | sort | uniq -c | sort -rn
# 计算平均响应时间(假设第 10 列是响应时间)
awk '{sum+=$10; count++} END {printf "平均响应时间: %.2f ms\n", sum/count}' access.log
# 找出响应时间超过 1 秒(1000ms)的慢请求
awk '$10 > 1000 {print $1, $7, $10"ms"}' access.log | sort -k3 -rn | head -20
用 awk -F: 处理 /etc/passwd 时,如果某行有两个连续的冒号(如 alice::1000),中间是空字段,$2 为空字符串,$3 才是 1000。而默认空白分隔符模式下连续空格合并为一个,不会产生空字段。遇到多列数据对不齐的问题,先检查分隔符的一致性。
sort / uniq / wc / cut / tr
# ─────── sort:排序 ──────────────────────────
# 默认按字典序(字符串比较)排序——注意 "10" < "9"!
# 处理数字必须加 -n,否则顺序错误
sort file.txt # 字典序升序
sort -r file.txt # -r:反向(降序)
sort -n numbers.txt # -n:按数字大小(而非字典序)
sort -h sizes.txt # -h:按人类可读大小排序(1K < 1M < 1G)
sort -u file.txt # -u:排序并去重(相当于 sort | uniq)
sort -k2 file.txt # -k2:按第 2 列排序(默认字典序)
sort -k2 -n -r file.txt # 按第 2 列数字降序
sort -k1,1 -k2,2n file.txt # 主键第 1 列字典序,次键第 2 列数字序
sort -t: -k3 -n /etc/passwd # 以 : 分隔,按第 3 列(UID)数字排序
sort --parallel=4 huge_file.txt # 并行排序(利用多核)
# ─────── uniq:去重 ──────────────────────────
# 重要:uniq 只删除相邻的重复行!使用前必须排序!
uniq file.txt # 去除相邻重复行(不排序则效果不完整)
sort file.txt | uniq # 正确用法:先排序再去重
uniq -c file.txt # -c:在行首显示出现次数
uniq -d file.txt # -d:只显示重复行(出现多次的)
uniq -u file.txt # -u:只显示唯一行(出现恰好一次的)
sort file.txt | uniq -c | sort -rn | head # 频率统计经典组合(最高频在前)
sort -i file.txt | uniq -i # -i:忽略大小写去重("Apple" = "apple")
# ─────── wc:统计 ────────────────────────────
wc -l file.txt # -l:统计行数(Lines)
wc -w file.txt # -w:统计单词数(Words,按空白分隔)
wc -c file.txt # -c:统计字节数(Characters/bytes)
wc -m file.txt # -m:统计字符数(多字节字符与字节数不同!)
wc file.txt # 同时显示行数、单词数、字节数
wc -l *.py | sort -n # 统计所有 .py 文件行数并排序
find . -name "*.py" | wc -l # 统计目录中 .py 文件总数
# ─────── cut:按列提取 ───────────────────────
cut -d: -f1 /etc/passwd # 以 : 分隔,取第 1 列(用户名)
cut -d: -f1,6 /etc/passwd # 取第 1 和第 6 列(用户名和主目录)
cut -d, -f2-4 data.csv # 取第 2 到第 4 列(范围)
cut -c1-10 file.txt # 取每行的前 10 个字符(按字符位置)
cut -c1-80 wide_file.txt # 截断长行到 80 字符(查看宽文件)
# ─────── tr:字符替换/删除 ───────────────────
# tr 不接受文件参数,只能从 stdin 读取,且只操作单个字符(不支持字符串)
echo "Hello World" | tr 'a-z' 'A-Z' # 转换为大写
echo "Hello World" | tr 'A-Z' 'a-z' # 转换为小写
echo "hello world" | tr -s ' ' # -s:压缩连续重复字符(多空格→单空格)
echo "hello123" | tr -d '0-9' # -d:删除指定字符集中的所有字符
cat file.txt | tr '\n' ',' # 将换行符替换为逗号(多行→单行)
echo "hello\nworld" | tr -d '\n' # 删除所有换行符
# ─────── diff:文件差异比较 ──────────────────
diff file1.txt file2.txt # 基础差异显示(< 是 file1,> 是 file2)
diff -u file1.txt file2.txt # -u:统一格式(和 git diff 相同格式)
diff -r dir1/ dir2/ # -r:递归比较目录
diff --color file1.txt file2.txt # 彩色显示差异
diff -w file1.txt file2.txt # -w:忽略空白字符差异
diff -i file1.txt file2.txt # -i:忽略大小写差异
diff <(sort a.txt) <(sort b.txt) # 进程替换:比较两个命令的输出
sort 默认按字典序排序,这意味着 "100" < "20" < "9"(因为比较第一个字符 1 < 2 < 9)。凡是涉及数字比较(文件大小、行数、时间戳等数字)都必须加 -n。类似的,uniq 只去除相邻重复行——如果文件未排序,重复行分散在不同位置,uniq 不会合并它们。正确用法始终是 sort | uniq 而非单独使用 uniq。
管道组合:Linux 的超级武器
Unix 哲学的精髓:每个工具做好一件事,通过管道将它们组合解决复杂问题。管道让数据在工具之间流动,而不需要创建临时文件。
# ─────── 日志分析 ────────────────────────────
# 从 access.log 中提取 404 错误的 URL 并排行
grep ' 404 ' /var/log/nginx/access.log \ # 筛选 404 响应行
| awk '{print $7}' \ # 提取 URL(第 7 列)
| sort \ # 排序(uniq 需要)
| uniq -c \ # 统计重复次数
| sort -rn \ # 按次数降序排列
| head -10 # 只显示前 10 个
# 统计过去 1 小时的错误日志数量(假设日志含时间戳)
grep "$(date -d '1 hour ago' +'%Y-%m-%d %H')" /var/log/app.log | grep -c "ERROR"
# 查找日志中响应时间超过 500ms 的请求(第 10 列为响应时间)
awk '$10 > 500' /var/log/nginx/access.log | wc -l
# ─────── 文件与目录分析 ──────────────────────
# 找出占用最多磁盘空间的 10 个目录
du -sh */ | sort -rh | head -10
# 统计每种文件类型的数量
find . -type f | sed 's/.*\.//' | sort | uniq -c | sort -rn
# 统计代码行数(不含空行和注释)
find . -name "*.py" -type f \
| xargs grep -v "^\s*#\|^\s*$" \ # 排除注释行和空行
| wc -l
# ─────── 代码库分析 ──────────────────────────
# 查找所有 Python 文件中的 TODO/FIXME 并显示文件名和行号
grep -rn "# TODO\|# FIXME" . --include="*.py"
# 批量替换所有 Python 文件中的旧导入路径(先预览再执行)
grep -rl "from oldpackage import" . --include="*.py" | head -5 # 先预览
grep -rl "from oldpackage import" . --include="*.py" \
| xargs sed -i 's/from oldpackage import/from newpackage import/g'
# 找出最大的 Python 文件(代码行数最多)
find . -name "*.py" | xargs wc -l | sort -rn | head -5
# ─────── 系统管理 ────────────────────────────
# 查看哪些进程占用了最多内存(前 5)
ps aux | sort -k4 -rn | head -5 | awk '{printf "%-20s %5.1f%%\n", $11, $4}'
# 统计每个用户的进程数量
ps aux | awk 'NR>1 {print $1}' | sort | uniq -c | sort -rn
# 查看当前系统最消耗 CPU 的 10 个进程
ps aux --sort=-%cpu | head -11 | awk 'NR>1 {printf "%-30s %s%%\n", $11, $3}'
# 列出所有已打开端口(不含注释和空行)
ss -tlnp | grep LISTEN | awk '{print $4}' | cut -d: -f2 | sort -n
# ─────── 文本转换技巧 ───────────────────────
# 将 CSV 的某列数据去重排序后输出(取第 2 列的唯一值)
cut -d, -f2 data.csv | sort -u
# 合并两个有序文件(类似 SQL JOIN)
join -t: -1 1 -2 1 <(sort /etc/passwd) <(sort /etc/shadow) | cut -d: -f1,2
# 统计文本文件中各个单词的出现频率(词频统计)
cat text.txt | tr '[:upper:]' '[:lower:]' \ # 全部小写
| tr -cs 'a-z' '\n' \ # 非字母字符替换为换行(分词)
| sort \ # 排序
| uniq -c \ # 计数
| sort -rn \ # 按频率降序
| head -20 # TOP20 词频
xargs:命令行参数构造
xargs 将 stdin 的内容转换为命令的参数。管道只能传递数据到 stdin,但很多命令(如 cp、mv、chmod)不接受 stdin,需要命令行参数——这时就需要 xargs 做"桥梁"。
# 基础用法:将 stdin 作为参数追加到命令后
echo "file1 file2 file3" | xargs rm # 等同于 rm file1 file2 file3
find . -name "*.tmp" | xargs rm # 删除所有 .tmp 文件
find . -name "*.py" | xargs wc -l # 统计所有 .py 文件行数
# -I {} 替换字符串(处理每个参数时执行独立命令)
find . -name "*.log" | xargs -I {} cp {} /backup/ # 逐一复制每个日志文件
cat urls.txt | xargs -I {} curl -s {} -o /dev/null # 逐一测试 URL 是否可达
# -0 和 find -print0:处理文件名中含空格的情况
# 默认 xargs 按空白字符分割,文件名含空格会被错误分割
find . -name "*.txt" -print0 | xargs -0 grep "pattern" # 用 NULL 作为分隔符
find . -name "* *" -print0 | xargs -0 rm # 安全删除含空格的文件名
# -n N:每次传递最多 N 个参数(控制批次大小)
find . -name "*.py" | xargs -n 10 grep "TODO" # 每次处理 10 个文件
# -P N:并行执行 N 个进程(利用多核加速)
find . -name "*.jpg" | xargs -P 4 -I {} convert {} -quality 85 {}.opt.jpg
# 用 ImageMagick 并行压缩图片(4个并发进程)
系统对命令行参数总长度有限制(通常 2MB,getconf ARG_MAX 查看),find 结果过多时直接 find | xargs rm 可能报"Argument list too long"。xargs 会自动将参数分批传递,不会超过限制,这是它优于 for f in $(find ...) 的原因之一。对于含空格、换行、特殊字符的文件名,始终使用 find -print0 | xargs -0 的组合。
Linux 文本处理的核心要点:① 理解标准流(stdin/stdout/stderr)和管道的工作原理——管道是并行的,不是顺序的;② grep -E(ERE)比 BRE 更直观,日常优先使用;③ sed -i.bak 修改文件前保留备份,永远是好习惯;④ awk 擅长结构化文本处理,-F 指定分隔符,BEGIN/END 做初始化和汇总;⑤ sort | uniq -c | sort -rn 是频率统计的万能公式;⑥ sort 处理数字必须加 -n;⑦ find -print0 | xargs -0 是处理含空格文件名的标准做法;⑧ 管道组合的威力在于用简单工具解决复杂问题。