sed 命令详解与参数实测(Ubuntu 22.04)
sed 命令逐参数实测详解:替换标志、地址定位、删除插入改写、原地修改与备份、扩展正则、多文件处理与退出码规则,全部命令与回显在 Ubuntu 22.04(GNU sed 4.8)实测通过。
sed 命令详解与参数实测(Ubuntu 22.04)
目标:全面掌握 GNU sed 的参数与核心编辑命令——替换、打印、删除、插入、原地修改、扩展正则与多文件处理,全部命令与回显已在 Ubuntu 22.04(GNU sed 4.8)实测通过。
一、环境信息
- 操作系统:Ubuntu 22.04.5 LTS(x86_64)
- sed 版本:GNU sed 4.8(
sed --version实测) - 实测工作目录:
/root/seddemo/ - 复用目录:
/root/grepdemo/app.log(8 行模拟服务日志,见《grep 命令详解与参数实测(Ubuntu 22.04)》数据集)
二、命令概览
2.1 语法与测试数据准备
sed 的基本语法为 sed [OPTION]... {script-only-if-no-other-script} [input-file]...:逐行读入文件到"模式空间",执行编辑脚本后输出(默认)。测试前准备演示文件:
mkdir -p /root/seddemo
cat > /root/seddemo/f.txt <<'EOF'
apple 10
banana 20
cherry 30
apple 40
EOF
cat > /root/seddemo/conf.txt <<'EOF'
# main config
port = 8080
host = 127.0.0.1
# debug section
debug = true
EOF
cat > /root/seddemo/nums.txt <<'EOF'
1
2
3
4
5
6
7
8
9
10
EOF
2.2 --help 全量回显
sed --help 输出全部参数清单(实测原样保留):
Usage: sed [OPTION]... {script-only-if-no-other-script} [input-file]...
-n, --quiet, --silent
suppress automatic printing of pattern space
--debug
annotate program execution
-e script, --expression=script
add the script to the commands to be executed
-f script-file, --file=script-file
add the contents of script-file to the commands to be executed
--follow-symlinks
follow symlinks when processing in place
-i[SUFFIX], --in-place[=SUFFIX]
edit files in place (makes backup if SUFFIX supplied)
-l N, --line-length=N
specify the desired line-wrap length for the `l' command
--posix
disable all GNU extensions.
-E, -r, --regexp-extended
use extended regular expressions in the script
(for portability use POSIX -E).
-s, --separate
consider files as separate rather than as a single,
continuous long stream.
--sandbox
operate in sandbox mode (disable e/r/w commands).
-u, --unbuffered
load minimal amounts of data from the input files and flush
the output buffers more often
-z, --null-data
separate lines by NUL characters
--help display this help and exit
--version output version information and exit
If no -e, --expression, -f, or --file option is given, then the first
non-option argument is taken as the sed script to interpret. All
remaining arguments are names of input files; if no input files are
specified, then the standard input is read.
GNU sed home page: <https://www.gnu.org/software/sed/>.
General help using GNU software: <https://www.gnu.org/gethelp/>.
E-mail bug reports to: <bug-sed@gnu.org>.
2.3 版本确认
$ sed --version | head -2
sed (GNU sed) 4.8
Packaged by Debian
三、参数逐项实测
3.1 s///:基本替换(每行替换第一处)
sed 's/旧/新/' 是 sed 使用率最高的命令,默认每行只替换第一个匹配:
$ sed 's/apple/APPLE/' /root/seddemo/f.txt
APPLE 10
banana 20
cherry 30
APPLE 40
每行的 apple 都被替换为 APPLE(每个文件行内只出现一次)。若一行内有多个匹配,默认只换第一个,需要 g 标志(见 3.2)。
3.2 g 标志:行内全局替换
$ echo 'a-b-a' | sed 's/a/X/'
X-b-a
$ echo 'a-b-a' | sed 's/a/X/g'
X-b-X
无 g 只换行内第一处;加 g 换掉行内全部匹配。日志批量清洗几乎都应加 g。
3.3 N 标志:只替换行内第 N 处
$ sed 's/apple/APPLE/2' /root/seddemo/f.txt
apple 10
banana 20
cherry 30
apple 40
/2 表示"仅当一行内出现第 2 次匹配时替换第 2 次"。f.txt 每行只有一个 apple,所以没有任何行被替换——这是该标志最容易被误解的地方。2g 则表示"从第 2 处起全部替换"。
3.4 & 与 \1:引用匹配内容
& 代表整个匹配,\1~\9 代表正则分组(BRE 分组用 \(\)):
$ sed 's/^[0-9]*/&.5/g' /root/seddemo/nums.txt | head -3
1.5
2.5
3.5
$ sed 's/\([a-z]\+\) \([0-9]\+\)/\2 \1/' /root/seddemo/f.txt | head -2
10 apple
20 banana
第一个示例把行首数字原样保留并追加 .5;第二个示例用两个分组捕获"单词 数字"并交换顺序。
3.5 -n:抑制自动输出(配合 p)
sed 默认把每行都打出来,-n 关闭自动打印,只有脚本显式 p 的行才输出——这是"打印类"操作的标配:
$ sed -n '1,3p' /root/seddemo/nums.txt
1
2
3
$ sed -n '/banana/p' /root/seddemo/f.txt
banana 20
第一个等效于 head -3;第二个按正则筛选,等效于 grep。
💡 说明:
-n与默认行为的分野在于"谁来决定输出"——默认是"每行都打印 + 替换结果再打印一遍",-n是"只有p命令点名的行才输出"。-n配p是取行的标准组合,忘了-n直接用p会把每行重复打出(实测见 3.5 两条回显的对比逻辑)。
3.6 地址定位:行号、正则、$ 与范围
sed 的编辑命令前可加"地址"限定作用行:
$ sed -n '$p' /root/seddemo/nums.txt
10
$ sed -n '2,$p' /root/seddemo/nums.txt | head -3
2
3
4
$ sed -n '/apple/=;/apple/p' /root/seddemo/f.txt
1
apple 10
4
apple 40
$ sed -n '/ERROR/{p;=}' /root/grepdemo/app.log | head -4
2026-10-15 09:00:02 ERROR Connection refused: db-primary:5432
2
2026-10-15 09:00:05 ERROR Timeout after 30s calling db-replica:5432
5
$表示最后一行(注意单引号内$p不会被 shell 展开);2,$p从第 2 行打到末尾;=命令输出行号,/apple/=;/apple/p先号后文;{p;=}把两条命令打包到同一个地址上,输出"内容+行号"紧挨的形式,比;写法更紧凑。
3.7 d:删除行
$ sed '/^#/d' /root/seddemo/conf.txt
port = 8080
host = 127.0.0.1
debug = true
$ sed '/^$/d' /root/seddemo/conf.txt
# main config
port = 8080
host = 127.0.0.1
# debug section
debug = true
$ sed '/^#/d; /^$/d' /root/seddemo/conf.txt
port = 8080
host = 127.0.0.1
debug = true
/^#/d 删注释行、/^$/d 删空行,两者组合是"读取配置文件有效内容"的标准动作。多脚本可用 ; 连接(见 3.15)。
3.8 a / i / c:追加、插入、改写整行
$ sed '2a\inserted after line2' /root/seddemo/f.txt
apple 10
banana 20
inserted after line2
cherry 30
apple 40
$ sed '2i\inserted before line2' /root/seddemo/f.txt
apple 10
inserted before line2
banana 20
cherry 30
apple 40
$ sed '2c\replaced line2' /root/seddemo/f.txt
apple 10
replaced line2
cherry 30
apple 40
$ sed '$a\# end of file' /root/seddemo/conf.txt
# main config
port = 8080
host = 127.0.0.1
# debug section
debug = true
# end of file
a 在匹配行后追加、i 在匹配行前插入、c 把匹配行整行改写;$a\... 在文件末尾追加一行(写自动化初始化脚本时常用)。
3.9 -i:原地修改文件(可选备份后缀)
$ cp /root/seddemo/conf.txt /root/seddemo/conf-edit.txt && sed -i 's/debug = true/debug = false/' /root/seddemo/conf-edit.txt && grep debug /root/seddemo/conf-edit.txt
# debug section
debug = false
$ cp /root/seddemo/conf.txt /root/seddemo/conf-bak.txt && sed -i.bak 's/host = 127.0.0.1/host = 192.168.1.10/' /root/seddemo/conf-bak.txt && ls /root/seddemo/conf-bak* && grep host /root/seddemo/conf-bak.txt && echo '--- .bak 内容:' && grep host /root/seddemo/conf-bak.txt.bak
/root/seddemo/conf-bak.txt
/root/seddemo/conf-bak.txt.bak
host = 192.168.1.10
--- .bak 内容:
host = 127.0.0.1
-i 直接改写文件(不输出到屏幕);-i.bak 修改前先把原文件复制为 .bak 后缀备份。批量改配置时务必带备份后缀,改错可回滚。
💡 说明:
-i是 sed 唯一会"破坏现场"的参数——其余参数都只影响输出流,-i却直接改写磁盘上的原文件。安全用法两条:总是带备份后缀(-i.bak);先不带-i跑一遍确认替换范围,再加-i落盘。
⚠️ 注意:
-i会重建文件(inode 变化),对硬链接文件使用会"断链",涉及软链时GNU 提供了--follow-symlinks参数。
3.10 -E / -r:扩展正则表达式
$ sed -E 's/([a-z]+) ([0-9]+)/\2 \1/' /root/grepdemo/app.log | head -2
2026-10-15 09:00:01 INFO Service started on 8080 port
2026-10-15 09:00:02 ERROR Connection refused: db-primary:5432
$ sed -n -E 's/^([0-9-]+) ([0-9:]+).*/\1T\2/p' /root/grepdemo/app.log | head -3
2026-10-15T09:00:01
2026-10-15T09:00:02
2026-10-15T09:00:03
-E 下 () + 直接书写:第一例把 port 8080 交换为 8080 port;第二例用两个分组把日志前缀重排成 ISO 8601 形式的 日期T时间,是从日志抽取时间戳的实用套路。
💡 技术拓展:sed 的正则流派与 grep 同源,默认 BRE(分组
\(\)、量词\+ \? \{n,m\})与扩展 ERE(分组()、量词+ ? {n,m}直接写):
流派 写法 转义要求 建议场景 BRE(默认) s/\([a-z]\+\)/X/分组与量词都要 \兼容 POSIX 老脚本 ERE( -E)s/([a-z]+)/X/无需转义 新脚本首选,可读性最好 本文 ERE 示例统一用
-E(-r是同义旧写法,POSIX 只认-E);对比 3.4 与本节同一交换逻辑的两种写法即可体会差异。
3.11 BRE 与 ERE 的实测差异:+ 必须转义
BRE(默认)里 + 是普通字符,写成 \+ 才是"一次以上";忘记转义时替换静默失效:
$ sed 's/\([a-z]+\)\([0-9]+\)/\2 \1/' /root/seddemo/f.txt | head -2
apple 10
banana 20
$ sed 's/\([a-z]\+\) \([0-9]\+\)/\2 \1/' /root/seddemo/f.txt | head -2
10 apple
20 banana
第一例的 [a-z]+ 中 + 被当字面量,模式不命中、输出原样——sed 替换"没生效"时先查转义;第二例转义后正确交换。
3.12 自定义分隔符 s|…|…| 与元字符转义
s 的分隔符可以是任意字符(|、#、, 等),处理路径时避免与 / 冲突:
$ echo '/usr/local/bin' | sed 's|/usr/local/bin|/opt/bin|'
/opt/bin
$ sed 's/127\.0\.0\.1/localhost/' /root/seddemo/conf.txt
# main config
port = 8080
host = localhost
# debug section
debug = true
用 | 作分隔符后路径中的 / 无需转义;IP 地址中的 . 是正则元字符(匹配任意字符),严格匹配需转义为 \.。
3.13 y///:逐字符映射转换
$ echo 'hello' | sed 'y/abcdef/ABCDEF/'
hEllo
y/源集/目标集/ 按字符位置一一映射转换(与正则无关):h 不在源集中保持,e 映射为 E。源集与目标集长度必须相等,适合固定字符翻译(如大小写、全半角)。
3.14 =:输出行号
$ sed '=' /root/seddemo/f.txt
1
apple 10
2
banana 20
3
cherry 30
4
apple 40
= 把每行行号单独打一行,输出"号、文"交替的形式,常用于快速给文件编行号。
3.15 -e:叠加多条编辑脚本
$ sed -e 's/apple/APPLE/g' -e 's/banana/BANANA/g' /root/seddemo/f.txt
APPLE 10
BANANA 20
cherry 30
APPLE 40
-e 可多次出现,脚本按顺序逐条作用于每行;同一地址内也可用 ; 连接(sed '/^#/d; /^$/d',见 3.7)。注意 s///; s/// 中第二个替换看到的是第一个替换后的结果。
3.16 -f:从文件读取脚本
$ cat /root/seddemo/sed-script.sed
s/cherry/CHERRY/g
2d
$ sed -f /root/seddemo/sed-script.sed /root/seddemo/f.txt
apple 10
CHERRY 30
apple 40
脚本文件每行一条命令,按序执行(第 2 行被删除、cherry 被替换)。复杂编辑逻辑放脚本文件便于版本管理与复用。
3.17 -z:以空字节分隔"行"
$ printf 'a\nb\x00c\nd\x00' > /root/seddemo/null.dat
$ sed -z 's/\n/-/g' /root/seddemo/null.dat | xxd | head -3
00000000: 612d 6200 632d 6400 a-b.c-d.
-z 后 sed 以 \0 而非 \n 分隔记录:\x00 前的 a\nb 成了一条记录,s/\n/-/g 把其中的换行替换为 -,得到 a-b\0c-d\0。配合 find -print0/xargs -0 处理含换行等特殊字符的文件名场景。
3.18 -s:多文件各自独立处理
$ cp /root/seddemo/nums.txt /root/seddemo/nums2.txt && sed -s '1s/^/== /' /root/seddemo/nums.txt /root/seddemo/nums2.txt
== 1
2
3
4
5
6
7
8
9
10
== 1
2
3
4
5
6
7
8
9
10
默认多文件被串成一条连续流,行号跨文件累计——1s/^/== / 只会给第一个文件的第 1 行加前缀;-s 让每个文件独立计数,因此两个文件的第 1 行都被加了 == 。
3.19 -u:无缓冲输出
$ echo 'x' | sed -u 's/x/y/'
y
-u 尽量少读输入、尽快刷新输出,适合 tail -f app.log | sed -u ... 这类实时流处理,避免输出因缓冲积压而延迟。
3.20 大小写转换 \U \L \E 与 I 标志(GNU 扩展)
$ sed -E 's/ERROR/\L&/' /root/grepdemo/app.log | head -2
2026-10-15 09:00:01 INFO Service started on port 8080
2026-10-15 09:00:02 error Connection refused: db-primary:5432
$ sed -E 's/(error)/\U\1\E done/' /root/grepdemo/app.log | sed -n '6p'
2026-10-15 09:00:06 ERROR done lowercase message should match with -i
$ sed -E 's/error|warn/CASE/Ig' /root/grepdemo/app.log | head -3
2026-10-15 09:00:01 INFO Service started on port 8080
2026-10-15 09:00:02 CASE Connection refused: db-primary:5432
2026-10-15 09:00:03 CASE Retry attempt 1 for db-primary
替换串中 \U 后续转大写、\L 后续转小写、\E 结束转换(GNU 扩展,POSIX 模式不可用):第一例把 ERROR 降为小写,第二例把小写 error 升为大写并拼接 done。I 标志让 s 命令忽略大小写匹配(第三例同时命中 ERROR 与 WARN),等价 grep 的 -i。
3.21 !:地址取反
$ sed '4!s/apple/APPLE/' /root/seddemo/f.txt
APPLE 10
banana 20
cherry 30
apple 40
$ sed '2,3!d' /root/seddemo/nums.txt
2
3
4!s/... 表示"除第 4 行外都替换";2,3!d 表示"除 2~3 行外都删除",两者组合可实现"保留指定行"的取反逻辑。
3.22 w 标志:把替换命中的行写入文件
$ sed 's/apple/APPLE/gw /root/seddemo/w-out.txt' /root/seddemo/f.txt >/dev/null && cat /root/seddemo/w-out.txt
APPLE 10
APPLE 40
w 文件 把发生替换的行另存一份,主输出照常;适合"边改边留存变更记录"。--sandbox 模式会禁用 w/r/e 等涉及外部文件的命令。
3.23 退出码规则
$ sed 's/apple/APPLE/' /root/seddemo/f.txt >/dev/null; echo exit=$?
exit=0
$ sed 's/x/y/' /root/nosuchfile.txt; echo exit=$?
sed: can't read /root/nosuchfile.txt: No such file or directory
exit=2
sed 的退出码只反映脚本执行是否出错:处理成功返回 0(与"是否发生替换"无关),输入文件无法读取等 I/O 错误返回 2,脚本语法错误返回 1。因此不能用退出码判断"有没有替换发生",这点与 grep 不同。
四、实战示例
4.1 批量改配置并备份
$ cp /root/seddemo/conf.txt /root/seddemo/conf-bak.txt && sed -i.bak 's/host = 127.0.0.1/host = 192.168.1.10/' /root/seddemo/conf-bak.txt && grep host /root/seddemo/conf-bak.txt
host = 192.168.1.10
-i.bak 先留底再改写,回滚只需 mv conf-bak.txt.bak conf-bak.txt。
4.2 清洗配置文件:去注释去空行
$ sed '/^#/d; /^$/d' /root/seddemo/conf.txt
port = 8080
host = 127.0.0.1
debug = true
一条命令得到"有效配置行",比 grep -v 双管道少一次进程。
4.3 日志时间戳重排为 ISO 8601
$ sed -n -E 's/^([0-9-]+) ([0-9:]+).*/\1T\2/p' /root/grepdemo/app.log | head -3
2026-10-15T09:00:01
2026-10-15T09:00:02
2026-10-15T09:00:03
-E + 两个分组 + p:抽取日期与时间并拼接 T,可直接喂给需要 ISO 格式的解析器。
4.4 多文件独立编号加标记
$ sed -s '1s/^/== /' /root/seddemo/nums.txt /root/seddemo/nums2.txt | head -2
== 1
2
配合 -s 给每个文件首行统一加标记,输出拼接报表时的分文件分隔技巧。
五、注意事项与常见问题
Q1:替换命令"没生效"也没有报错?
先查三处:BRE 的 + ? () {n,m} 未转义(见 3.11);大小写不匹配(需要 I 标志或先转大小写,见 3.20);地址限定的行号/正则没有命中任何行。
Q2:-i 误改了文件能恢复吗?
-i 不带后缀时无备份,无法用 sed 恢复,只能靠外部备份(git、cp);养成 -i.bak 习惯(见 3.9)。
Q3:想替换整行内容?
c\新内容 是整行改写,s/旧/新/ 是行内子串替换,语义不同(见 3.8 与 3.1)。
Q4:脚本能用退出码判断"替换是否发生"吗? 不能。sed 成功处理即返回 0,与替换次数无关;与 grep 的 0/1 语义不同(见 3.23)。
Q5:多文件行号错乱?
默认多文件串成一条流、行号累计;需要每文件独立编号/定位时加 -s(见 3.18)。
Q6:文件名带空格/换行怎么安全传递?
用 find ... -print0 配合 sed -z 或 xargs -0,以空字节为安全分隔符(见 3.17)。
Q7:\U \L 在别的机器上失效?
大小写转换是 GNU 扩展,--posix 模式或 BSD sed(macOS)不支持;跨平台脚本请改用 tr 或 awk 处理大小写。
六、参考链接
- GNU sed 官方手册:https://www.gnu.org/software/sed/manual/sed.html
- 本机帮助:
sed --help、man sed(Ubuntu 22.04 内置) - POSIX sed 规范:https://pubs.opengroup.org/onlinepubs/9699919799/utilities/sed.html