「sed(1)」- 文本过滤和转化的流编辑器

常用命令

字符串替换:

sed -i 's/bar/baz/gI' /path/to/file

# I:忽略大小写。如果希望大小写敏感,则使用 i 或者忽略。

向文件末尾追加文本:

sed -i '$a some-string' /path/to/file

删除文件的第一行内容:

sed '1d' /path/to/file

替换中文字符,但鉴于 sed 并不支持 Unicode 转义,所以需要特殊处理:

# printf "\u4e00-\u9fa5"                                                        # 中文的 Unicode 范围,并不包含标点符号
一-龥

# sed -E 's/[一-龥]//g' /path/to/file.txt

根据行号范围进行替换:

sed '19,33s/google/facebook/g' file

语法格式

sed [OPTION]... {script-only-if-no-other-script} [input-file]...

命令描述

sed,stream editor,是一个非交互式、命令行式的文本编辑器,用于过滤和转化文本内容。

sed,以‘行’为单位,从文件中读取一行,然后使用给出的命令处理读取到的行。sed可以从文件中读取编辑的内容,也可以从管道中(pipeline)中读取文本内容,这也是sed区别与其他编辑器的一个地方。

如果没有特殊说明,本文的sed版本为4.2.2。

sed是如何工作的?

sed 维护了两个数据缓冲:

  • 活动的模式空间(the active pattern space)。
  • 辅助的保持空间(the auxiliary hold space)。

这两个空间初始化的时候都为空。

sed对文本循环执行如下操作:

(1). 读取一行,移除任何尾随的换行符号,然后放入pattern space里。

(2). 执行命令;每一条命令都有一个关联的地址:地址是一种条件代码,只有条件匹配的时候才执行命令。

(3). 当脚本(命令)执行结束时,将pattern space的内容打印到输出流,如果之前移出的结尾换行符,则添加上。

(4). 然后回到(1)开始处理下一行。

除非使用了特殊命令(如 D,下面会介绍D命令),否则在进入下一轮循环的时候,pattern space会被删除。

另一方面,hold space会保存它的数据(命令 ‘h’, ‘H’, ‘x’, ‘g’, ‘G’ 用于在两个buffer之间移动数据)。

命令行选项及含义

-n, –quiet, –silent
默认情况下,在处理完每一行之后,sed输出pattern space的内容。这个选项可以关闭默认输出,这时只有使用p命令才会输出。

-e script, –expression=script
向要执行的命令中加入新的命令。

-f script-file, –file=script-file
指定包含sed命令的脚本文件。就是将 {script-only-if-no-other-script} 的内容写入文件,然后通过 -f 指定文件。

–follow-symlinks
此选项仅在支持符号链接的平台上可用,并且只有和 -i 一起使用时才有效。在这种情况下,如果命令行上指定的文件是一个符号链接,sed将编辑链接指定的文件。默认,打破符号链接,因此链接指向的文件不会被修改。

-i[SUFFIX], –in-place[=SUFFIX]
直接在原文件上修改。默认 sed 将修改之后的文本内容直接输出,-i 选项将会在原文件上直接修改。如果指定了 SUFFIX,会创建后缀为 SUFFIX 的原文件备份。

-l N, –line-length=N
这个选项一般和 l 命令一起用,指定自动换行的长度。0表示不换行。如果没有指定则默认是70。

–posix
GNU/sed包括了几个对POSIX/sed的扩展。

为了脚本可移植,此选项禁用GNU/sed所有扩展,包括附加命令。大部分的扩展程序符合POSIX规范,但有一些(比如说N命令)的行为实际上违反了标准。

如果你想禁用只是后者的延伸,你可以设置POSIXLY_CORRECT为一个非空值。

-E/-r, –regexp-extended
使用「扩展正则表达式(ERE)」,而不是「基本正则表达式(BRE)」。egrep之类的接受扩展正则表达式,因为反斜杠用的少,所以可以很清楚。以前-E是GNU的扩展,但-E后来被加到POSIX标准,所以为了便携性使用-E。GNU sed将-E视作非法选项,而*BSD接受这个选项,但脚本中使用-E可能无法移植到其他旧系统。

-s, –separate
将命令行中的「多个文件」视作独立的文件。默认,sed将命令行中的多个文件视作一个很长的文件流(视作一个文件)来处理。使用该选项后:

  • 地址范围(如“/ ABC /,/,/”)是不允许跨越多个文件。
  • 行号是相对于每个文件的开始,$ 指每个文件的最后一行。
  • 调用R命令时,回到每个文件的开始。

-u, –unbuffered
从输入文件加载最小量的数据,并更频繁地刷新输出缓冲区。比如,和 `tail -f’ 一起使用的时候,如果想尽快的看见sed处理后输出,就可以使用 -u 选项。

–sandbox
在沙箱模式中运行。

-z, –null-data
将输入视作行的集合,每行以NUL结尾。
这个选项可以和 `sort -z’ 或者 `find -print0′ 一起使用,用于处理任意的文件名。

其他选项

–help
显示帮助信息并退出。

–version
显示帮助版本并退出。

附加说明

如果没有 -e, –expression, -f, –file 选项,sed 后面的第一个参数会被当作脚本命令,剩下的参数会被当作输入文件,如果没有指定文件,则从标准输入中读取。

脚本及命令

脚本语法简介

这一部分介绍SED命令语法,就是sed命令后面的 {script-only-if-no-other-script} 部分,一般这一部分也被称作SED脚本。

命令格式:

[addr]X[options],各部分含义如下:

  • X 是 sed 命令。
  • [addr] 是可选的行地址。如果指定了[addr] 只有在匹配的行才会执行命令。[addr] 可以是数字、正则、或者是范围。
  • [options] 是某些sed命令,后面会介绍。

多个命令:

如果有多个命令,命令之间可以:

  • 用分号(;)分割
  • 换行符(ASCII 10)
  • 使用 -e、-f 选项。

示例如下:

#!/bin/sh

# demo01:命令使用分号分割。
sed '/^foo/d ; s/hello/world/' input.txt > output.txt

# demo02:使用多个-e命令行选项。
sed -e '/^foo/d' -e 's/hello/world/' input.txt > output.txt

# demo03:使用-f选项指定脚本文件。
echo '/^foo/d' > script.sed
echo 's/hello/world/' >> script.sed
sed -f script.sed input.txt > output.txt

# demo04:-e 与 -f 混用。
echo 's/hello/world/' > script2.sed
sed -e '/^foo/d' -f script2.sed input.txt > output.txt

注意:由于 a、c、i 的语法特殊,后面不能用分号(;)为分隔符,因此应该使用换行符(ASCII 10)或者放在脚本(或脚本文件)的最后。命令也可以先于不重要的空白字符前。

从这里开始向下,就是介绍上面的这些命令了,还有这些命令的用法了,介绍的过程中会有很多的如下示例参考。

命令概览

The following commands are supported in GNU sed. Some are standard POSIX commands, while other are GNU extensions. Details and examples for each command are in the following sections. (Mnemonics) are shown in parentheses.

a\
text
a text

在一行之后追加text。

b label

无条件分支。如果省略标签,下一个周期开始。分支到脚本结束。

c\
text
c text

使用text替换行。

d

删除pattern space。然后开始下一个周期。

D

如果pattern space里没有换行符,删除pattern space里的内容,然后开始下一个周期。这和 d 命令的作用一样。

如果pattern space里面有换行符,这时候D命令就有不同的表现了:它会删除pattern space的文本直到第一个换行符号,并使用剩下的pattern space从新开始周期,不再读取输入文件。

e

执行在pattern space中找到的命令,然后使用输出替换pattern space。尾随的换行符号会被抑制。

e command

执行command然后将输出发送到输出流。命令可以分为多行,但是上一个要以 `\’ 结尾。

