SED 即 Stream EDitor。和交互式编辑器如 vi 需要打开整个文件不同,sed 是行编辑器,每次处理一行,比较适合在脚本中进行无交互编辑,对于大文件如果不大量使用多行技术,一般不会出现内存不足的问题。
对于 sed,我们可以这样理解,sed 就是一个店面,有两个房间,一个房间用于处理数据,另一个房间用于暂时保存数据,类似于临时仓库。店面的门口排队着一行行来自文件或管道的文本,店家让排队的行自动进入房间,但是一次只能进入一行,然后进行匹配和处理,直到全部处理完毕。
处理用的房间是根据指定的条件对读入的文本行确定是否要处理的,而条件可以通过指定具体行号(例如第 3 行),或者正则表达式的模式(pattern)来匹配输入行(例如,包含 /hello/的行)、或混合使用以上两种方式确定两行来组成的连续行范围(例如,第 5 行至第 10 行,或第 1 行至匹配 /foo/ 为止)。从广义角度看,正则的 pattern 和行号都是某种模式,因此该房间称为模式空间 (Pattern Space),另一个用于保存数据的房间称为保持空间 (Hold Space)。
模式空间中处理的动作包括:删除行、读入文件、输出至文件或屏幕、插入文本、修改或替换文本、或者把模式空间的行暂时保存至保持空间及其反向操作等等。
我们一定要明确的是:sed 命令会自动进行循环的,一次只读入一行,读入的文本先删除其尾随的换行符,然后在模式空间中进行匹配和处理,在开启下一轮循环之前如果没有禁用默认的自动打印,或者使用打印命令输出,则在输出行后追加换行符,确保换行。另外,sed 默认不会修改原文件,除非指定 -i 选项。
sed [option]... [script] [input-file]...
其中:script 表示要执行的命令脚本,input-file 表示要进行处理的文件。
这里先举例部分工作可能需要的示例:
示例一: 禁用 SELinux
# sed -Ei.bak 's/^(SELINUX=).*/\1disabled/' /etc/selinux/config
以上命令中选项 -E 表示使用扩展正则表达式,选项 -i.bak 表示先备份原文件并修改原文件。脚本包含在单或双引号中,s/// 替换命令先搜索以 ‘SELINUX=’ 开头的行,如果匹配,则替换为 ‘SELINUX=disabled’,其中的一对小括号表示分组,\1 代表引用前面匹配到的分组内容。最后是要修改的文件。
示例二: 替换 ‘/etc/httpd/htthttpd.conf’ 中两行文本为 ‘User apache’ 和 ‘Group apache’
# sed -Ei.bak -e 's/^(User).*/\1 apache/' -e 's/^(Group).*/\1 apache/' /etc/httpd/httpd.conf
以上命令中两个选项 -e 分别指定脚本。
示例三: 修改 CentOS 8 中文件 ‘/etc/default/grub’,在匹配行最后双引号前增加 ’ net.ifname=0’, 实现网卡名使用 ‘eth0’ 这样的格式,注意完成以下操作后要重启系统
# sed -Ei.bak '/^GRUB_CMDLINE_LINUX/s/"$/ net.ifnames=0"/' /etc/default/grub
# cat /etc/default/grub
GRUB_TIMEOUT=5
GRUB_DISTRIBUTOR="$(sed 's, release .*$,,g' /etc/system-release)"
GRUB_DEFAULT=saved
GRUB_DISABLE_SUBMENU=true
GRUB_TERMINAL_OUTPUT="console"
GRUB_CMDLINE_LINUX="resume=UUID=2a7ff86b-764e-490a-b082-bd8e26dcc35e rhgb quiet net.ifname=0"
GRUB_DISABLE_RECOVERY="true"
GRUB_ENABLE_BLSCFG=true
# grub2-mkconfig -o /boot/grub2/grub.cfg # 适用 CentOS 7 及以上
# grub-mkconfig -o /boot/grub/grub.cfg # 适用 Ubuntu
以上 sed 脚本中 /^GRUB_CMDLINE_LINUX/ 用以匹配要修改的文本行,s/// 则替换匹配行中的最后那个双引号($ 表示行尾位置)变成 ’ net.ifname=0"'。
示例四: 找出 ‘eth0’ 网卡的 ip 地址
# ifconfig eth0 | sed -En '2s/^[^0-9]+([0-9.]+) .*$/\1/p'
10.0.0.100
以上脚本中的 ‘2’ 指定行号为 2 的地址条件,替换命令中搜索部分 ^[^0-9]+ 表示以非数字组成的开头字符串,小括号内表示以数字和点号组成的内容,最后空格开始至最后,替换部分 \1 引用小括号分组的内容,p 打印出替换后的结果。
示例五: 如果想在 sed 脚本使用变量的值,这时需要双引号或使用三个单引号
# var=bar; echo foo | sed 's/foo/$var/'
$var
# var=bar; echo foo | sed "s/foo/$var/"
bar
# var=bar; echo foo | sed 's/foo/'''$var'''/'
bar
-n, --quiet, --silent
sed 默认在每次脚本执行循环结束前打印模式空间中的内容,而该选项禁用该自动打印功能。
-e script, --expression=script
把指定脚本中的命令添加到要执行的命令集合中,命令行上可以使用多次。
-f script-file, --file=script-file
把包含在脚本文件中的命令添加到准备运行的命令集合中。
-E, -r, --regexp-extended
使用扩展正则表达式,而不是基础正则表达式。
-i[SUFFIX], --in-place[=SUFFIX]
指定该选项将对原文件进行就地编辑。如果不使用该选项,默认不会对原文件进行修改。
这是通过创建一个临时文件并把处理后的输出重定向到此文件,当处理完所有行到达输入文件结尾时,
将该临时文件重命名为原始文件名(覆盖原文件)以实现就地编辑。
如果提供了扩展名后缀(SUFFIX),则在临时文件重命名之前,先对原文件进行重命名,即在
其原名称后面追加指定的后缀,从而生成备份副本。
如果没有提供扩展名后缀,则会覆盖原始文件而不备份!!!
注意:由于 '-i' 可以有一个可选的参数,所以,在其后不能跟随其他短选项,例如:
sed -Ei '...' filename
这与没有备份后缀的选项 '-E -i' 一样。filename 就会就地编辑,不会创建备份文件。而
sed -iE '...' filename
这与 '--in-place=E' 长选项一样,从而创建一个名为 'filenameE' 的备份文件。
警告:小心同时使用 '-n -i' 选项,前者禁用自动打印,后者则在没有备份的情况下就地编辑原文件。
如果不小心使用了该混合选项组合(且没有显示使用 'p' 命令),那么原文件将为空:
sed -ni 's/foo/bar/' filename
-follow-symlinks
此选项仅在支持符号链接的平台可用,并且仅在指定了选项 '-i' 时有效。如果指定的文件是符号链接,
则 sed 将会编辑符号链接引用的最终目标文件。默认是断开符号链接,不修改链接的目标文件。
-s, --separate
默认 sed 会把指定的多个文件视为一个单独的连续的大文件。通过该选项,允许把它们视为各自单独的文件。
因此,像 '/abc/, /def/' 这样的地址范围匹配不会跨越多个文件,行号与每个文件相联系,
'$' 引用每个文件的最后一行,并且,使用 'R' 命令调用文件时,会倒回到每个文件的开始。
-u, --unbuffered
尽可能及时输入和输出。如果输入来自像 'tail -f',并且希望尽早看到处理后的结果,特别有用。
-z, --null-data, --zero-terminated
把输入的内容当成文本行集合,每一行由零字节(ASCII 'NUL' 字符)结尾,而不是换行符结束。
该选项与 'sort -z' 和'find -print0' 等命令一起使用,可以处理任何文件名。
--version
打印正在运行的 sed 版本,然后退出
--help
打印使用 sed 的帮助
如果在命令行上没有使用 -e、-f 选项,那么,命令行上第一个非选项参数将被当成要执行的脚本。
如果在处理完上述内容后仍有任何参数,则这些参数都将解释为要处理的输入文件名。文件名 - 表示标准输入流。如果未指定文件名,则处理标准输入。
sed 脚本由一个或多个 sed 命令组成,这些命令通过一个或多个 -e、-f 选项后的参数传入,或者,如果没有使用这些选项,则第一个非选项参数传入的视为 sed 命令脚本。
sed 命令遵循以下语法:
[addr]X[options]
其中 X 是一个单字符的 sed 命令占位符。[addr] 是一个可选的行地址,如果没有指定地址,命令 X 会对所有输入行进行处理。addr 可以是一个单独的行号、一个正则表达式匹配的行、或者,由行号或正则匹配的行(需要两个地址)组成形成连续的行范围。[optinos] 是可用于某些 sed 命令的选项。
下面的示例会删除输入文件的第30行到第35行,并将输出结果重定向到 output.txt 文件中,其中 30, 35 是一个地址范围,d(delete) 是删除命令:
sed '30, 35d' input.txt > output.txt
下面的示例将打印所有的输入行,直到匹配以 foo 单词开头的行为止。如果匹配到指定的模式,则 sed 就立即中止,退出状态码为 42,如果匹配不带模式,同时没有发生其他错误,sed 退出时状态码为 0。其中以双斜杆包围的表达式 /^foo/ 是正则表达式,q(quit) 是退出命令,42 是退出命令时的状态码:
sed '/^foo/q42' input.txt > output.txt
脚本或脚本文件中的多个命令之间可以使用分号或换行符分隔,也可以使用多个 -e 或 -f 选项指定多个脚本。
下面的示例都是等价的。它们完成两个 sed 操作:先逐行读入,删除匹配正则表达式 /^foo/ 的行;然后再用 world 替换行中的第一个出现的 hello (如果有的话):
sed '/^foo/d; s/hello/world/' input.txt > output.txt
sed -e '/^foo/d' -e 's/hello/world' input.txt > output.txt
echo '/^foo/d' > script.sed # 把脚本写入文件中
echo 's/hello/world' >> script.sed # 把脚本追加到文件中
sed -f script.sed input.txt > output.txt
echo 's/hello/world' > script2.sed
sed -e '/^foo/d' -f script2.sed input.txt > output.txt
由于语法原因,命令 a(append)、c(change)、i(insert) 后面不能使用分号作为命令之间的分隔,因此,应该使用换行作为这些命令之间的分隔,或者,把这些命令放在脚本的最后。
sed 提供了以下命令。注意,每次读入文本行时都会自动删除行尾的换行符,而在输出时自动在行尾追加换行符。
a\
text
a text
(append) 在匹配到的行后追加一行指定的文本 'text'。
c\
text
c text
(change) 使用文本 'text' 替换或改变匹配的行。
i\
text
i text
(insert) 在当前行的前面插入一行指定文本 'text'。
d
(delete) 删除模式空间的内容,一般就是当前读入的行,立即开始下一轮循环。
D
如果模式空间中含有换行符,则删除该空间中的第一个换行符及其前面的文本,然后,在没有读取下一行文本
的前提下开始下一轮循环,注意,此时模式空间中还留有未删除的内容。
如果模式空间中没有换行符,则删除空间中的内容,并启动新一轮循环,这与执行 'd' 命令效果一样。
e
(execute) 发送到 shell 中执行在模式空间中找到的命令,并使用命令执行后的结果替换模式空间中的内容,
且删除行尾的换行符。
e command
执行 'command' 命令,并将其执行结果发送至输出流。该命令可以跨多行执行,除了最后以反斜杠结尾的行例外。
F
(filename) 打印当前输入文件的名称(以一个换行符结尾)。
g
(goto) 使用保持空间的内容替换模式空间的内容。
G
先在模式空间中的内容后追加一个换行符,再追加保持空间的内容。
h
(hold space) 使用模式空间的内容替换保持空间的内容。
H
先在保持空间中追加一个换行符,再追加模式空间中的内容。
l
(list) 以可视的形式打印模式空间的内容。就是将不可打印的字符打印成可显示的 C 语言式样
的字符,例如,相应的转义序列打印成 '\a'、'\b'、'\f'、'\r'、'\t' 和 '\v' 等,并在行尾添加一个 '$'。
n
(next) 如果没有禁用自动打印功能,则先打印模式空间的内容,然后,立即从输入文件中读取下一行文本,
并替换掉模式空间。如果没有更多的输入,则 sed 直接退出。
N
先在模式空间中内容后面追加一个换行符,再追加从输入文件中读取的下一行文本。
如果没有更多的输入,sed 则退出。
p
(print) 打印模式空间中的内容。
P
(大写) 打印模式空间中第一个换行符及其前面的内容。
q[exit-code]
(quit) 打印模式空间内容后,不执行更多的命令后退出。'exit-code' 是可以指定的退出状态码。
Q[exit-code]
该命令与 'q' 命令一样,但是不会打印模式空间的内容。
r filename
(read) 在匹配的行后读入以 "filename" 为文件名的文件内容。
R filename
把要读入的文件排成一队,并在当前循环结束后,或者在读取下一行文本前,
把 'filename' 的内容插入到输出流中。
w filename
(write) 把模式空间中的内容写到以 "filename" 为文件名的文件中。
W filename
把模式空间中的第一个换行符及其前面的内容写到以 "filename" 为文件名的文件中。
b label
(branch) 表示程序无条件地跳转到标签 'label' 位置。如果省略了标签,将开始下一轮循环。
#
# 及其后面直至换行符之前的文本都是注释。
{ cmd; cmd ...}
将多个命令组合在一起,形成命令集。
=
打印当前输入行的行号,并换行。
t label
(test) 只有在当前读入的行上执行上一个 's' 替换命令成功,或者,执行条件分支后,程序才会跳到
标签 'label' 处。如果标签省略,则开始下一轮循环。
T label
只有在当前行上执行上一个 's' 替换命令失败后,或者,执行条件分支后,程序才会跳到标签 'label' 处。
变迁如果省略,开始下一轮循环。
: label
为分支命令('b' 表示无条件分支,'t'、'T' 表示条件分支)指定跳转标签 'label' 的位置。
x
(exchange) 交换模式空间和保持空间中的内容。
z
(zap) 清空模式空间中的内容。
y/src/dst/
如果模式空间中的内容匹配 'src' 源字符集中的字符,则逐个转换为 'dst' 目标字符集中对应的字符。
该命令的功能与 shell 中的 'tr' 命令类似。注意,其内部不能使用正则表达式。
替换命令 s(substitute) 可能是 sed 中最重要的命令,它有许多选项。其使用语法是:
's/regexp/replacement/flags'
该命令的基本概念不复杂:即试图使用正则表达式中的搜索模式 regexp 去匹配模式空间中的内容,如果匹配成功,则使用替换部分 replacement 替换匹配的部分。这个语法在许多命令中都有,例如 vim、bash中。
替换部分可以使用表达式 \n 反向引用正则表达式中的使用小括号进行分组匹配的内容,n 取值范围是 [1-9]。此外,替换部分可以使用未用反斜杠转义的 & 来引用正则表达式匹配的全部。
替换命令中的三个斜杠 / 可以统一变更为其它任意单个字符。例如,s/foo/bar/ --> s@foo@bar@。在某些情况下,可以避免使用很多的反斜杠进行转义。
最后,作为 GNU sed 的扩展,可以使用反斜杠和以下字符组成的序列进行大小写字符转换。具体含义如下:
\L (lowercase) 在遇到 '\U' 或者 '\E' 之前,把替换部分 'replacement' 全部字符转化成小写。
\l (lowercase) 把下一个字符转换成小写。
\U (uppercase) 在遇到 '\L' 或者 '\E' 之前,把替换部分 'replacement' 全部字符转化成大写。
\u (uppercase) 把下一个字符转换成大写。
\E (end) 终止有 '\L' 或者 '\U' 开始的大小写转换。
当使用 g(global) 标志 (flag) 时,执行全局替换。例如,在模式空间中的字符串 a-b-,执行以下命令时:
$ echo 'a-b-' | sed -E 's/(b?)-/x\u\1/g'
axxB
这里使用 -E,表示使用扩展正则表达式,可以少使用反斜杠进行转义,更简洁点。正则表达式中的 ? 表示前面的字符可以出现 0 次或者 1 次,小括号包围的字符串表示分组,后面可以使用 \1 来引用第一个分组。在以上正则表达式第一次进行匹配时会匹配到第一个 -,第二次匹配到 b-,因此,\1 分别引用两次匹配到的分组字符串 '' 和 b。
如果没有使用 g 标志,则只会替换出现的第一个匹配项:
$ echo 'a-b-' | sed -E 's/(b?)-/x\u\1/'
axb-
如果含有 \l 和 \u 的替换部分中使用了反向引用表达式 \n,而反向引用的分组内容为空时,那么,\l 和 \u 将影响反向引用后面的字符。例如,以下命令:
$ echo 'a-b-' | sed 's/\(b\?\)-/\u\1x/g'
aXBx
由于第一次匹配到的分组内容为空,因此 \u 影响到 \1 后面的 x 变成大写的 ‘X’。第二次匹配到分组内容 b-,\u 影响到字符 b 变成大写 B。
如果想在替换部分包含字面的 \、& 或换行符,那么,请确保在这些字符前面使用反斜杠进行转义,即成 \\、\&、\n。
s 替换命令可以使用 0 个或 0 个以下的标志:
g
(global) 全局替换所有匹配正则表达式的文本,而不是默认的第一个匹配项。
number
只替换地 'number' 个匹配的 'regexp' 项。
注意:如果同时使用 'g' 和 'number' 标志,对于 GNU sed 来说,会忽略第 'number' 个
前面的匹配项,匹配和替换第 'number' 个及其后面的匹配项。
p
(print) 如果替换成功,则打印替换后的模式空间内容。
注意:如果同时指定了标志 'p' 和 'e',那么,这两个标志的相对次序不同会产生不同
的结果。一般而言,'ep' 是您想要的,即先执行再打印。但是,反过来操作对于调试很有用。
w filename
(write) 如果替换成功,则将替换后的结果写到以 'filename' 为文件名的文件中。作为
GNU sed 扩展,如果没有使用选项 '-i',提供了两种特殊的文件名:'/dev/stderr',
把结果打印到标准错误中;'/dev/stdout',把结果打印到标准输出中。
e
(execute) 此标志允许将 shell 命令通过管道输入到模式空间,如果替换命令成功,那么,
执行在模式空间中找到的命令,并用命令执行的结果替换模式空间,删除尾随的换行符。
该标志是 GNU sed 扩展。
I
(insensitive) 这是 GNU sed 的扩展,它使 sed 以不区分大小写的方式匹配正则表达式。
M
(multi-line) 这是 GNU sed 的扩展。它指示 sed 在多行模式下匹配正则表达式。
其修饰符引起 '^' 和 '$' 分别匹配换行符及其后的空位置,行尾空位置及其后的换行符。
而特殊字符序列 (\' 和 \') 一直分别匹配缓冲区的开始和结尾。
另外,在多行模式下,'.' 字符不会匹配换行符。
如果您使用 sed,很可能想要知道以下命令:
# [不允许有地址]
字符 # 开始一个注释,一直到换行为止。
q[exit-code]
不会执行更多的命令,或者再读入行,立即退出 sed。例如,在打印第 2 行后退出:
$ sed 3 | sed 2q
1
2
该命令只能接受一个地址。注意,如果没有使用 -n 禁用自动打印功能,则会打印模式空间的内容。
d
删除模式空间的内容,立即启动下一轮循环。例如,删除第 2 输入行:
$ seq 3 | sed 2d
1
3
p
打印模式空间的内容至标准输出。此命令通常只与 -n 选项一起使用。例如,只打印第 2 行:
$ seq 3 | sed -n 2p
2
n
如果没有禁用自动打印功能,则默认会打印模式空间的内容,然后,读入下一输入行,并替换模式空间。如果没有了更多的输入行,则退出 sed。
此命令用于跳过一个输入行。例如,完成间隔两行后的替换(2 个 n 跳过 2 行):
$ seq 6 | sed 'n; n; s/./x/'
1
2
x
4
5
x
GNU sed 提供一个扩展的地址语法:‘开始~步长’ 起到相同的效果:
$ seq 6 | sed '0~3s/./x/'
{ cmds }
可以把一组命令组合成命令集。当您希望在匹配正则表达式时,一起执行几个命令时特别有用。例如,
完成替换后打印第 2 行:
$ seq 3 | sed -n '2{s/2/x/; p}'
x
虽然以下命令使用频率相比较低,但是,使用它们可以构建一些小而实用的 sed 脚本。
y/source-chars/dest-chars/
模式空间中的任何字符如果能够匹配源字符集 (source-chars) 中的字符,那么,逐个转换成目标字符集 (dest-chars) 中的相对应位置的字符。与 tr 命令功能相似。
例如,转换 ‘a-j’ 至 ‘0-9’:
$ echo hello world | sed ‘y/abcdefghij/0123456789/’
74llo worl3
a text
在匹配行后添加文本 ‘text’。例如,在第 2 后面追加单位 ‘hello’:
$ seq 3 | sed '2a hello'
1
2
hello
3
`a\
text`
在匹配行后添加文本 ‘text’。例如,在第 2 后面追加单位 ‘hello’:
$ seq 3 | sed '2a\
> hello' # 字符 '>' 是自动产生的,不是输入的
1
2
hello
3
以下命令接受两个地址,并在不带反斜杠的 ‘world’ 行后继续:
seq 3 | sed '2a\
> hello\
> world
> 3s/./x/'
1
2
hello
world
x
作为 GNU sed 扩展,a 命令和文本 ‘text’ 可以分为两个 -e 参数,从而实现更简单的脚本编写:
$ seq 3 | sed -e '2a\' -e hello
$ sed -e '2a\' -e "$VAR"
i text
在当前行前插入文本 ‘text’。例如:在第二行前面插入单词 ‘hello’:
$ seq 3 | sed ‘2i hello’
1
Hello
2
3
c text
使用文本 ‘text’ 替换一行或多行。例如,使用单词 ‘hello’ 替换第 2 行至第 9 行:
$ seq 10 | sed '2, 9c hello'
1
hello
10
=
打印当前输入行的行号,且换行。例如:
$ printf '%s\n' aaa bbb ccc | sed =
1
aaa
2
bbb
3
ccc
r filename
在匹配行后面读入文件内容。例如:
$ seq 3 | sed '2r/etc/hostname'
1
2
Fencepost.gun.org
3
其他几个命令
l n、w filename、D、N、P、h、H、g、G 和 x。
在 sed 程序中有几种方法可以指定多个命令。
当使用 -f 选项来指定从脚本文件中读取并运行 sed 脚本时,使用换行来分隔命令是最自然的。而命令行上,所有的 sed 命令也可以使用换行来分隔,或者,可以把每个命令都作为 -e 选项的参数使用:
$ seq 6 | sed '1d
> 3d
> 5d'
2
4
6
$ seq 6 | sed -e 1d -e 3d -e 5d
2
4
6
可以使用分号来分隔简单的命令
$ seq 6 | sed ‘1d; 3d; 5d’
2
4
6
{、}、b、t、T 这些命令可以使用分号进行分隔,例如:
$ seq 4 | sed '{1d;3d}'
2
4
$ seq 6 | sed '{1d;3d};5d'
2
4
6
在 b、t、T 命令中使用标签:读取分号之前的命令,标签前后的空白都会忽略。在下面的示例中,标签是 x。
第一个示例在 GNU sed 中可以正常执行。第二个是可移植的等价物。
$ seq 3 | sed '1/b x ; s/^/=/ ; :x ; 3d'
1
=2
$ seq 3 | sed -e '1/bx' -e 's/^/=/' -e ':x' -e '3d'
1
=2
以下命令不能使用分号分隔,需要换行:
a, c, i
a、c、i 命令后面的所有字符都当成是追加、更改、插入的文本。这时使用分号会导致您不希望的结果:
$ seq 2 | sed '1aHello ; 2d'
1
Hello ; 2d
2
$ seq 2 | sed '1aHello
> 2d'
1
Hello
# (comment)
从 # 开始至换行为止之间的所有字符都当成注释。
$ seq 3 | sed '# this is a comment ; 2d'
1
2
3
$ seq 3 | sed '# thist is a comment
> 2d'
1
3
r, R, w, W
r、R、w、W 命令会把该命令之后的所有字符都解析成文件名。如果发现有空格、注释或分号包含在文件名中,将导致意外结果。
$ seq 2 | sed '1w hello.txt ; 2d'
1
2
$ ls -log
total 4
-rw-r--r--. 1 2 Nov 29 08:41 'hello.txt ; 2d'
$ cat hello.txt\ \;\ 2d
1
e
e 命令会把该命令之后、行尾之前的所有字符都送到 shell 当成命令执行。如果发现空格、注释或分号包含在文件名中,会导致意外结果:
$ echo a | sed '1e touch foo#bar'
a
$ ls -1
foo#bar
$ echo a | sed '1e touch foo ; s/a/b/'
sh: s/a/b/: No such file or directory
a
s///[we]
在成功替换后,w 命令会把替换结果写到指定的文件中,而 e 命令会把替换结果当成 shell 命令执行。如果发现空格、注释或分号包含在文件名中,会导致意外结果:
$ echo a | sed 's/a/b/w1.txt#foo'
b
$ls -1
1.txt#foo
地址条件决定了 sed 命令将会在哪些行上执行。例如,下面的命令只在第 144 行中出现的第一个 ‘hello’ 替换成 ‘world’:
sed '144s/hello/world/' input.txt > output.txt
如果没有指定地址,那么替换命令将在所有行上执行。例如,下面的命令将对输入文件中的所有行中出现的第一个 ‘hello’ 替换成 ‘world’:
sed 's/hello/world' input.txt > output.txt
地址可以使用正则表达式,以便根据行内容而不是行号来匹配行。例如,下面的命令只在包含 ‘apple’ 的行上出现的第一个 ‘hello’ 替换成 ‘world’:
sed '/apple/s/hello/world/' input.txt > output.txt
地址范围由逗号分隔的两个地址指定。地址可以是数字(行号)、正则表达式或者两者的组合。例如,下面的命令只在第 4 行到第 17 行(含 4 和 7)之间的行上出现的第一个 ‘hello’ 替换成 ‘world’:
sed '4,17s/hello/world/' input.txt > output.txt
在命令字符之后,地址声明之后添加一个 ! 字符对匹配状态取反。那就是说,如果 ! 字符跟在一个地址或者一个地址范围之后,那么,只有没有被匹配的地址行将会被选择。例如,下面的命令只在不含包 ‘apple’ 的行上出现的第一个 ‘hello’ 替换成 ‘world’:
sed '/apple/!s/hello/world' input.txt > output.txt
sed 脚本中的地址可以采用以下任何形式:
number
指定行号,只会匹配输入的哪一行。注意:除非指定了 -i 或 -s 选项,否则 sed 会对所有输入文件中的行进行连续计数。
$
与输入的最后的文件的最后一行匹配,或者在指定了 -i 或 -s 选项时,将与每个文件的最后一行匹配。
first~step
(开始~步长) 这时 GNU 扩展。匹配从第 first 行开始的每个步长行。可以使用 0~2 匹配偶数行,使用 1~2 匹配奇数行。
$ seq 10 | sed '0~4p'
4
8
$ seq 10 | sed -n '1~3p'
1
4
7
10
GNU sed 支持以下正则表达式地址。
/regexp/
选择所有匹配正则表达式 regexp 的行。如果正则表达式中包含任何斜杆 ( / ) 字符,其中的每一个斜杆必须使用反斜杠 ( \ ) 进行转义。
下面的命令打印在 ‘/etc/passwd’ 文件中以 ‘bash’ 结尾的所有行:
$ sed -n '/bash$/p' /etc/passwd
当然还有其他方法可以实现这个功能,例如:
$ grep 'bash$' /etc/passwd
$ awk -F: '$7 == "/bin/bash"' /etc/passwd
\%regexp%
其中的 % 字符可以由其他任意单个字符替代。如果正则表达式中包含大量的斜杆时,比较好,可以避免使用大量的反斜杠进行转义。
以下命令时等价的。都是打印以 /home/alice/documents/ 开头的所有行:
$ sed -n '/^\/home\/alice\/documents\//p'
$ sed -n '\%^/home/alice/documents/%p'
/regexp/I, \%regexp%I
这是 GNU 扩展,修饰符 I 使正则表达式能以不区分大小写的方式进行匹配。由于 i 已经用于插入命令,因此使用大写的 I。
在以下示例中,/B/I 是一个文本匹配地址,使用不区分大小写匹配,d 是删除命令:
$ printf "%s\n" a B c | sed '/b/Id'
a
c
而使用小写 i 命令时,其功能时插入命令,而 ‘d’ 表示要插入的值。
$ printf "%s\n" a b c | sed '/b/id'
a
d
b
c
/regexp/M, \%regexp%M
这时 GNU sed 的扩展。修饰符 M 指示 sed 可以在多行模式下匹配正则表达式。
地址决定是否处理当前模式空间的内容,但是,如果模式空间内容发生了变化(例如,使用了替换命令),那么,后面的正则表达式将会匹配变化后的文本。例如,下面的示例,禁用了自动打印功能,s/2/x/ 替换命令改变了包含的 ‘2’ 到 ‘x’,而 /[0-9]/p 命令匹配包含数字的行,然后打印出来。
$ seq 3 | sed -n 's/2/x/ ; /[0-9]/p'
1
3
通过逗号分隔两个地址来指定地址范围。地址范围匹配从第一个地址匹配的位置开始的行,直到第二个地址匹配为止(含前后两个地址):
$ seq 10 | sed -n '4,6p'
4
5
6
如果第二个地址是正则表达式,那么检查结束匹配将从与第一个地址匹配行开始向后搜索,范围将始终跨越至少两行(除非输入流结束):
$ seq 10 | sed -n '4, /[0-9]/p'
4
5
如果第二个地址小于或等于第一个地址匹配行的数字,则只匹配第一行:
$ seq 10 | sed -n '4, 1p'
4
GNU sed 支持一些特殊的地址范围形式。
0, /regexp/
在地址规范中可以使用行号 0,例如 ‘0, /regexp/’,表示试图在第一行进行匹配。而 ‘/1, /regexp/’ 表示试图从第二行开始搜索。
以下示例演示从地址 1 开始与从地址 0 开始的区别:
$ seq 10 | sed -n '1, /[0-9]/p'
1
2
$ seq 10 | sed -n '0, /[0-9]/p'
1
addr1, +N
匹配第 addr1 行及其后面的 N 行:
$ seq 10 | sed -n '6, +2p'
6
7
8
addr1, ~N
匹配第 addr1 行及其后面的行,直到行号是 N 的倍数。例如,以下命令从第 6 行开始,直到是 4 的倍数行,即第 8 行:
$ seq 10 | sed -n '6, ~4p'
6
7
8
$ seq 100 | sed -n '10, ~13p'
10
11
12
13
正则表达式(regexp)许多程序都在使用,例如,grep,vim等。sed 中的正则表达式指定在两个斜杠之间。其威力来自于可以使用替代项和具有重复的能力。有些特殊字符在模式中并不代表自身,而是有其它含义。
例如,正则表达式中的字符 ^ 与行首位置匹配。字符 . 匹配任意单个字符。
$ printf "%s\n" abode bad bed bit bid byte body | sed -n '/^b.d/p'
bad
bed
bid
body
基础正则表达式(BRE)和扩展正则表达式(ERE)是模式的两种语法变体。sed 默认使用的 BRE,与 grep 类似。使用选项 -E, -r, --regexp-extended 其中之一启用 ERE 语法。
在 GNU sed 中,两种语法之间的唯一区别是以下特殊字符的行为不同:?、+、(、)、{、}、|。
对于 BRE 语法,这些字符没有特殊意义,除非有前缀的反斜杠才具有特殊意义;而对于 ERE 语法,这些字符刚好相反,即它们是特殊的,除非有前缀的反斜杠,才变成其字面意义上的字符本身。
下面是 sed 中使用的正则表达式语法简要描述。
| 字符 |
|
|---|---|
| char | 单个普通字符只能匹配自己。 |
| * | 匹配 * 前面零个或多个匹配实例的序列,匹配实例必须是普通单个字符、反斜杠转义的特殊字符、.、分组、或者方括号表达式。 |
| . | 匹配任何单个字符,包括换行符。但是在多行模式下不匹配换行符! |
| ^ | 匹配模式空间中最开始的位置。 |
| $ | 与 ^ 类似,它引用模式空间的结尾位置。 |
| [list], [^list] | [list] 表示列表中的任意单个字符。例如,[aeiou] 匹配所有元音。而 [^list] 则相反。 |
| \+ | 表示一个或多个前面的实例序列,等价于 \{1, \}。 |
| \? | 表示零个或者一个前面实例序列,等价于 \{0, 1\}。 |
| \{N\} | 表示刚好 N 个前面实例序列。 |
| \{N, M\} | 表示 N 个及其以上至 M 个前面实例序列。 |
| \{N, \} | 表示 N 个及其以上各前面实例序列。 |
小括号包围的表达式作为子表达式进行分组。可以应用于后缀操作,例如,\(abcd\)* 表示零次或多次重复的 ‘abcd’,如 ‘abcdabcd’;也可用于反向引用。 | |
| regexp1|regexp2 | 表示要么匹配 regexp1,要么匹配 regexp2。 |
| \digit | 表示引用第 digit 个前面使用小括号包围的子表达式所形成的分组,称为反向引用。 |
| \n | 匹配换行符。 |
| \char | 如果 char 单个字符本身是特殊字符,那么,这种转义后代表字面意义字符自身。注意,\t 在大多数 sed 实现版本中并不代表制表符。 |
注意:正则表达式的匹配时是贪婪的,即尽可能长地匹配长度。
方括号表达式使用方括号包围的列表。表示可以匹配该列表中的任意单个字符;如果列表中的第一个字符是 ^,则表示相反的含义,即会匹配不在该列表中的任何字符。例如,以下示例,把 ‘gray’ 或 ‘grey’ 替换成 ‘blue’:
$ sed 's/gr[ae]y/blue/'
在方括号中使用连字符 (-) 分隔两个字符组成范围表达式。例如,[a-d] 等同于 [abcd]。
最后,sed 已经使用方括号预定义了一些字符类。例如:
$ echo 1 | sed 's/[[:digit:]]/x/'
x
以下是预定义的字符类:
| 字符类 |
|
|---|---|
| [:alnum:] | (alphanumeric) 字符数字类:[:alpha:] 和 [:digit:]。 |
| [:alpha:] | (alphabetic) 字符类:[:lower:] 和 [:upper:]。 |
| [:blank:] | 空字符:空格和 tab 制表符。 |
| [:cntrl:] | (control) 控制字符类。 |
| [:digit:] | 数字类,等价于 [0-9]。 |
| [:graph:] | 图形化字符类:[:alpha:] 和 [:punct:]。 |
| [:lower:] | 小写字符,等价于 [a-z]。 |
| [:print:] | 可打印的字符:[:alnum:], [:punct:] 和 空白字符。 |
| [:punct:] | (punctuation) 标点符号。 |
| [:space:] | 空白字符:在 C 语言环境中,tab 制表符、换行符、垂直 tab 制表符、换页、回车和空格。包含 [:blank:]。 |
| [:upper:] | 大写字符:[A-Z]。 |
| [:xdigit:] | (hexadecimal) 十六进制数字。 |
还有一些细节与 bash 中一致。例如,]、-、^ 在方括号中不同位置具有不同的含义。在方括号中,$、[、\ 不再具有特殊含义。
以下字符序列具有特殊的含义,在基础和扩展正则表达式中语法一致。
\w 匹配任意组成“单词”的字符。组成“单词”的字符是任意字母、数字或下划线(_)。
$ echo "abc %-= def." | sed 's/\w/X/g'
XXX %-= XXX.
\W 匹配任意“非单词”字符。
$ echo "abc %-= def." | sed 's/\W/X/g'
abcXXXXXdefX
\b 匹配单词边界。
$ echo "abc %-= def." | sed 's/\b/X/g'
XabcX %-= XdefX.
\B 匹配单词边界以外的任何位置。
echo "abc %-= def." | sed 's/\B/X/g'
aXbXc X%X-X=X dXeXf.X
\s 匹配空白字符(空格和各种tab制表符)。内嵌在模式空间和保持空间的换行符也会匹配。
echo "abc %-= def." | sed 's/\s/X/g'
abcX%-=Xdef.
\S 匹配非空白字符。
echo "abc %-= def." | sed 's/\S/X/g'
XXX XXX XXXX
\< 匹配一个单词的开始位置。
echo "abc %-= def." | sed 's/\
Xabc %-= Xdef.
\> 匹配一个单词的结束位置。
echo "abc %-= def." | sed 's/\>/X/g'
abcX %-= defX.
\‘ 只匹配模式空间的开始位置。这与多行模式的 '^' 不同。
比较以下两个例子:
printf "a\nb\nc\n" | sed 'N;N;s/^/X/gm'
Xa
Xb
Xc
# 译者:反斜杠后面的字符是通过按键 1 左边的那个键实现的。
printf "a\nb\nc\n" | sed 'N;N;s/\`/X/gm'
Xa
b
c
\’ 只匹配模式空间的结束位置。这与多行模式的 '$' 不同。这个字符不知道是哪个。
printf "a\nb\nc\n" | sed 'N;N;s/$/X/gm'
aX
bX
cX
# 实验时发现有趣的现象
printf "aAAA\nb\nc\n" | sed 'N;N;s/\|/X/gm'
(printf "aAAA\nb\nc\n" | sed -E 'N;N;s/|/X/gm')一样的结果:
XaXAXAXAX
XbX
XcX