文本搜索与处理指南:grep / ripgrep / sed / awk / 管道
适用场景:搜索代码内容、批量替换文本、日志过滤、数据提取、管道组合。
适用版本:ripgrep 14+、GNU grep/sed/awk;PowerShell 示例以 7+ 为主。
最后核对:2026-07-30
30 秒快速路径
rg -n "TODO|FIXME" .
rg -l "oldImport" src/
rg --hidden --glob '!.git/**' "API_KEY" .
目录
- 核心概念
- grep — 基础文本搜索
- ripgrep (rg) — 现代替代品
- sed — 流式文本替换
- awk — 列提取与格式化
- 管道组合
- 正则表达式速查
- PowerShell 等价操作
- 实战场景
核心概念
管道思维
Unix 哲学:每个工具做一件小事,通过管道(|)串联成强大组合。
输入 → 过滤 → 变换 → 排序 → 去重 → 输出
# 例:统计项目中哪个 npm 包被 import 最多
grep -rh "from '" src/ | sed "s/.*from '//;s/'.*//" | sort | uniq -c | sort -rn | head -10
标准流
| 流 | 编号 | 用途 |
|---|---|---|
| stdin | 0 | 输入(管道传入或文件) |
| stdout | 1 | 正常输出 |
| stderr | 2 | 错误输出 |
command > output.txt # stdout 写入文件(覆盖)
command >> output.txt # stdout 追加
command 2> error.log # stderr 写入文件
command > all.log 2>&1 # stdout + stderr 合并写入
command > /dev/null 2>&1 # 丢弃所有输出
grep — 基础文本搜索
# 基础搜索
grep "useState" src/App.tsx # 搜索文件中包含 useState 的行
grep -r "TODO" ./src # 递归搜索目录
grep -rn "console.log" ./src # -n 显示行号
grep -ri "error" ./logs # -i 忽略大小写
grep -rl "axios" ./src # -l 只列出文件名(不显示内容)
# 反向搜索
grep -v "node_modules" filelist.txt # -v 排除匹配行
# 正则搜索
grep -E "import.*from" src/App.tsx # -E 启用正则
grep -P "\d{3,}" log.txt # -P 是 GNU grep 扩展,macOS/BSD grep 通常不支持
# 上下文
grep -A 3 "error" log.txt # -A 显示匹配行后 3 行
grep -B 2 "error" log.txt # -B 显示匹配行前 2 行
grep -C 2 "error" log.txt # -C 前后各 2 行
# 统计
grep -c "error" log.txt # 匹配行数
grep -rc "TODO" ./src # 每个文件的匹配数
ripgrep (rg) — 现代替代品
通常比递归 grep 更快,默认递归,并遵守 .gitignore;实际性能取决于文件数量、存储和搜索模式。默认还会跳过隐藏文件、二进制文件,并且不跟随符号链接,因此“搜不到”时先检查过滤规则。
# 安装
# Windows: scoop install ripgrep / winget install BurntSushi.ripgrep
# macOS: brew install ripgrep
# Linux: apt install ripgrep
# 基础用法(自动递归当前目录)
rg "useState" # 搜索当前目录
rg "useState" src/ # 指定目录
rg -i "error" # 忽略大小写
rg -l "axios" # 只列文件名
rg -c "TODO" # 每文件匹配数
# 过滤文件类型
rg -t ts "interface" # 只搜 .ts 文件
rg -t css "color" # 只搜 .css 文件
rg --type-list # 查看所有支持的类型
rg -g "*.tsx" "Props" # glob 过滤
# 调整默认过滤
rg --hidden "API_KEY" # 包含隐藏文件,仍遵守 ignore
rg --no-ignore "generated" # 不遵守 ignore 文件
rg -L "pattern" # 跟随符号链接;先确认不会形成大范围扫描
# 排除
rg --glob "!*.test.*" "function" # 排除测试文件
rg --glob "!dist/**" "import" # 排除 dist 目录
# 正则
rg "console\.(log|warn|error)" # 默认就是正则
rg -F '$$' # 单引号避免 Bash 把 $$ 展开为当前 PID
# 替换预览(不修改文件)
rg "oldFunction" -r "newFunction" # 显示替换后的样子
# JSON 输出(方便脚本处理)
rg --json "TODO"
sed — 流式文本替换
sed = Stream Editor,逐行处理文本。最常用于查找替换。
# 基础替换(输出到 stdout,不改原文件)
sed 's/old/new/' file.txt # 每行替换第一个匹配
sed 's/old/new/g' file.txt # g = 替换所有匹配(全局)
sed 's/old/new/gi' file.txt # gi = 全局 + 忽略大小写
# 修改原文件前先保留备份并检查 diff
sed -i.bak 's/old/new/g' file.txt # GNU/BSD sed 都会留下 file.txt.bak
diff -u file.txt.bak file.txt
# 删除行
sed '/pattern/d' file.txt # 删除匹配行
sed '/^$/d' file.txt # 删除空行
sed '1d' file.txt # 删除第一行
sed '1,5d' file.txt # 删除 1-5 行
# 打印特定行
sed -n '10,20p' file.txt # 只打印 10-20 行
sed -n '/START/,/END/p' file.txt # 打印两个标记之间的内容
# 插入/追加
sed '3i\new line here' file.txt # 在第 3 行前插入
sed '3a\new line here' file.txt # 在第 3 行后追加
# 分隔符可以换(路径里有 / 时有用)
sed 's|/usr/local|/opt|g' file.txt # 用 | 做分隔符
# 多条规则
sed -e 's/foo/bar/g' -e 's/baz/qux/g' file.txt
awk — 列提取与格式化
awk 按行处理,擅长处理结构化文本(按空格/tab 分列)。
# 基础列提取($1=第一列,$2=第二列,$0=整行)
awk '{print $1}' file.txt # 打印第一列
awk '{print $1, $3}' file.txt # 打印第一和第三列
awk '{print $NF}' file.txt # 最后一列(NF=列数)
# 指定分隔符
awk -F: '{print $1}' /etc/passwd # 以 : 分隔
awk -F, '{print $2}' data.csv # 仅适合没有引号和嵌入逗号的简单数据
# 条件过滤
awk '$3 > 100' data.txt # 第三列大于 100 的行
awk '/error/' log.txt # 包含 error 的行(像 grep)
awk '$1 == "GET" {print $2}' access.log # 第一列是 GET 的,打印第二列
# 计算
awk '{sum += $1} END {print sum}' nums.txt # 求和
awk '{sum += $1; n++} END {print sum/n}' nums.txt # 平均值
# 格式化输出
awk '{printf "%-20s %s\n", $1, $2}' file.txt # 左对齐 20 字符宽
# 内置变量
# NR = 当前行号
# NF = 当前行的列数
# FS = 字段分隔符
awk 'NR > 1' data.csv # 跳过第一行(表头)
awk 'NR==5,NR==10' file.txt # 打印 5-10 行
管道组合
常用组合工具
| 工具 | 功能 |
|---|---|
sort |
排序 |
uniq |
去重(需先排序) |
wc |
统计行数/字数/字符数 |
head / tail |
取前/后 N 行 |
cut |
按分隔符取列 |
tr |
字符替换/删除 |
tee |
同时输出到 stdout 和文件 |
xargs |
将 stdin 转为命令参数 |
组合示例
# 统计 src/ 下 .ts 文件数量
find src/ -name "*.ts" | wc -l
# 查找最大的 10 个文件(GNU find;路径中的空格不会被拆参数)
find . -type f -printf '%s\t%p\n' | sort -k1,1rn | head -10
# 去重并统计出现次数
awk '{print $1}' access.log | sort | uniq -c | sort -rn | head -10
# → 统计访问最多的 IP
# 把搜索结果安全地传给另一个命令(GNU/Git Bash)
rg -l -0 "oldImport" src/ | xargs -0 -r sed -i.bak 's/oldImport/newImport/g'
# → 支持带空格的文件名,并保留 .bak;确认 diff 后再删除备份
# 同时看输出和保存到文件
npm run build 2>&1 | tee build.log
# CSV 提取某列
cut -d',' -f2 data.csv | sort -u
# → 提取第二列并去重
# 字符替换
echo "hello-world" | tr '-' '_' # hello_world
echo "HELLO" | tr 'A-Z' 'a-z' # hello
cat file.txt | tr -d '\r' # 删除 Windows 换行符 \r
正则表达式速查
基础
不同工具使用的正则引擎不同。下表中的 POSIX ERE 写法适用于 grep -E、sed -E 和大多数 awk;PCRE 专有语法应只在 grep -P、ripgrep 的 PCRE2 模式等明确支持它的工具中使用。
| 语法 | 含义 | 示例 |
|---|---|---|
. |
任意单字符 | a.c → abc, aXc |
* |
前面的 0 次或多次 | ab*c → ac, abc, abbc |
+ |
前面的 1 次或多次 | ab+c → abc, abbc |
? |
前面的 0 或 1 次 | colou?r → color, colour |
^ |
行首 | ^import |
$ |
行尾 | \.tsx$ |
[[:digit:]] |
数字 | [[:digit:]]{3} → 123 |
[[:alnum:]_] |
字母数字下划线 | [[:alnum:]_]+ |
[[:space:]] |
空白字符 | [[:space:]]+ |
| `(^ | [^[:alnum:]_])` | 可移植写法中用于模拟左侧单词边界 |
分组与引用
(pattern) # 捕获组
(?:pattern) # 非捕获组,仅 PCRE 等部分引擎支持
\1 # 引用第一个捕获组
# sed 替换中使用捕获组
sed 's/\(.*\):\(.*\)/\2:\1/' file.txt # 交换冒号前后
sed -E 's/(.*):(.*)/\2:\1/' file.txt # -E 用现代语法
常用正则
| 用途 | 正则 |
|---|---|
| 邮箱(粗筛) | [[:alnum:]._%+-]+@[[:alnum:].-]+\.[[:alpha:]]+ |
| IPv4(仅格式粗筛,不校验 0–255) | [0-9]{1,3}(\.[0-9]{1,3}){3} |
| URL(粗筛) | https?://[^[:space:]]+ |
| 汉字 | 不同引擎和 Unicode 版本差异较大;优先使用支持 Unicode Script 属性的工具 |
| 空行 | ^$ |
| import 语句 | ^import.*from\s+['"](.+)['"] |
PowerShell 等价操作
PowerShell 不用 grep/sed/awk,用对象管道 + cmdlet:
# grep → Select-String
Get-ChildItem -Path src -Recurse -File -Filter "*.ts" |
Select-String -Pattern "useState"
Select-String -Path "log.txt" -Pattern "error" -CaseSensitive
# sed 替换 → -replace 操作符
Copy-Item -LiteralPath file.txt file.txt.bak
(Get-Content -LiteralPath file.txt) -replace 'old', 'new' |
Set-Content -LiteralPath file.txt -Encoding utf8
# 批量替换
Get-ChildItem -Recurse -Filter "*.ts" | ForEach-Object {
(Get-Content -LiteralPath $_.FullName) -replace 'oldImport', 'newImport' |
Set-Content -LiteralPath $_.FullName -Encoding utf8
}
# awk 列提取 → Split + 索引
Get-Content log.txt | ForEach-Object { ($_ -split '\s+')[0] }
# sort | uniq -c → Group-Object
Get-Content log.txt | Group-Object | Sort-Object Count -Descending | Select -First 10
# wc -l → Measure-Object
(Get-Content file.txt | Measure-Object -Line).Lines
# head / tail
Get-Content file.txt -Head 20
Get-Content file.txt -Tail 10
# 管道到文件
command | Out-File output.txt
command | Tee-Object -FilePath output.txt # 同时输出到屏幕和文件
批量改写前应先备份或确认版本控制状态,再检查 diff。PowerShell 7 的 utf8 是无 BOM UTF-8;Windows PowerShell 5.1 的同名参数会写 BOM,且文本改写可能统一换行符。需要保持原始编码或换行格式时,不要直接套用批量模板。
实战场景
1. 找出项目中所有 TODO/FIXME
rg "TODO|FIXME|HACK|XXX" --glob "!node_modules/**"
2. 统计代码行数
find src/ -type f \( -name "*.ts" -o -name "*.tsx" \) -print0 |
xargs -0 -r wc -l | tail -1
# 或者用 cloc(专用工具)
3. 批量重命名文件中的 import 路径
# 预览
rg "from '@/old-path" src/ -l
# 执行替换
rg -l -0 "from '@/old-path" src/ |
xargs -0 -r sed -i.bak "s|from '@/old-path|from '@/new-path|g"
4. 从 JSON 日志中提取错误
# 每行一个 JSON 对象(JSON Lines)时使用 jq,避免用正则解析 JSON
jq -r 'select(.level=="error") | .message' app.log
5. 查找尚未清理的 Git 冲突标记
grep -rn "<<<<<<" src/ # 找出还有冲突标记的文件
6. 查看 package.json 中所有依赖版本
jq -r '
(.dependencies // {}) + (.devDependencies // {}) |
to_entries[] | "\(.key)\t\(.value)"
' package.json | column -t