F

打印当前输入文件的文件名,会尾随一个换行符。

g

使用hold space的内容替换pattern space里面的内容。

G

在pattern space里面的内容后追加一个换行符,然后将hold space的内容追加到pattern space。

h

用pattern space的内容替换hold space里面的内容。

H

在hold space里面的内容后追加一个换行符,然后将pattern space的内容追加到pattern space。

i\
text

i text

在行之前插入 text。

l

以明确的形式打印pattern space里的内容。

n

如果没有禁用自动打印,则打印pattern space,然后使用输入的下一行替换pattern space。如果没有输入了,sed会退出,不再处理任何命令。

N

向pattern space添加一个换行符,然后追加输入的下一行到到pattern space。如果没有输入了,sed退出不再处理任何命令。

p

打印pattern space.

P

打印pattern space,直到遇到第一个换行符。

q[exit-code]

退出,不再处理任何命令。

Q[exit-code]

和 q 命令类似,返回自定义状态码。但是不会打印pattern space里的内容。

r filename

追加文本,文本是从filename指定的文件中读取的。

R filename

追加一行,该行是从filename中读取的。每执行一个周期,就会从filename中读取一行。这个命令属于GNU的扩展。

s/regexp/replacement/[flags]

针对pattern space,将匹配regexp的行,用replacement替换。

t label

如果上一个输入行替换或者条件分支被采取,成功的时候,进行分支标记。

在标签省略的情况下,会下一个周期开始。

T label

如果上一个输入行替换或者条件分支被采取,失败的时候,进行分支标记。

在标签省略的情况下,会下一个周期开始。这个命令属于GNU扩展。

v [version]

如果GNU扩展不支持或者请求的版本不可用,会使 sed 失败。

w filename

将pattern space的内容写入文件。

W filename

将pattern space的内容写入文件,但是只有第一个换行符之前的内容。

x

交换pattern space与hold space的内容。

y/src/dst/

作用与pattern space,用于字母转换。用在dst中存储的字符匹配任何src中的。

z

清空pattern space里的内容。

#

行注释。

{ cmd ; cmd … }

将多个命令组在一起。

=

打印当前的行号(以换行符结尾)。

: label

为分支命令(b、t、T)指出lable的位置。

s命令

这一部分主要是演示 s 命令,s 命令是 sed 中最常用的命令,主要作用就是用于替换。

