强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

AWK & SED 生产力教程 / 第 2 章:SED 基础

第 2 章:SED 基础

SED 的本质很简单:读一行,执行命令,输出结果,重复。

2.1 SED 工作原理

工作流程

┌───────────────┐
│  输入 (stdin) │
└───────┬───────┘
        ▼
┌───────────────┐
│  读入模式空间  │  ← 每次读一行
│ (Pattern Space)│
└───────┬───────┘
        ▼
┌───────────────┐
│  执行 SED 命令 │  ← 对模式空间中的内容执行操作
└───────┬───────┘
        ▼
┌───────────────┐
│  输出模式空间  │  ← 将结果输出到 stdout
└───────┬───────┘
        ▼
  (读入下一行,重复)

📌 关键概念

  • 模式空间(Pattern Space):SED 的工作缓冲区,每次存放一行
  • 保持空间(Hold Space):辅助缓冲区,用于跨行操作(第 3 章详解)
  • 地址(Address):指定命令作用于哪些行

基本语法

sed [选项] '命令' 文件...
sed [选项] -e '命令1' -e '命令2' 文件...
sed [选项] -f 脚本文件 文件...

常用选项

选项说明示例
-n静默模式,不自动输出sed -n '5p' file
-e指定多个命令sed -e 's/a/b/' -e 's/c/d/' file
-i原地编辑文件sed -i 's/old/new/g' file
-i.bak原地编辑并备份sed -i.bak 's/old/new/g' file
-E使用扩展正则表达式`sed -E ’s/(foo
-r-E(GNU 特有)`sed -r ’s/(foo
--follow-symlinks跟踪符号链接sed -i --follow-symlinks 's/old/new/' link

⚠️ macOS 用户-i 必须跟一个备份后缀(可以是空字符串):

# Linux (GNU sed)
sed -i 's/old/new/g' file
# macOS (BSD sed)
sed -i '' 's/old/new/g' file
# 或使用 brew install gnu-sed

2.2 替换命令(substitute)

替换是 SED 最常用的功能,语法:

s/模式/替换字符串/标志

基本替换

# 创建测试文件
cat > config.txt << 'EOF'
server_name = localhost
server_port = 8080
db_host = 127.0.0.1
db_port = 3306
log_level = DEBUG
EOF

# 将第一处 localhost 替换为 example.com
$ sed 's/localhost/example.com/' config.txt
→ server_name = example.com
→ server_port = 8080
→ db_host = 127.0.0.1
→ db_port = 3306
→ log_level = DEBUG

替换标志

标志说明示例
g替换所有匹配(全局)sed 's/old/new/g' file
数字替换第 N 处匹配sed 's/old/new/2' file
p替换成功时打印sed -n 's/old/new/p' file
w 文件替换成功时写入文件sed 's/old/new/w out.txt' file
i忽略大小写sed 's/old/new/gi' file
I忽略大小写(GNU 特有)sed 's/old/new/gI' file
e将结果作为命令执行sed 's/^/ls /e' file
# g — 全局替换
$ echo "aaa bbb aaa" | sed 's/aaa/xxx/g'
→ xxx bbb xxx

# 数字 — 替换第 N 处
$ echo "aaa bbb aaa" | sed 's/aaa/xxx/2'
→ aaa bbb xxx

# p — 打印被修改的行
$ sed -n 's/localhost/example.com/p' config.txt
→ server_name = example.com

# i/I — 忽略大小写
$ echo "Hello HELLO hello" | sed 's/hello/hi/gI'
→ hi hi hi

分隔符

SED 默认使用 / 作为分隔符,但可以用任何字符替代:

# 处理路径时,用 # 避免转义斜杠
$ sed 's#/usr/local/bin#/opt/bin#g' paths.txt

# 处理 URL 时,用 | 作分隔符
$ sed 's|http://|https://|g' urls.txt

# 用逗号作分隔符
$ echo "one,two,three" | sed 's/,/|/g'
→ one|two|three

💡 选择分隔符的原则:选择在模式和替换字符串中不会出现的字符。

反向引用

在替换字符串中引用正则分组:

# \1, \2 ... 引用捕获组
$ echo "John Smith" | sed -E 's/([A-Za-z]+) ([A-Za-z]+)/\2, \1/'
→ Smith, John

# 交换两个单词
$ echo "foo bar" | sed -E 's/(\w+) (\w+)/\2 \1/'
→ bar foo

# 添加引号
$ echo "name=value" | sed -E 's/=(.+)/="\1"/'
→ name="value"

# \0 或 & 代表整个匹配
$ echo "error: something" | sed 's/.*/[&]/'
[error: something]

📌 记住\0& 都代表整个匹配串,不是第 0 个分组。

2.3 删除命令(delete)

# 删除匹配的行
sed '/模式/d' 文件

# 删除第 N 行
sed 'Nd' 文件

# 删除范围
sed 'M,Nd' 文件

示例

cat > sample.txt << 'EOF'
# 配置文件
# 作者: admin

server_port = 8080
# 已弃用
db_host = localhost
max_connections = 100

EOF

# 删除注释行
$ sed '/^#/d' sample.txt
→ server_port = 8080
→ db_host = localhost
→ max_connections = 100

# 删除空行
$ sed '/^$/d' sample.txt
# 配置文件
# 作者: admin
→ server_port = 8080
# 已弃用
→ db_host = localhost
→ max_connections = 100

# 删除注释和空行
$ sed '/^#/d; /^$/d' sample.txt
→ server_port = 8080
→ db_host = localhost
→ max_connections = 100

# 删除第 3 行到第 5 行
$ sed '3,5d' sample.txt

# 删除最后一行
$ sed '$d' sample.txt

⚠️ 注意d 命令会删除整行,不会输出被删除的行。如果要查看被删除的行,使用 w 写入另一个文件。

2.4 插入和追加命令

命令说明位置
i\文本在匹配行插入insert
a\文本在匹配行追加append
c\文本替换匹配行change
cat > hosts.txt << 'EOF'
127.0.0.1  localhost
192.168.1.1  gateway
EOF

# 在第 1 行前插入
$ sed '1i\# Auto-generated by setup.sh' hosts.txt
# Auto-generated by setup.sh
→ 127.0.0.1  localhost
→ 192.168.1.1  gateway

# 在 localhost 行后追加
$ sed '/localhost/a\::1  localhost' hosts.txt
→ 127.0.0.1  localhost
→ ::1  localhost
→ 192.168.1.1  gateway

# 替换匹配行
$ sed '/gateway/c\192.168.1.254  router' hosts.txt
→ 127.0.0.1  localhost
→ 192.168.1.254  router

# 插入多行(使用 \n)
$ sed '1i\# Header Line 1\n# Header Line 2' hosts.txt
# Header Line 1
# Header Line 2
→ 127.0.0.1  localhost
→ 192.168.1.1  gateway

💡 GNU sed 可以省略 \ 后的换行,写成:sed '1i # Line 1' file

2.5 打印命令

# 打印特定行
$ sed -n '5p' file          # 第 5 行
$ sed -n '3,7p' file        # 第 3-7 行
$ sed -n '$p' file          # 最后一行

# 打印匹配行(类似 grep)
$ sed -n '/error/p' logfile

# 打印行号
$ sed -n '/error/=' logfile

# 同时打印行号和内容
$ sed -n '/error/{=;p}' logfile
# 实用示例:查看配置文件(去掉注释和空行)并显示行号
$ cat -n /etc/ssh/sshd_config | sed '/^$/d' | sed '/^[[:space:]]*#/d'

2.6 写入命令

# 将匹配行写入另一个文件
$ sed -n '/error/w errors.txt' logfile

# 将不同模式的行写入不同文件
$ sed -n '/error/w errors.txt; /warn/w warnings.txt' logfile

# 将范围内的行写入文件
$ sed -n '10,20w subset.txt' logfile

2.7 原地编辑

基本用法

# 直接修改文件(不推荐,无法恢复)
sed -i 's/old/new/g' file

# 修改并备份(推荐)
sed -i.bak 's/old/new/g' file
# file     — 修改后的文件
# file.bak — 原始文件

# 不创建备份(确信不需要时)
sed -i '' 's/old/new/g' file   # macOS

批量修改多个文件

# 修改当前目录所有 .conf 文件
find . -name "*.conf" -exec sed -i.bak 's/old_domain/new_domain/g' {} +

# 或使用 xargs
find . -name "*.conf" | xargs sed -i.bak 's/old_domain/new_domain/g'

# 使用通配符(如果文件不多)
sed -i.bak 's/old/new/g' *.conf

⚠️ 重要提醒

  1. 原地编辑前先不加 -i 测试输出是否正确
  2. 首次操作务必带备份后缀
  3. 修改大量文件前先在少量文件上测试
# ❌ 危险操作(直接修改所有文件)
find . -name "*.py" -exec sed -i 's/old/new/g' {} +

# ✅ 安全操作(先测试,再执行)
# 第一步:预览
find . -name "*.py" | head -5 | xargs sed 's/old/new/g'
# 第二步:确认后执行
find . -name "*.py" -exec sed -i.bak 's/old/new/g' {} +
# 第三步:验证
grep -r "old" *.py
# 第四步:删除备份
find . -name "*.bak" -delete

2.8 多命令执行

方法一:分号分隔

$ sed 's/foo/bar/g; s/baz/qux/g' file

方法二:多个 -e 选项

$ sed -e 's/foo/bar/g' -e 's/baz/qux/g' file

方法三:大括号组合

# 对匹配行执行多个命令
$ sed '/error/{s/error/ERROR/; s/$/ [RETRY]/}' logfile

方法四:脚本文件

# 创建 sed 脚本
cat > transform.sed << 'EOF'
# 删除空行
/^$/d
# 删除注释
/^#/d
# 替换配置值
s/debug_level = [0-9]/debug_level = 1/
# 添加时间戳
s/^/[2024-01-01] /
EOF

# 使用脚本文件
$ sed -f transform.sed config.txt

2.9 实战案例

🏢 场景一:批量修改 Nginx 配置

# 查看修改前的配置
$ grep -n "server_name\|listen\|ssl" /etc/nginx/site.conf

# 修改域名
$ sed -i.bak 's/old-domain\.com/new-domain.com/g' /etc/nginx/site.conf

# 添加 SSL 配置(在 listen 80 后面添加)
$ sed -i '/listen 80;/a\    listen 443 ssl;' /etc/nginx/site.conf

# 注释掉某行
$ sed -i 's/^    access_log/#    access_log/' /etc/nginx/site.conf

🏢 场景二:清理 CSV 文件

cat > data.csv << 'EOF'
Name,Age,Email
"Alice",30,"[email protected]"
"Bob", 25,"[email protected]"
"Carol", 35,"[email protected]"
EOF

# 去掉字段中的引号
$ sed 's/"//g' data.csv
→ Name,Age,Email
→ Alice,30,[email protected]
→ Bob, 25,[email protected]
→ Carol, 35,[email protected]

# 去掉多余空格
$ sed 's/, /,/g; s/ ,/,/g' data.csv

🏢 场景三:修改 Docker Compose 文件

# 将 image 版本从 1.0 升级到 2.0
$ sed -i 's/image: myapp:1.0/image: myapp:2.0/' docker-compose.yml

# 注释掉某个 service(简化方法,实际建议用 yq)
$ sed -i '/service-name/{N;s/^/#/}' docker-compose.yml

🏢 场景四:日志文件预处理

# 从日志中提取时间戳和消息
# 原始格式: 2024-01-15 10:23:45 [ERROR] Something went wrong
$ sed -n 's/^\([0-9-]* [0-9:]*\) \[\([A-Z]*\)\] \(.*\)/\1 | \3/p' app.log

# 将多行错误合并为单行(高级用法,参见第 3 章)

2.10 常见陷阱

陷阱说明解决方案
忘记 -i没有输出,以为命令没生效原地编辑不会输出到终端
分隔符冲突/ 在路径中出现换用 #、`
贪婪匹配.* 匹配过多.*?(ERE)或更精确的模式
macOS -i忘记空字符串参数sed -i '' '...'
行尾换行符每行末尾有 \n注意 $ 匹配的是行尾,不包含 \n
大文件内存SED 不会一次性加载所有行这是优势,不是问题

2.11 命令速查表

# 替换
s/old/new/          # 替换第一处
s/old/new/g         # 全局替换
s/old/new/gi        # 全局替换,忽略大小写

# 删除
d                   # 删除行
3d                  # 删除第 3 行
3,7d                # 删除 3-7 行
/pattern/d          # 删除匹配行

# 打印
p                   # 打印行
3p                  # 打印第 3 行
/pattern/p          # 打印匹配行(需 -n)
=                   # 打印行号

# 插入/追加/替换
i\text              # 在行前插入
a\text              # 在行后追加
c\text              # 替换整行

# 写入
w file              # 将模式空间写入文件

扩展阅读


下一章:第 3 章:SED 进阶 — 掌握地址范围、保持空间和高级流控制。