Sed 学习笔记

这是一份稍微整理了一下的 sed 学习笔记。

我是把它当作“流式编辑器”来理解的,核心在于它不打开文件,而是把数据流读进来,处理完,扔出去。刚开始学的时候觉得语法反人类,后来发现这东西处理配置文件、批量改代码、分析日志简直是神器。


0. 核心心法

sed 的工作流程其实就三步:

  1. 入一行到模式空间(Pattern Space)。
  2. 执行你给它的命令。
  3. 打印结果(除非你让它别打),然后清空空间,读下一行。

通用公式:

sed [选项] '地址+命令' 文件名

1. 基础:替换 (s)

这是最常用的功能,没有之一。

基本语法: s/旧的/新的/标记

# 最简单的替换,只替每行第一个
echo "hello world, hello sed" | sed 's/hello/hi/'
# 输出: hi world, hello sed

# 全局替换 (加个 g) -> Global
echo "hello world, hello sed" | sed 's/hello/hi/g'
# 输出: hi world, hi sed

很多人不知道如果不加 g,它只动第一处。另外,那个分隔符 / 其实是可以换的!

当你在处理 URL 或者文件路径时,满屏的转义符 \/usr\/local\/bin 看着眼晕。

换成 # 或者 @ 瞬间清爽:

# 难看版
sed 's/\/usr\/local\/bin/\/usr\/bin/g' path.txt

# 优雅版 (推荐)
sed 's#^/usr/local/bin#/usr/bin#g' path.txt

2. 定位

如果不指定地址,sed 会处理每一行。但通常我们只想处理特定部分。

按行号

# 只删除第 2 行
sed '2d' file.txt

# 删除第 2 到第 5 行
sed '2,5d' file.txt

# 删除从第 3 行到最后一行 ($ 代表末尾)
sed '3,$d' file.txt

按正则匹配 (Regex)

这才是 sed 的灵魂。

# 只有包含 "error" 的行,才把 "warn" 替换成 "critical"
sed '/error/s/warn/critical/g' log.txt

# 删除所有空行 (匹配开头即结尾)
sed '/^$/d' file.txt

# 删除所有注释行 (以 # 开头的)
sed '/^#/d' config.ini

3. 那些常用但容易忘的命令

除了 s (替换),这几个必须背下来:

-np (打印)

默认情况下 sed 会把所有行都打印出来。

如果只想看处理过的行,或者匹配到的行,必须配合 -n (silent) 和 p (print)。

# 只打印第 5 行 (没 -n 会打印全部,且第 5 行打两次,巨坑)
sed -n '5p' file.txt

# 像 grep 一样用:只打印包含 "failed" 的行
sed -n '/failed/p' access.log

d (删除)

上面用过很多次了,不再赘述。

场景:把配置文件里带 # 的注释全删了,生成一个纯净版配置。

sed '/^#/d' nginx.conf

i (插入) 和 a (追加)

  • i (insert): 在当前行之前插。
  • a (append): 在当前行之后插。
# 在含有 "Listen" 的行后面,加一行 "server_name localhost;"
sed '/Listen/a server_name localhost;' nginx.conf

4. 高级操作:分组与反向引用

这个学会了才算真正入门 sed。当你需要保留一部分内容,修改另一部分内容时使用。

核心是 ()\1, \2

场景:把 name: value 格式改成 value = name

echo "lang: python" | sed -E 's/(.*): (.*)/\2 = \1/'
# 输出: python = lang
  • () 把内容包起来。
  • \1 代表第一个括号匹配到的内容,\2 代表第二个。
  • 注意:加 -E 是为了支持扩展正则,不然括号得写成 \( \),太丑了。

**还有一个神器 &**:

它代表“刚才匹配到的所有内容”。

# 给所有的数字加上方括号
echo "ID 12345" | sed 's/[0-9]\+/[&]/g'
# 输出: ID [12345]

5. 范围匹配 (Range)

这个功能用来截取日志非常棒。语法是 '/开始标记/,/结束标记/ 命令'

场景:提取日志中从 “Start” 到 “End” 之间的所有行。

sed -n '/Start/,/End/p' app.log

6. 关于 -i (直接修改文件)

最容易翻车的地方。

sed 默认只输出到屏幕,不改文件。想改文件要加 -i

但是macOS 和 Linux 的 sed 在这里不一样!

  • Linux (GNU sed): sed -i 's/a/b/' file.txt (直接改,不备份)
  • macOS (BSD sed): 强制要求跟一个备份后缀。如果你不想要备份,必须给一个空字符串。

跨平台兼容写法(或者为了安全起见):

# 修改 file.txt,同时生成一个 file.txt.bak 备份
sed -i.bak 's/foo/bar/' file.txt

在写 CI/CD 脚本或者 Dockerfile 时,最好先确认环境是 Linux 还是 Mac,或者干脆用临时文件 sed ... > temp && mv temp file,虽然土,但是稳。


7. 什么时候不要用 Sed?

虽然 sed 很强,但下面情况需要切换到 awk 或者 Python:

  1. 涉及到多行逻辑时:虽然 sedN, H, G (Hold Space) 这些命令可以处理多行,但写法非常像汇编语言,极难维护。几个月后你自己都看不懂。
  2. 需要进行数学运算时sed 算数很痛苦。
  3. CSV/JSON 处理:用 sed 处理 CSV 碰到带逗号的字段会死人;处理 JSON 请直接用 jq

8. 速查 Cheat Sheet

最后整理几个我平时 alias 或者写在脚本里的常用的:

# 1. 批量删除行尾空格 (强迫症福音)
sed -i 's/[ \t]*$//' file.txt

# 2. 给文件每一行加双引号 (处理 SQL 列表时很有用)
sed 's/^/"/; s/$/"/' list.txt

# 3. 打印文件的第 10 到 20 行
sed -n '10,20p' file.txt

# 4. 获取本机 IP (配合 ifconfig/ip addr,只取数字部分)
# 这是一个典型的链式处理
ifconfig eth0 | grep 'inet ' | sed 's/^.*inet //g' | sed 's/ *netmask.*$//g'

如果觉得 sed 的列处理能力(比如“修改每行的第3个单词”)有点弱,那下一站应该是 Awk