s 命令的语法:s/regexp/replacement/flags。该命令会用 regexp 到模式空间中匹配,然后将匹配的内容用replacement替换:

  • regexp 的语法在 《Regular Expression Addresses》 章节介绍。
  • replacement 可以包含 \n(n >= 1 && n<=9)引用,n指的是匹配内容的第n部分(\1代表匹配内容中的第一部分。。。这个地方你需要懂一下正则里面的`()’)。

可以使用转义字符 & ,代表全部匹配到的。

命令中除了可以用 / 分割各个部分,也可以用其他的当个的单个字符。如果要在 regexp 或 replacement 中使用 / ,需要转义。

GNU增加了几个扩展选项(\L, \l, \U, \u, \E)用在 s 命令中:

  • \L: 在遇到 \U 或 \E 之前,将 replacement 都转为小写。
  • \l: 将下一个字符转为小写。
  • \U:在遇到 \L 或 \E 之前,将 replacement 都转为大写。
  • \u: 将下一个字符转为大写。
  • \E: 停止\L与\U大小写转换。

如果使用标识(flag) g,则表示全局替换,默情况下,是只替换第一个匹配到的,然后剩下的即使是匹配的,也不会再去处理。

#!/bin/sh

# Demo1:
echo 'a-b-' | sed 's/\(b\?\)-/x\u\1/g'
# 输出:axxB
# 解释:
# 第一个匹配到的是‘-’,然后将其替换为x。
# 第二个匹配到的是‘b-’,将其替换为x,\u 代表了下一个字符大写,这里的下一个就是指 \1,而 \1 指的是匹配内容的第一部分,这里的第一部分是b。

# Demo2:
echo 'a-b-' | sed 's/\(b\?\)-/\u\1x/g'
# 输出:aXBx
# 解释:
# 第一个匹配到的是‘-’,然后将其替换为x。但是如果\n代指的部分是空的,那么\u或者\l会作用与剩下的部分,这也就是为什么第一个X是大写的。
# 第二个匹配到的是‘b-’。replacement分为两部分,第一部分为\u\1,第二部分为x。\u 代表了下一个字符大写,这里的下一个就是指 \1,而 \1 指的是匹配内容的第一部分,这里的第一部分是b,所以\u\1'产生'了B,然后有一个x.

# Demo3:在Demo2中,如果\1代指的部分是空的,那么\u会作用与剩下的部分,即影响到x。\E可以取消这种传递。
# 类比Demo2。
echo 'a-b-' | sed 's/\(b\?\)-/\u\1\Ex/g'
# 输出:axBx

s 命令中的flag可以省略,下面是可用flag:

  • g: 将所有匹配内容都进行替换。默认只有第一个。
  • number: 指定要替换的第几个。比如number是5,表示值替换第五个匹配到的。注意:在POSIX规范中,并没有定义如果同时使用了g和number该如何处理。GNU/sed中的行为:忽略第number之前的匹配内容,然后从第number个开始,替换所有匹配的。
  • p: 如果发生了替换,则打印新的模式空间的内容。注意:如果同时使了p和e,顺序不同,结果不同。ep为先替换后打印,但是另外一种方式对与调试很有用。出于调试考虑,当前版本的GNU/sed特别地解释p选项的存在既在e前又在e后,在执行之前和之后打印模式空间,虽然一般s命令的flag效果只显示一次。
  • w filename: 如果发生了替换,将结果写入指定的文件。支持两种特殊的文件:/dev/stderr、/dev/stdout,这个支持属于GNU的扩展。
  • e: 该命令允许将shell命令的输出输出到模式空间。如果发生了替换,在模式空间中找到的命令会被执行,然后使用命令的输出替换模式空间的内容。已知结尾的换行符;如果命令包含NUL字符,结果为undefined。这是GNU/sed的扩展。
  • I/i: I 修饰符是GNU扩展,在进行正则匹配的时候,大小写敏感。
  • M/m:M 修饰符是GNU扩展,使得正则匹配工作在多行(multi-line)模式下。此时 ^ 代表了换行符后的空字符串,$ 代表了换行符前的空字符串。而 \` 代表了buffer的开始,而 \’ 代表了buffer的结束。另外,句号(.)在多行模式中不再匹配换行符。

命令概览:常用

这一章节介绍 sed 中比较常用的命令。

#
注释。行注释,作用结束与行尾部。如果你担心便携性,要知道在某些sed的实现中只支持单行注释,而且行的第一个字符必须是#。
注意:如果sed脚本的前两个字符是 #n,那么 -n 选项是强制的,就是说会禁用自动打印。如果你的sed脚本#n为第一行开始的并且你不想要这种行为,那么确保使用大写的N或者在n前加入空格。

q [exit-code]
退出sed不再执行任何命令或者输入。该命令只能接受单个地址。如果没有使用-n(禁止自动打印),该命令会自动打印模式空间里的内容。返回状态码的这个能力是属于GNU的扩展,Q命令可以不打印模式空间然后安静的退出。如下示例:

#!/bin/sh

# Demo01: 处理到第二行的时候立即退出。
seq 3 | sed 2q

# 输出:
# 1
# 2
#

#
# 该命令并没有指出退出的状态码。
#

d
删除模式空间的内容,然后开始下一个周期。如下示例:

#!/bin/sh

# Demo01: 当地址(行号)为2时,删除模式空间的内容,然后处理下一行。
seq 3 | sed 2d

# 输出:
# 1
# 3

p
打印模式空间的内容。这个命令通常与 -n 命令一起使用。如下:

#!/bin/sh

# Demo01:只打印第二行。
seq 3 | sed -n 2p

# 输出:
# 2

n
如果禁用了自动打印(使用了-n),则打印模式空间的内容,然后读取下一行替换模式空间。如果没有下一行了,sed不再处理任何命令,然后退出。这个命令在‘跳行’(skip line)的时候非常有用。如下示例:

#!/bin/sh

# Demo01: 跳过两行,处理第三行
seq 6 | sed 'n;n;s/./x/'

# 输出:
# 1
# 2
# x
# 4
# 5
# x

# Demo02: 下面是一种等价的写法,这种写法属于GNU的扩展。
seq 6 | sed '0~3s/./x/'

# 输出:
# 1
# 2
# x
# 4
# 5
# x

{ commands }
将命令做为一组来执行。如果你想在一个地址(行)上执行多条命令,{}会很有用。如下示例:

#!/bin/sh

# Demo01: 在第二行上执行两条命令 s/2/X/ 和 p
seq 3 | sed -n '2{s/2/X/ ; p}'

# 输出:
# X

命令概览:不常用

这一部分介绍 sed 中不常用的命令, 但是这些不常用的命令有时在构建小的sed脚本时很有用. 下面一一介绍:

y/source-chars/dest-chars/
将dest-chars所列出的字符, 用source-chars中的字符替换. 看如下示例:

#!/bin/sh

# Demo01: 将abcdefghij转化为0123456789
echo hello world | sed 'y/abcdefghij/0123456789/'
#
# 输出:
# 74llo worl3

souoce-chars 和 dest-chars 中可以使用 `\’, `/’, `new-line'(换行), 但是要使用 \ 转义. souoce-chars 和 dest-chars 中的字符(转义字符`\’不算, 比如: `\/’ 算一个字符)个数要相同.

实际上tr命令也有这个功能.

a text
在行后追加 text, 这个是一个GNU扩展。如下示例:

#!/bin/sh

# Demo01: 在第2行后追加hello. .
seq 3 | sed '2a hello'
# 输出:
# 1
# 2
# hello
# 3

参数text前的空白字符会被忽略(如示例中“hello”前的空格)。被添加的文本会读取到行尾.

a\
text
在行后追加 text.

#!/bin/sh

seq 3 | sed '2a\
hello'
# 输出:
# 1
# 2
# hello
# 3

该命令将在当前周期结束的时候输出 text, 或者读取下一行的时候.

做为GNU扩展的sed, 该命令可以使用两个地址, 如下示例:

#!/bin/sh

seq 3 | sed '2a\
hello\
world
3s/./X/'

# 输出:
# 1
# 2
# hello
# world
# X

做为GNU扩展的sed还有一个功能, 命令和text可以分割到两个-e选项中, 如下示例:

#!/bin/sh

seq 3 | sed -e '2a\' -e hello
# 输出:
# 1
# 2
# hello
# 3

# 这样可以用于创建动态的替换内容, Demo:
sed -e '2a\' -e "$VAR"

i text

在行前插入text. 这个是GNU对标准i命令的扩展. 如下示例:

#!/bin/sh

seq 3 | sed '2i hello'
# 输出:
# 1
# hello
# 2
#

同a命令, text 前的空白字符会被忽略. 要追加的text会读取到行尾.

i\
text

在行前插入text. 如下示例:

#!/bin/sh

# Demo: 在第二行前插入 hello.
seq 3 | sed '2i\
hello'
# 输出:
# 1
# hello
# 2
# 3

使用`\’要转义, 即`\\’表示 `\’. 做为GNU扩展, 该命令接受两个地址. 如下示例:

#!/bin/sh

seq 3 | sed '2i\
hello\
world
s/./X/'
# 输出:
# X
# hello
# world
# X
# X

做为GUN的扩展, 命令和text可以分割到两个-e选项中, 如下示例:

#!/bin/sh

seq 3 | sed -e '2i\' -e hello
# 输出:
# 1
# hello
# 2
# 3

# 这样可以用于创建动态的替换内容, Demo:
sed -e '2i\' -e "$VAR"

c text

使用text替换指定行. 这个GNU对标准c命令的扩展. 如下示例:

#!/bin/sh

# Demo: 将2-9行用hello替换.
seq 10 | sed '2,9c hello'
# 1
# hello
# 10

同a命令, text 前的空白字符会被忽略. 用作替换的text会读取到行尾.

c\
text

使用text替换指定行或行范围. 如下示例:

#!/bin/sh

seq 5 | sed '2,4c\
hello\
world'
# 输出
# 1
# hello
# world
# 5

如果没有使用地址, 所有的行都会被替换.

命令执行结束的时候,会删除模式空间的内容,然后开启一个新的周期. 如下示例:

#!/bin/sh

# Demo: 命令执行结束的时候,会删除模式空间的内容,然后开启一个新的周期
# 这就是为什么 hello 没有被替换为X的原因.
seq 3 | sed '2c\
hello
s/./X/'
# 输出:
# X
# hello
# X

做为GUN的扩展, 命令和text可以分割到两个-e选项中, 如下示例:

#!/bin/sh

seq 3 | sed -e '2c\' -e hello
# 输出:
# 1
# hello
# 3

# 这样可以用于创建动态的替换内容, Demo:
sed -e '2c\' -e "$VAR"

=

输出当前的行号(行号显示在独立的行中). 如下示例:

#!/bin/sh

printf '%s\n' aaa bbb ccc | sed =
# 输出:
# 1
# aaa
# 2
# bbb
# 3
# ccc

做为GNU的扩展, 该命令支持两个地址.

l n
使用明确的形式打印模式空间的内容:

  • 不可打印的字符(及`\’字符)使用C风格的转义形式打印.
  • 过于长的行, 会打印到不同的行, 在行尾显示 `\’ 来表示换行.
  • 行的最终结尾使用$符号标记.

参数n是GNU的扩展。表示行的长度, 超过这个长度就会换行,通过在行尾显示\来表示换行。参数n0表示不换行。如果忽略n会使用默认值。默认值是70

r filename
从文件中读取文本。如下示例:

#!/bin/sh

seq 3 | sed '2r/etc/hostname'
# 输出:
# 1
# 2
# Linux
# 3

读取文件内容, 然后在下个周期开始之前或者当前周期结束的时候, 插入文件内容。

注意:如果文件无法读取, 则将视为读取了一个空文件, 不会有任何的错误提示。

做为GUN的扩展, 参数filename支持/dev/stdin作为文件名。同时也支持两个地址, 文件的内容会被插入指定地址。

w filename
将模式空间的内容写入文件. 做为GNU的扩展, 支持 /dev/stderr 和 /dev/stdout 做为文件名.

在第一次写入文件的时候, 会先清空(或创建)文件;所有引用了相同文件名的w命令(包括s命令的flag w)输出的时候不会重新打开或者关闭文件.

D
如果模式空间没有换行符, 则启动一个普通的新周期,就像使用了d命令.如果模式空间中有换行符, D命令的表现就会有所不同, 它会删除模式空间中第一个换行符前的内容, 然后使用剩下模式空间(不再从文件中读取文本)开启一个新的周期.

N
向模式空间中追加一个换行符,然后读取下一行的内容,并添加到模式空间中。如果读取不到下一行(没有内容了), sed会立即退出,不再执行后续的任何命令。查看如下示例:

#!/bin/sh

printf "a\nb\nc\nd" | sed  'N;N;N;p' -n
# 输出内容:
# a
# b
# c
# d

printf "a\nb\nc\nd" | sed  'N;N;N;N;p' -n
# 不会有任何输出。

如果使用了-z选项, 会向模式空间中追加一个NUL字符,而不是换行符。

当在文件最后一行的时候,如果执行N命令,是无法读取到下一行的,大多数版本的sed退出而不打印任何内容。而GNU sed在退出之前打印模式空间,除非指定了-n选项。默认情况下:

#!/bin/sh

seq 3 | sed N
# 输出:
# 1
# 2
# 3

可以使用–posix来禁用这个GNU扩展:

#!/bin/sh

seq 3 | sed --posix N
# 输出:
# 1
# 2

P
打印模式空间的内容, 直到第一个换行符。

h
使用模式空间的内容替换hold space的内容.

H
向hold space的内容追加一个换行符, 然后将模式空间的内容追加到hold space.

g
使用hold space的内容替换模式空间的内容.

G
向模式空间的内容追加一个换行符, 然后将hold space的内容追加到模式空间.

x
交换模式空间和hold space的内容.

命令概览:高级

在大多数情况下,使用这些命令表示你可能更喜欢像awk或Perl这样的编程. 但偶尔也有人承诺坚持使用sed, 这些命令可以使得一个人能够编写相当复杂的脚本.

: label
不允许使用地址。

指定分支命令的标签位置。在所有其他方面,是一个空操作。

b label
无条件分支到标签。如果没有label则开始下一个周期。

t label
只有在自上次输入行被读取或条件分支获取以来已成功替换时,才能进行标记。

如果没有label则开始下一个周期。

在GNU版本的sed实现中特有的命令

SED由很多的实现,比如BSD、GNU等等。在不同的SED实现中,用法和命令的支持上也有所差别。下面的这些命令都属sed的GNU实现中特有的:

e [command]
该命令允许SEHLL命令[command]的输出重定向到模式空间中。

如果未指定[command],则会将模式空间中的内容当作命令来执行,然后使用命令的输出替换模式空间里的内容;尾随的换行被抑制。
如果指定了[command],会执行该[command],并将打印到stdout。

[command]可以在多行内,但是结尾要跟着反斜线(`\’)。如果命令包含了NUL字符,则结果为undefined。

