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