【Linux从入门到精通】第28篇:文本处理三剑客(中)——sed 流编辑器

一、引言:手改100个文件,还是用sed一秒搞定?

想象一个场景:公司服务器从192.168.1.0网段迁移到10.0.0.0网段,你需要修改所有配置文件中的IP地址。配置文件散落在多个目录,格式各不相同。

手动一个个打开修改?可能改到第10个就开始手抖了。

用sed:

bash

复制代码
sudo sed -i 's/192\.168\.1\./10.0.0./g' /etc/**/*.conf

这条命令的一行效果等同于手动修改几百个文件。

sed (Stream Editor)的设计哲学是:非交互式编辑 。它在内存中逐行读取文件,按规则处理,然后将结果输出到标准输出。默认情况下,原始文件不会被修改 ------这正是安全的保证:你可以反复测试命令,确认无误后再用-i选项真正修改文件。

二、sed基础:三个核心概念

2.1 sed的工作模型

text

复制代码
输入文件 → 逐行读入 → 模式空间 → 执行编辑命令 → 输出 → 下一行

每一行被读取到内存中的"模式空间"(pattern space),sed在这个空间里执行所有编辑命令,处理完成后输出结果,然后读取下一行。

2.2 基本语法

bash

复制代码
sed [选项] '命令' 文件名
sed [选项] -e '命令1' -e '命令2' 文件名   # 多个命令
sed [选项] -f 脚本文件 文件名              # 从文件读取命令

核心选项

选项 含义 重要程度
-n 不自动输出每一行(需要配合p命令显式输出) ★★★
-e 执行多个命令 ★★☆
-i 直接修改文件(危险但常用) ★★★
-i.bak 修改文件前创建备份(.bak后缀) ★★★
-r / -E 使用扩展正则(推荐) ★★☆

2.3 行寻址:定位你要编辑的行

sed的命令结构是:[地址]命令 。不指定地址则对所有行执行命令。

按行号寻址

bash

复制代码
sed '3p' file          # 打印第3行(其他行也会输出)
sed -n '3p' file       # 只打印第3行
sed '1,5p' file        # 打印第1-5行
sed '3,$p' file        # 打印第3行到最后一行($表示最后一行)

按模式寻址

bash

复制代码
sed '/error/p' log     # 打印包含error的行
sed '/start/,/end/p'   # 打印从包含start到包含end之间的所有行

混合寻址

bash

复制代码
sed '3,/ERROR/p' log   # 从第3行到第一个包含ERROR的行

用sed模拟head和tailsed -n '1,10p' file等于head -10sed -n '$p' file等于tail -1。虽然日常不会用sed替代head/tail,但这体现了sed寻址能力的灵活性。

三、s命令:查找替换

s(substitute)是sed最重要的命令,占据sed使用场景的80%以上。

3.1 基本语法

bash

复制代码
sed 's/旧内容/新内容/选项' 文件

bash

复制代码
# 将每行第一个old替换为new
sed 's/old/new/' file.txt

# 将每行所有old替换为new(g = global)
sed 's/old/new/g' file.txt

# 将每行第二个old替换为new
sed 's/old/new/2' file.txt

3.2 替换选项

选项 含义
g 替换行内所有匹配
i 忽略大小写
p 打印替换成功的行(通常和-n配合)
w 文件 将替换成功的行写入文件

bash

复制代码
# 将所有error(忽略大小写)替换为ERROR,并打印受影响的行
sed -n 's/error/ERROR/gip' app.log

# 将改动行保存到新文件
sed 's/foo/bar/gw changes.txt' original.txt

3.3 正则替换实战

bash

复制代码
# 删除行首空格
sed 's/^ *//' file.txt

# 删除行尾空格
sed 's/ *$//' file.txt

# 删除所有HTML标签(将<任意内容>替换为空)
sed 's/<[^>]*>//g' page.html

# 将多个连续空格压缩为一个
sed -E 's/ +/ /g' file.txt

# 给匹配行添加注释符号
sed '/^server_name/s/^/# /' nginx.conf

3.4 分组引用:保留部分内容

这是sed替换中非常强大的功能------用分组\( \)( )捕获部分内容,在替换中通过\1\2引用:

bash

复制代码
# 交换两列数据(如把 "name:zhangsan" 变成 "name: zhangsan")
echo "zhangsan:30" | sed -E 's/([a-z]+):([0-9]+)/\2:\1/'