注意:和 r 命令不同,该命令的输出会立即打印;而r命令是在周期结束的时候输出。

F
输出当前处理的文件名(尾随了一个换行符)。

Q [exit-code]

该命令指接受一个地址。和q命令一样可以自定义退出码,不同的是该命令不会打印模式空间的内容。

R filename

从文件中读取一行,然后在当前周期结束时或者读取下一行的时候,打印出读取到的行。如下示例:

#!/bin/sh

cat /etc/resolv.conf
# 输出:
# nameserver 192.168.103.1
# nameserver 192.168.103.1

echo "a
b
c
" | sed 'R/etc/resolv.conf'
# 输出:
# a
# nameserver 192.168.103.1
# b
# nameserver 192.168.103.1
# c
#

注意:如果文件不可读,不会有错误提示,会假装读到一个空文件。

和r命令一样,支持从/dev/stdin中读取内容。

T label

只有在自上次输入行被读取或条件分支被取出以来没有成功的替换时,才能进行标记

如果没有label则开始下一个周期。

v version

没什么实际的作用,唯一的作用就是不支持GNU sed扩展时,会使sed执行失败,只是因为其他版本的sed没有实现它。此外,可以指定脚本所需的sed版本 如:4.0.5。默认是4.0(第一次加入的v命令)

即使设置了POSIXLY_CORRECT,该命令也是有效的。

W filename

将模式空间中第一换行符号之前的内容写入文件。

在w命令下关于文件处理的一切在这里都适用。

z

清空模式空间的内容。和‘s/.*//’的作用是一样的, 但是更高效并且工作在输入流中存在无效多字节序列的情况下。‘.’在POSIX mandates中匹配不了那种序列,所以在大多数多字节语言环境中,没有可移植的方式来清除脚本中间的sed缓冲区(包括UTF-8语言环境).

多命令语法

本章节介绍命令的使用语法及多个命令一起使用的语法,以及一些要注意的事项。

如果同时需要多个命令:

  • 在sed脚本中,最常见的做法是使用换行。
  • 在命令行中,最常见的做法是使用 -e 命令。
  • 可以使用分号(;)来分割简单的命令。
  • {,},b,t,T,: 命令也可以用分号分割。
  • 在 b,t,T,: 命令中使用的Label会读取到分号结束。开头和结尾的空白字符会被忽略。关于分支和标签后面会介绍。
#!/bin/sh

# 使用换行
seq 6 | sed '1d
3d
5d'

# 使用 -e
seq 6 | sed -e 1d -e 3d -e 5d

# 使用分号(;)
seq 6 | sed '1d;3d;5d'

seq 4 | sed '{1d;3d}'

seq 6 | sed '{1d;3d};5d'

