文本处理(下):sed
sed 核心定位
sed(Stream Editor)是流编辑器,对文本进行非交互式的批量修改。它逐行读取输入,应用编辑命令,输出结果。
与 vim 的区别:
vim:交互式编辑器,打开文件,手动编辑,保存退出sed:非交互式,适合脚本中批量处理,不修改原文件(除非-i)
基础语法
sed 'command' file # 对 file 执行 command,输出到屏幕
sed -i 'command' file # 直接修改文件(in-place)
sed -e 'cmd1' -e 'cmd2' file # 执行多个命令
最常用:替换(s 命令)
# 基本替换:每行第一个匹配
sed 's/old/new/' file.txt
# 全局替换(g = global):每行所有匹配
sed 's/old/new/g' file.txt
# 替换第 2 个匹配
sed 's/old/new/2' file.txt
# 替换第 2 个及之后的所有匹配
sed 's/old/new/2g' file.txt
# 使用其他分隔符(避免转义 /)
sed 's#/usr/local#/opt#g' file.txt # 用 # 代替 /
sed 's|/usr/local|/opt|g' file.txt # 用 | 代替 /
# 替换并直接修改文件(-i)
sed -i 's/old/new/g' file.txt
# 替换前备份(-i.bak)
sed -i.bak 's/old/new/g' file.txt # 原文件备份为 file.txt.bak
替换中的特殊符号
| 符号 | 含义 | 示例 |
|---|---|---|
& | 代表匹配的整个字符串 | sed 's/hello/[&]/' → [hello] |
\1 \2 | 代表第 n 个捕获组 | sed 's/\(hello\) \(world\)/\2 \1/' |
# 给每行加引号
sed 's/.*/"&"/' file.txt
# 交换两个单词的位置
sed 's/\(word1\) \(word2\)/\2 \1/' file.txt
# 给匹配内容加标记
sed 's/error/[ERROR: &]/g' log.txt
删除行(d 命令)
# 删除第 3 行
sed '3d' file.txt
# 删除第 2 到 5 行
sed '2,5d' file.txt
# 删除包含 "test" 的行
sed '/test/d' file.txt
# 删除以 # 开头的行(注释行)
sed '/^#/d' file.txt
# 删除空行
sed '/^$/d' file.txt
# 删除第 1 行(常用于去掉 CSV 标题)
sed '1d' file.csv
打印行(p 命令)
# 打印第 3 行(-n 抑制默认输出,只打印匹配行)
sed -n '3p' file.txt
# 打印第 2 到 5 行
sed -n '2,5p' file.txt
# 打印包含 "error" 的行
sed -n '/error/p' file.txt
# 打印最后一行($ 表示最后一行)
sed -n '$p' file.txt
# 打印奇数行(步长为 2)
sed -n '1~2p' file.txt
# 打印偶数行
sed -n '2~2p' file.txt
插入和追加(i 和 a 命令)
# 在第 3 行前插入(i = insert)
sed '3i\This is inserted before line 3' file.txt
# 在第 3 行后追加(a = append)
sed '3a\This is appended after line 3' file.txt
# 在包含 "hello" 的行前插入
sed '/hello/i\New line before hello' file.txt
# 在文件开头插入一行
sed '1i\# Header comment' file.txt
# 在文件末尾追加一行
sed '$a\# Footer comment' file.txt
多命令组合
# 使用 -e 连接多个命令
sed -e 's/foo/bar/g' -e 's/hello/hi/g' file.txt
# 使用分号分隔
sed 's/foo/bar/g; s/hello/hi/g' file.txt
# 使用脚本文件(-f)
cat > script.sed
s/foo/bar/g
/hello/d
1i\# Header
# Ctrl+D
sed -f script.sed file.txt
实战场景
场景 1:批量修改配置文件
# 修改 IP 地址
sed -i 's/192.168.1.100/192.168.1.200/g' config.txt
# 修改端口号
sed -i 's/port=8080/port=9090/' config.txt
# 取消注释(去掉行首的 #)
sed -i 's/^#\(listen\)/\1/' config.txt
# 添加注释(在行首加 #)
sed -i 's/^listen/#listen/' config.txt
场景 2:格式化输出
# 给每行加行号
sed = file.txt | sed 'N; s/\n/ /'
# 删除行尾空格
sed 's/[[:space:]]*$//' file.txt
# 将多个空行合并为一个
sed '/^$/N; /^\n$/D' file.txt
# 在文件末尾添加空行(如果原来没有)
sed -e '$a\' file.txt
场景 3:结合管道使用
# 查看进程,去掉 grep 自身
ps aux | grep nginx | sed '/grep/d'
# 查看 IP 地址,只显示 IP 行
ip addr | sed -n '/inet /p'
# 提取版本号
cat version.txt | sed 's/version //'
本篇小结
sed是流编辑器,逐行处理,适合批量修改和脚本自动化s/old/new/g:替换命令,g全局替换,&代表匹配内容,\1代表捕获组d:删除行,3d删除第 3 行,/pattern/d删除匹配行p:打印行,配合-n只输出匹配行,$p打印最后一行i:行前插入,a:行后追加-i:直接修改文件,-i.bak修改前备份-e:执行多个命令,-f:从脚本文件读取命令
动手实践
创建测试文件并替换:
cat > test.txt hello world hello linux hello sed # Ctrl+D sed 's/hello/hi/' test.txt # 每行第一个 sed 's/hello/hi/g' test.txt # 全局 sed -i.bak 's/hello/hi/g' test.txt # 修改并备份 cat test.txt cat test.txt.bak删除练习:
sed '2d' test.txt # 删除第 2 行 sed '/linux/d' test.txt # 删除包含 linux 的行 sed '/^$/d' test.txt # 删除空行打印练习:
sed -n '2p' test.txt # 打印第 2 行 sed -n '1,2p' test.txt # 打印 1-2 行 sed -n '$p' test.txt # 打印最后一行配置文件修改:
cat > config.txt host=localhost port=8080 debug=true # Ctrl+D sed -i 's/localhost/127.0.0.1/' config.txt sed -i 's/port=8080/port=9090/' config.txt sed -i 's/true/false/' config.txt cat config.txt结合管道:
ps aux | sed -n '/bash/p' | sed '/grep/d'思考:
sed 's/old/new/'和sed 's/old/new/g'有什么区别?如果一行中有 3 个 "old",前者会替换几个?后者会替换几个?