# 提取IPv4地址前三段并替换
echo "192.168.1.100" | sed -E 's/([0-9]+)\.([0-9]+)\.([0-9]+)\.([0-9]+)/\1.\2.\3.0/'
# 输出:192.168.1.0

# 给电话号码分段(不是精确匹配,只演示分组用法)
echo "13812345678" | sed -E 's/([0-9]{3})([0-9]{4})([0-9]{4})/\1-\2-\3/'
# 输出:138-1234-5678(仅演示分组,非精确手机号匹配)

3.5 分隔符的选择

sed的s命令默认用/作为分隔符,但如果替换内容本身包含/,就需要转义。这时可以换用其他分隔符:

bash

复制代码
# 路径替换——用/当分隔符需要转义,很难看
sed 's/\/var\/www\/html/\/opt\/www/g' config

# 换用#作分隔符,清晰多了
sed 's#/var/www/html#/opt/www#g' config

# 也可以用|、@等作为分隔符
sed 's|/usr/local|/opt|g' config

规则s后面跟的第一个字符就是分隔符,可以是任意标点符号。

四、删除操作:d命令

d命令删除匹配行,不需要替换内容:

bash

复制代码
# 删除第3行
sed '3d' file.txt

# 删除空行
sed '/^$/d' file.txt

# 删除注释行(以#开头的行)
sed '/^#/d' file.txt

# 删除包含DEBUG的行
sed '/DEBUG/d' app.log

# 删除指定行范围
sed '1,10d' file.txt               # 删除1-10行
sed '/start/,/end/d' file.txt      # 删除start到end之间的所有行

实际场景:查看有效配置,去掉注释和空行:

bash

复制代码
sed '/^#/d; /^$/d' /etc/nginx/nginx.conf
# 或者合并为一个模式:
sed -E '/^#|^$/d' /etc/nginx/nginx.conf

五、插入与追加:i和a命令

命令 含义 语法
i 在匹配行前面插入 sed '3i\新行内容'
a 在匹配行后面追加 sed '3a\新行内容'

bash

复制代码
# 在第3行之前插入
sed '3i\# 这是新增的配置' nginx.conf

# 在第3行之后追加
sed '3a\# 这是追加的配置' nginx.conf

# 在匹配行之前插入
sed '/^server {/i\# Web服务器配置开始' nginx.conf

# 在匹配行之后追加
sed '/^server {/a\    # 新增配置项' nginx.conf

# 在文件末尾追加
sed '$a\# END OF CONFIG' nginx.conf

六、-i选项:真正修改文件

默认sed只输出到屏幕,不会改变文件。要用-i选项真正修改:

bash

复制代码
# 危险:直接修改文件,无法恢复
sed -i 's/old/new/g' file.txt

# 安全:修改前创建备份文件 file.txt.bak
sed -i.bak 's/old/new/g' file.txt

强烈建议 :养成使用sed -i.bak的习惯。多一个备份文件,多一条后路。

-i的本质

-i并不是sed把修改写回原文件,而是sed创建一个临时文件,把处理结果写入临时文件,然后用临时文件替换原文件。这意味着:

  • 文件的inode会改变(不能对硬链接文件使用-i并期望链接仍然有效)

  • 如果sed在中途被中断,原文件可能丢失(所以-i.bak更安全)

七、综合实战

7.1 批量修改Nginx配置

bash

复制代码
# 将所有server_name从旧域名改为新域名
sudo sed -i.bak 's/server_name old\.example\.com/server_name new.example.com/g' /etc/nginx/sites-available/*

# 将80端口改为8080(只修改listen行,避免改到其他行的数字80)
sudo sed -i 's/listen 80;/listen 8080;/g' /etc/nginx/sites-available/*

7.2 日志格式化

bash

复制代码
# 将Apache日志的时间格式从[dd/Mon/yyyy]改为[yyyy-MM-dd]
# sed -E 's/\[([0-9]{2})\/([A-Z][a-z]{2})\/([0-9]{4})\]/[\3-\2-\1]/' access.log

# 删除日志中的敏感信息(如密码参数)
sed -E 's/password=[^& ]*/password=***/g' app.log

7.3 生成新配置文件(基于模板)

bash