# 第二个命令是下面的等价兼容版本。
seq 3 | sed '/1/b x ; s/^/=/ ; :x ; 3d'
seq 3 | sed -e '/1/bx' -e 's/^/=/' -e ':x' -e '3d'
# 输出:
# 1
# =2

下面需要“换行符”的命令:

下面的这些命令有分号是不够的,他们需要一个换行符号

  • a,c,i (append/change/insert),如下示例:
#!/bin/sh

# Demo:" ; 2d"被当作了要输出的字符串。
seq 2 | sed '1aHello ; 2d'
# 输出:
# 1
# Hello ; 2d
# 2

# Demo02: 下面的命令是等价的,演示了如何避免这种情况:
seq 2 | sed '1aHello
2d'
seq 2 | sed -e 1aHello -e 2d
# 1
# Hello

# *** 注意:
# 在 a 命令后面直接跟要追加的text的写法是GNU sed的扩展。
# POSIX的标准写法是:
seq 2 | sed '1a\
Hello
2d'

  • # (注释命令),如下示例:
#!/bin/sh

seq 3 | sed '# this is a comment ; 2d'
# 输出
# 1
# 2
# 3

seq 3 | sed '# this is a comment
2d'
# 输出:
# 1
# 3

  • r,R,w,W (读写文件类的命令),如下示例:
#!/bin/sh

$ seq 2 | sed '1w hello.txt ; 2d'
# 1
# 2

ls -log
# total 4
# -rw-rw-r-- 1 2 Jan 23 23:03 hello.txt ; 2d

cat 'hello.txt ; 2d'
# 1

# 由此可见,整个‘hello.txt ; 2d’ 被当作的文件名,然后被写入了文件。

# 再次强调:如果文件无法读写,sed也不会有错误警告。

  • e (命令执行),如下示例:
#!/bin/sh

echo a | sed '1e touch foo#bar'
# a

ls -1
# foo#bar

echo a | sed '1e touch foo ; s/a/b/'
# sh: 1: s/a/b/: not found
# a

# 同样的e后面的都被当作命令了。

  • s///[we] (在s中使用了 e 或 w 标志)
#!/bin/sh

echo a | sed 's/a/b/w1.txt#foo'
# b

ls -1
# 1.txt#foo

地址:选择行

地址(Address),用于挑选出sed要处理的行。“地址“的作用是告诉 sed 要在那些行上执行命令:

  • 如果没有地址,sed 会处理所有的行。
  • 如果有一个地址,sed 会处理地址指定的行。
  • 如果有两个地址(用逗号分隔),sed 会处理两个地址间指定的行,包行结尾行。
  • 地址也可以是一个表达式。如:sed ‘/apple/s/hello/world/’ input.txt > output.txt
  • 地址后面可以使用感叹号(!),表示处理匹配的地址之外。

地址分为以下几种形式:

  • 使用数字挑选行;
  • 使用匹配文本挑选行;
  • 范围地址;

数字类地址(Numeric Addresses)

number
这是最常见的,通过一个number指定行号。

注意,sed会把多个输入文件当做一个文件来处理,所以,此时number就跨文件了。除非使用了-i或-s的选项来将文件视为独立的文件。

$
代表了文件的最后一行。

注意,sed会把所有的输入文件当做一个文件来处理,所以,此时$就跨文件了。除非使用了-i或-s的选项来将文件视为独立的文件。

first~step
匹配的行号为first + (n * step)。所以,表达式1~2就是奇数行;表达式0~2就是偶数行;等价于第50行。示例如下:

# seq 10 | sed -n ‘0~4p’

4

8

# seq 10 | sed -n ‘1~3p’

1

4

7

10

这是一个GNU sed的扩展功能。

文本匹配地址(Regexp Addresses)

GNU sed支持使用正则表达式作为地址,即地址是一个正则表达式。默认的正则表达式是基础正则表达式(Basic Regular Expression,BRE)。如果使用了-E或者-r选项,使用的是扩展正则表达式。

/regexp/
表示所有匹配regexp的行。如果在regexp中使用/,则必须要进行转义。打印/etc/passwd中所有以“bash”结尾的行:sed -n '/bash$/p' /etc/passwd

“//”(空正则表达式)重复上一个正则表达式匹配(如果将空的正则表达式传递给s命令,则它是相同的)。注意:正则表达式的修饰符将在编译正则表达式时进行求值,因此与空正则表达式一起指定它们是无效的。

\%regexp%
(百分号可以用其他字符替换)之所以有这种地址存在,纯粹就是为了解决转义字符太多的情况。示例如下(看一下第一种写法,然后对比一下第2、3中的写):

# sed -n ‘/^\/home\/alice\/documents\//p’

# sed -n ‘\%^/home/alice/documents/%p’

# sed -n ‘\;^/home/alice/documents/;p’

/regexp/I, \%regexp%I
I标识符表示匹配时忽略大小写。注意:I是大写的,小写的i是插入命令。

在下面的示例中, /b/是正则地址匹配,I指的是忽略大小写,d删除:

# printf “%s\n” a b c | sed ‘/B/Id’

a

c

在下面的示例中,/b/是一个地址,i是插入命令,而d成为了要插入的内容:

# printf “%s\n” a b c | sed ‘/b/id’

a

d

b

c

/regexp/M, \%regexp%M
M修饰符是GNU扩展,用于支持多行匹配。使用了M修饰符后,^表示换行符后的空字符串,$表示换行符前的空字符串。字符序列\`代表buffer的开始,字符序列\’代表buffer结束。此外,在多行模式中,句点(.)不匹配换行符。

范围地址(Range Addresses)

地址可以是一个范围,格式是用逗号分割的两个数字。示例如下,以范围作为地址:

# seq 10 | sed -n ‘4,6p’

4

5

6

范围包括开头和结尾的数字。

如果第二个数字是正则表达式,那么结束地址为表达式匹配的第一个地址,因此匹配结果最少为两行(除非文本没有剩余内容了)。示例如下:

# seq 10 | sed -n ‘4,/[0-9]/p’

4

5

GNU sed也支持某些特殊的双地址格式。下面的格式属于GNU的扩展:

0,/regexp/
使用以0开始地址,比如0,/regexp/,正则尝试匹配第一个输入的行。在这一点上和1,/regexp/是有出入的。示例如下:

# seq 10 | sed -n ‘1,/[0-9]/p’

1

2

# seq 10 | sed -n ‘0,/[0-9]/p’

1

addr1,+N
从addr1开始,到addr1+N结束。示例如下:

# seq 10 | sed -n ‘6,+2p’

6

7

8

addr1,~N
从addr1开始,直到遇到第一个能被N整除的行号。示例如下:

# seq 1000000 | sed -n ‘6,~9p’

6

7

8

9

从第六行开始,第一个能被9整除的行是第9行。

关于”地址范围“要注意的地方

  • 对于addr1,addr2,如果 addr2 > addr1(这属于不正常,但sed没有视作错误),只会处理addr1匹配的行。
  • 如果 addr2 是个正则表达式,它不会对 addr1 指定的行进行匹配,意思就是说 addr2 作用于 addr1 之后的行。
  • 如果在地址后面(或者地址范围)命令之前,使用了 `!’ 表示在不匹配的行上执行命令功能。

正则表达式:选择文本

介绍如何在sed中使用正则表达式。由于性能问题,因该支持的 POSIX.2 BREs 没有被完全的支持。\n表示新行,\a、\t以及其他的也是类似的。

在sed中的正则表达式

要想知道如何使用sed,最好要理解正则表达式。正则表达式从左到右匹配目标字符串。

在sed中,将正则表表达式放在两个斜线(/)之间。如下,打印包含hello的行:

#!/bin/sh

sed -n '/hello/p'

# 等价于:
grep 'hello'

正则表达式的厉害的地方在于“替代”和“重复”功能。这些是通过在表达式中使用特殊字符,这些字符以某种特殊含义进行解释。

在正则表达式中,`^’表示行的开始。`.’代表任意单个字符。如下的sed命令表示打印以字母b开头,然后跟着任意一个字符,然后再跟着一个d字符:

#!/bin/sh

printf "%s\n" abode bad bed bit bid byte body | sed -n '/^b.d/p'

# 输出:
#
# bad
# bed
# bid
# body

基础正则表达式(BRE)与扩展正则表达式(ERE)

BRE和ERE是在指定模式语法中的两个变体。BRE是默认的(grep也是如此)。ERE需要使用-r或-E选项来激活(grep也是如此)。

在GNU sed中,BRE和ERE的不同的地方在仅于几个特殊字符:‘?’, ‘+’, parentheses(圆括号), braces(花括号), ‘|’。

在BRE中,如果这些字符没有前缀的反斜线,那他们就是普通的字符。而在ERE中,如果这些字符有前缀的反斜线,那他们不再有任何特殊含义。请看下面的如下示例:

Desired pattern BRE ERE
+ echo “a+b=c” | sed -n ‘/a+b/p’
a+b=c
echo “a+b=c” | sed -E -n ‘/a+b/p’
a+b=c
aab echo “aab” | sed -n ‘/a+b/p’
aab
echo “aab” | sed -E -n ‘/a+b/p’
aab

基础正则表达式(BRE)语法概述

下面是sed中用到的正则表达式的简单概述:

char
一个普通字符与自身匹配。

*****
匹配上一个正则表达式的零个或多个匹配实例的序列,该正则表达式必须是普通字符、前面是‘\‘、’.‘、正则表达式组、括号表达式的特殊字符。

作为GNU扩展,后缀正则表达式的后面也可以跟*;例如,a**等价于a*。在POSIX 1003.1-2001中,*当它出现在正则表达式或子表达式的开始时,*表示自身,但是许多非GNU实现不支持这一点,兼容的脚本应该在这些上下文中使用\ *。

.
匹配任何字符,包括换行符。

^
匹配模式空间开始处的空字符串,即在模式空格开始之后必须出现的显示。

在大多数脚本中,模式空间被初始化为每行的内容。因此,^#include将匹配已#include开始的行(如果先前有空格,匹配会失败)。只要模式空间的原始内容不被修改,例如使用s命令,这种简化是有效的。

^仅在正则表达式或子表达式(即\之后,或者\|之后)中起特殊字符的作用。兼容性脚本应避免^在子表达式的开头,因为POSIX允许将在这种情况下的普通字符。

$
表示模式空间的结束。只有在正则表达式或者子表达式((即\之后,或者\|之后))的结尾,$才会被视为特殊字符,并且用在子表达式的结尾是不兼容的。

[list]

[^list]
匹配list中的任意单个字符。例如:[aeiou]匹配所有的元音。如果list是char1-char2的形式,会匹配char1和char2之间的所有字符(包含char1和char2)。详细内容,查看下面的“字符类和括号表达式”章节。

\+

如同*,但是匹配一个或多个。它是GNU的扩展。

\?
如同*,但是匹配一个或零个。它是GNU的扩展。

\{i\}

如同*,但是匹配i次,i为正数,为了兼容,最好是在0-255之间的数字,包含0和255。

\{i,j\}

匹配i~j之间的序列。包含i和j。

\{i,\}

匹配超过或者等于i的序列。

\(regexp\)

将()中的表达式视为一个整体。有如下作用:

  • 应用postfix操作符,如\(abcd\)*:这表示搜索零个或多个完整的’abcd’序列,而abcd*将搜索’abc’,后跟零个或多个’d’。请注意,POSIX 1003.1-2001需要支持\(abcd \)*,但许多非GNU实现不支持它,因此它不是普遍可移植的。
  • 使用反向参考。

regexp1\|regexp2

匹配regexp1或regexp2。使用括号来使用复杂的替代正则表达式。匹配过程依次从左到右进行每个替代,并且使用成功的第一个。它是一个GNU扩展。

regexp1regexp2

匹配regexp1和regexp2的连接。连接绑定比\|、^、$更紧密,但不如其他正则表达式运算符紧密。

\digit