复制代码
# 有一个模板文件,需要替换里面的变量
cp template.conf production.conf
sed -i.bak \
    -e "s/{{SERVER_NAME}}/${HOSTNAME}/g" \
    -e "s/{{MAX_WORKERS}}/8/g" \
    -e "s/{{LOG_DIR}}/\/var\/log\/myapp/g" \
    production.conf

八、sed常见踩坑

坑一:-E与-r

扩展正则选-E-r取决于系统:

  • GNU sed(Linux):两者都支持,推荐-E

  • BSD sed(macOS):只支持-E

  • 写跨平台脚本时,用-E

坑二:macOS的-i行为不同

macOS的sed要求-i后面必须有备份后缀(即使为空):sed -i '' 's/old/new/' file。Linux的sed允许-i不带参数。跨平台兼容写法:sed -i.bak && rm file.bak,或使用perl替代。

坑三:g是行内所有,不是所有行

新手常误以为g表示所有行,实际上g只影响当前匹配行内部的替换范围。sed默认就对每一行 执行命令,g的作用是让每行内的匹配不止替换第一个。

九、本篇小结

sed核心四操作

操作 命令 示例
查找替换 s/old/new/ sed 's/foo/bar/g' file
删除 d sed '/^$/d' file
前面插入 i sed '/pattern/i\新行' file
后面追加 a sed '/pattern/a\新行' file

地址定位方式

方式 示例
行号 sed '3,10s/old/new/'
模式 sed '/error/s/old/new/'
混合 sed '3,/pattern/s/old/new/'

最佳实践

  1. 先用-n测试,确认无误再用-i.bak

  2. 路径替换换用#|做分隔符

  3. 复杂替换用-E扩展正则,减少转义

动手练习

bash

复制代码
# 1. 删除文件中的所有空行和注释行
sed -E '/^$|^#/d' /etc/ssh/sshd_config

# 2. 将所有的大写字母改为小写(学会\L转换)
echo "UPPER CASE" | sed 's/.*/\L&/'

# 3. 在符合 nginx.conf 中的所有 listen 行前添加注释
sed 's/^\(\s*listen\b\)/# \1/' nginx.conf

# 4. 提取日志中所有状态码
sed -nE 's/.*" ([0-9]{3}) .*/\1/p' access.log | sort | uniq -c

# 5. 将一个文件的第一行到最后一行加上行号
sed = file.txt | sed 'N; s/\n/\t/'

十、下篇预告

sed擅长按行编辑 ,但如果要处理结构化数据------比如统计日志中每个IP的访问次数、对CSV文件做数学运算、格式化输出报表------sed就力不从心了。

下一篇,三剑客的压轴大将awk 即将登场。awk是一门完整的文本处理语言,它把文本拆分为字段,可以对字段进行数学运算、条件判断、格式化输出。日志统计、数据报表、简单的数据库查询......awk都能轻松胜任。


延伸思考 :sed的-np组合可以精确提取特定行,这其实等价于grep的部分功能。但sed在提取的同时还能做替换,这就是它比grep更进一步的地方。而下一篇的awk,则是在sed的基础上再增加了数学运算和条件处理能力。三剑客层层递进,各司其职。

相关推荐
多租户观察室1 小时前
DNS服务器跟普通服务器有什么区别?
运维·服务器
Will_Ye2 小时前
Ubuntu:系统断网后自动重连指定wifi脚本
linux·运维·ubuntu
学术小白人2 小时前
【见刊通知】ICGEM E2025、IPAT 2025、AISNS 2026、IEAS 2025、BTFM 2026 等数个会议已见刊
运维·服务器·检索·rdlink研发家·见刊
郝学胜-神的一滴2 小时前
深入epoll封装:event_set与event_add核心原理剖析
linux·服务器·开发语言·网络·c++·unix
HABuo2 小时前
【linux(四)】套接字编程--socket套接字及其接口认识
linux·运维·服务器·c语言·c++·ubuntu·centos
凤年徐2 小时前
命令行进度条完全指南:倒计时、缓冲区刷新与动态下载
linux
#君君#2 小时前
ros2 下VSCode 中显示波浪线(找不到定义)原因
ide·vscode·编辑器
北山有鸟2 小时前
address-cell& size-cell
linux·网络
小则又沐风a2 小时前
基础的开发工具(Linux)
linux·运维·服务器