在正则表达式中匹配数字第几\(…\)括号子表达式。这被称为反向参考。子表达式通过计数\(从左到右的次数进行隐式编号。

\n

匹配换行符。

\char

匹配char,其中char是$、*、.、[、\、^之一。注意:可以假定要解释的只有C样的反斜杠序列是\n和\\;特别是\t不可移植,并且在sed的大多数实现下匹配“t”,而不是制表符。

注意:正则表达式的匹配是贪婪模式。匹配是从左到右尝试的,如果从同一个字符开始可以进行两次或多次匹配,则选择最长的。

下面是一些示例

‘abcdef’

匹配’abcdef’

‘a*b’

匹配零个或多个a,且后面跟着单个b。例如:‘b’ 或 ‘aaaaab’

‘a\?b’

?表示匹配一个或者零个前面的序列。所以,代表了‘a’或者‘ab’。

‘a\+b\+’

匹配一个或多个a后面跟着一个或多个b的情况。例如:‘ab’ 、‘aaaab’ 、 ‘abbbbb’ 、 ‘aaaaaabbbbbbb’

‘.*’
‘.\+’

这两个都表示匹配一个字符串,但是第一个允许字符串为空,而第二个不允许字符串为空。

‘^main.*(.*)’

匹配以main开始,且后面有(和)的内容。n、(、)这三者不必相连。

‘^#’

匹配以#为开始的行。

‘\\$’

匹配以一个单引号结尾的字符串。\\用于转义,表示单个\。

‘\$’

匹配包含一个$符号的字符串。

‘[a-zA-Z0-9]’

在本地化的C中,匹配任何ASCII字符和数字。

‘[^ tab]\+’

(这里tab代表单个tab字符。)这匹配一个或多个字符的字符串,其中没有一个是空格或tab。通常这意味着一个字。

‘^\(.*\)\n\1$’

匹配由换行分割的两个相同字符串。

‘.\{9\}A$’

匹配九个字符后面跟着一个结尾的A的字符串。

‘^.\{15\}A’

匹配有16个字符开始,且最后一个为A的字符串。

扩展正则表达式(ERE)语法概述

BRE和ERE唯一区别是几个字符的行为:’?’,’+’,括号,花括号('{}’)和’|’。在BRE中,将它们转义后才表示为特殊字符。但在ERE中,如果希望它们与文字字符匹配,则必须将其转义。’|’是特殊的,因为’\|’是一个GNU扩展,标准的BRE不提供其功能。

下面是一些简单的示例

abc?

在ERE中,?具有特殊的含义,无需转义,但是如果转义了?(即\?),那就表示?。例如:在ERE中,‘abc\?’表示匹配‘abc?’。

c\+

如同上一个列子,在ERE中,如果转义了+不再有特殊含义。c\+表示匹配c+字符串。

a\{3,\}

当在ERE中,应该使用a{3,},用于匹配三个或三个以上的连续的a。

\(abc\)\{2,3\}

在ERE中,应该写成‘(abc){2,3}’,表示匹配‘abcabc’或‘abcabcabc’。

\(abc*\)\1

在ERE中,应该写成‘(abc*)\1’。注意:在使用ERE的时候,反向引用依旧需要转义。

a\|b

在ERE中,要使用’a|b‘,才能匹配到’a‘或’b‘。

字符类和括号表达式

括号表达式是由'[‘和’]’括起来的字符列表。它匹配该列表中的任何单个字符;如果列表的第一个字符是’^’,那么它匹配列表中不包含的任何字符。如下例子,将‘gray’或‘grey’替换成‘blue’:

#!/bin/sh
sed  's/gr[ae]y/blue/'

[]即可以在ERE中使用,也可以在BRE中使用。

在括号表达式中,范围表达式由用连字符分隔的两个字符组成。它匹配在两个字符之间排序的任何单个字符(包括)。在默认的C语言环境中,排序顺序是本地字符顺序;例如,'[a-d]’等同于'[abcd]’。

最后,某些命名类的字符是在括号表达式中是预定义的,后面会介绍到。

这些命名类必须在括号内使用。如下的是正确使用:

#!/bin/sh

echo 1 | sed 's/[[:digit:]]/X/'

较新的sed版本被拒绝使用不正确的命名类。较旧的版本接受它,但将其视为单括号表达式(相当于'[dgit:]’,即只有字符d/g/i/t/:):

#!/bin/sh

# 对于错误的命名类,新旧版本sed的表现如下:

# 新版本的sed
echo 1 | sed 's/[:digit:]/X/'
# sed: character class syntax is [[:space:]], not [:space:]

# 旧版本的sed
echo 1 | sed 's/[:digit:]/X/'
1

[:alnum:]

字母数字字符:‘[:alpha:]’ 和 ‘[:digit:]’;在C语言环境和ASCII字符编码中,这与‘[0-9A-Za-z]’是相同的。

‘[:alpha:]’

字母字符:‘[:lower:]’ 和 ‘[:upper:]’;在C语言环境和ASCII字符编码中,这与‘[A-Za-z]’是相同的。

‘[:blank:]’

空白字符:空格和制表符。

‘[:cntrl:]’

控制字符。在ASCII中,这些字符的八进制编码为000 到 037, 和 177 (DEL)。在其他字符集中,这些是相同的字符,如果有的话。

‘[:digit:]’

数字: 0 1 2 3 4 5 6 7 8 9

‘[:graph:]’

图形字符: ‘[:alnum:]’‘[:punct:]’.

‘[:lower:]’

小写字母;在C语言环境和ASCII字符编码中是a b c d e f g h i j k l m n o p q r s t u v w x y z

‘[:print:]’

可打印字符:‘[:alnum:]’、 ‘[:punct:]’、 space

‘[:punct:]’

标点符号;在C语言环境和ASCII字符编码中为! ” # $ % & ‘ ( ) * + , – . / : ; < = > ? @ [ \ ] ^ _ ` { | } ~

‘[:space:]’

空格字符;在C语言环境和ASCII字符编码中是:tab, newline, vertical tab, form feed, carriage return, space.

‘[:upper:]’

大写字母;在C语言环境和ASCII字符编码中是A B C D E F G H I J K L M N O P Q R S T U V W X Y Z

‘[:xdigit:]’

十六进制:0 1 2 3 4 5 6 7 8 9 A B C D E F a b c d e f

注意:这些类名称中的方括号是符号名称的一部分。在使用括号表达式时,除了括号表达式的括号之外,还必须包含命名类的括号,即要写两对括号。

在括号表达式中,大多数’元字符‘失去其特殊含义:

‘]’

如果不是列表中的第一个项,则表示结束括号表达式。如果你想在列表项中使用’]’,那必须放在第一个。

‘-’

如果不是列表中的第一个或者最后一个或范围的终点,表示范围。

‘^’

表示不在列表中的字符。如果要使’^’成为列表中的一个项,可以把^放在处理第一个以外的任何位置。

在列表中,字符$, *, ., [, \通常不是特殊的。例如,[\*]匹配“\”或“*”,因为\在这里没有特别含义。然而,像[.ch.], [=a=], [:space:]这样的字符串在列表中是特殊的,分别代表整理符号,等价类和字符类,因此在列表中,[后面跟着., =, :是特别的。另外,当不在POSIXLY_CORRECT模式时,在列表中,像\n和\t这样的特殊转义被识别。关于转义的内容,查看“转义序列”章节

‘[.’

代表打开整理符号。

‘.]’

代表关闭整理符号。

‘[=’

表示打开等价类。

‘=]’

表示关闭等价类。

‘[:’

表示打开字符类符号,后面应该有一个有效的字符类名称。

‘:]’

表示关闭字符类符号。

Next: Back-references and Subexpressions, Previous: Character Classes and Bracket Expressions, Up: sed regular expressions [Contents][Index]

正则表达式扩展

以下序列在正则表达式中有特殊含义(在Address和s命令中使用)。在BRE和ERE都可以使用这些序列。

\w
匹配任何“单词”。“单词”是指任何字母、数字、下划线。

#!/bin/sh

echo "abc %-= def." | sed 's/\w/X/g'
# 输出:XXX %-= XXX.

\W
匹配所有的“非单词”。

#!/bin/sh

echo "abc %-= def." | sed 's/\W/X/g'
# 输出:abcXXXXXdefX

\b
匹配“单词”边界。如果不好理解,请看下面的列子:

#!/bin/sh

echo "abc %-= def." | sed 's/\b/X/g'
# 输出:XabcX %-= XdefX.

# 连续的字会被视为是一个单词
echo "abc %-= de.f." | sed 's/\b/X/g'
# 输出:XabcX %-= XdeX.XfX.

\B
如果字左边或者右边与字同样都是字或者非字,那么匹配。这个很难理解,看下面的如下示例:

#!/bin/sh

echo "abc %-= def." | sed 's/\w/X/g'
# 输出:aXbXc X%X-X=X dXeXf.X

\s
匹配空格字符(空格和制表符)。嵌入模式空间和持有空间的换行符也将匹配。看如下示例:

#!/bin/sh

echo "abc %-= def." | sed 's/\s/X/g'
# 输出:abcX%-=Xdef.

\S
匹配非空白字符。

#!/bin/sh

echo "abc %-= def." | sed 's/\w/X/g'
#输出:XXX XXX XXXX

\<
匹配字的开始。如下所示:

#!/bin/sh

echo "abc %-= def." | sed 's/\</X/g'
# 输出:Xabc %-= Xdef.

\>
匹配字的结束。如下所示:

#!/bin/sh

echo "abc %-= def." | sed 's/\>/X/g'
# 输出:abcX %-= defX.

\`
仅匹配模式空间的开始。在多行模式下,这与^不同。比较如下的两个例子:

#!/bin/sh

printf "a\nb\nc\n" | sed 'N;N;s/^/X/gm'
# 输出如下:
# Xa
# Xb
# Xc

printf "a\nb\nc\n" | sed 'N;N;s/\`/X/gm'
# 输出如下:
# Xa
# b
# c

\’

仅匹配模式空间的结束。在多行模式下,与$有所不同。

反向引用与子表达式

反向引用是指匹配正则表达式的前一部分的正则表达式命令。反引号用反斜杠和单个数字(例如“\1”)指定。它们引用的正则表达式的一部分称为子表达式,并用括号指定。

反向引用和子表达式用于两种情况:

  • 正则表达式搜索模式。
  • s命令的替换部分。

在正则表达式模式中,使用反向引用来匹配与先前匹配的子表达式相同的内容。在下面的示例中,子表达式为’.’-任何单个字符(被括号括起来使其成为子表达式)。反向引用’\1’要求与子表达式匹配相同的内容(相同字符)。

下面的命令匹配以任何字符开始的单词,后跟字母“o”,然后再跟一个和第一个相同的字符:

#!/bin/sh

sed -E -n '/^(.)o\1$/p' /usr/share/dict/words
# 输出如下:
# bob
# mom
# non
# pop
# sos
# tot
# wow

多个子表达式将从左到右自动编号。如下命令,搜索6个字母的回文(前三个字母为3个子表达式,后跟3个反向引用相反顺序):

#!/bin/sh

sed -E -n '/^(.)(.)(.)\3\2\1$/p' /usr/share/dict/words
# 输出内容如下:
# redder

在s命令中,可以在替换部分中使用反向引用来引用regexp部分中的子表达式。

以下示例使用正则表达式中的两个子表达式来匹配两个空格分隔的单词。替换部件中的反向引用以不同的顺序打印单词:

#!/bin/sh

echo "James Bond" | sed -E 's/(.*) (.*)/The name is \2, \1 \2./'
# 输出:The name is Bond, James Bond.

当与交替使用时,如果组不参与匹配,则反向引用使整个匹配失败。例如,’a(.)|b\1’将不匹配’ba’。当使用-e或从文件(’-f file’)给出多个正则表达式时,每个表达式的反向引用是本地的。

转义序列(如何使用特使字符)

在本章之前,我们只遇到了“\^”形式的转义,它告诉sed不要将^解释为特殊字符。例如,’\*’匹配单个星号,而不是零个或多个反斜杠。

本章介绍另一种类型的转义,即应用于字符或字符序列的转义,通常是字面上的字符,sed替换为特殊字符。这提供了以可见方式对模式中的不可打印字符进行编码的方式。对sed脚本中的非打印字符的外观没有限制,但是当在shell中编写脚本或通过文本编辑时,通常使用以下转义序列之一比其代表的二进制字符更容易:

\a

代表或匹配BEL字符,即“alert”(ASCII 7)。

\f

代表或匹配一个FF,即“换页”(ASCII 12)。

\n

代表或者匹配一个换行符。(ASCII 10)。

\r

代表或者匹配一个回车符号。(ASCII 13)。

\t

代表或者匹配一个水平制表符号。(ASCII 9).

\v

代表或者匹配一个垂直制表符号。(ASCII 11).

\cx

生成或匹配CONTROL-x,其中x是任何字符。’\cx’的精确效果如下:如果x是小写字母,则将其转换为大写字母。然后字符(十六进制40)的第6位被反转。因此’\cz’变为十六进制1A,但’\c{‘变为十六进制3B,而’\c;’变为十六进制7B。
\dxxx

生成或匹配十进制ASCII值为xxx的字符。

\oxxx

生成或匹配八进制ASCII值为xxx的字符。

\xxx

生成或匹配十六进制ASCII值为xx的字符。

‘\b’

因为与现有的“字边界”的冲突,所以省略了’\b’(退格)。

不同语言环境中的问题

提到语言环境支持在很大程度上取决于OS/libc,而不是sed。

当前区域设置会影响与sed正则表达式匹配的字符。

在其他语言环境中,未指定排序顺序,'[ad]’可能等同于'[abcd]’或'[aBbCcDd]’,或者可能无法匹配任何字符或其中的一组字符匹配甚至可能不稳定。要获得对括号表达式的传统解释,可以通过将LC_ALL环境变量设置为值’C’来使用’C’语言环境。

#!/bin/sh

# TODO: is there any real-world system/locale where 'A'
#       is replaced by '-' ?
echo A | sed 's/[a-z]/-/'
# 输出:A

他们的解释取决于LC_CTYPE语言环境;例如’:alnum:’表示当前语言环境中的数字和字母的字符类。

显示排序规则示例:

#!/bin/sh

# TODO: this works on glibc systems, not on musl-libc/freebsd/macosx.
$ printf 'cliché\n' | LC_ALL=fr_FR.utf8 sed 's/[[=e=]]/X/g'
clichX

高级sed:周期与缓冲

TODO SED 高级部分

注意事项

TODO SED 注意事项

退出状态

0 成功。

1 无效的命令、语法、正则、同时使用了扩展命令与–posix选项,这些情况的状态码都为1。

2 指定的文件无法打开。如果在多个文件中有一个打不开,sed会继续处理剩下的,不会因为一个失败就退出。

4 IO错误或者严重的运行时处理错误,sed会立即退出并返回状态码4。

可以使用 q 或者 Q 命令可以自定义状态码来结束sed(这两个命令属于GNU sed的扩展): echo | sed ‘Q42’ ; echo $?

常见错误

#1 sed: -e expression #1, char 18: Invalid collation character

执行sed -i ‘s/[\d128-\d255]//g’ tmp.txt命令时,产生如下错误:

sed: -e expression #1, char 18: Invalid collation character

和本地语言环境有关,在命令前面追加:LANG=C,执行命令LANG=C sed -i ‘s/[\d128-\d255]//g’ tmp.txt

参考文献

man 1 sed, Version sed (GNU sed) 4.4
官方文档:https://www.gnu.org/software/sed/manual/sed.html
PDF: https://www.gnu.org/software/sed/manual/sed.pdf
SED: insert text after the last line? – Unix & Linux Stack Exchange
sed Case Insensitive Search Matching and Replacement – nixCraft
linux – Find and replace text in a file between range of lines using sed – Stack Overflow