sed 命令

sed 简述

sed 命令行格式如下:
sed [选项] [脚本] [输入文件]
sed 选项

-e script, --expression=script

在命令行上运行 script (指令),只有一组指令时可不需要本选项。

-E, -r, --regexp-extended

支持延伸正规表示法(缺省是基本正规表示法)。

-f script-file, --file=script-file

运行 script-file (指令档), sed 的指令是在指令档内。

-i

直接修改文件内容。

-z

separate lines by NUL characters,将换行以 NUL 字符取代,取代换行时需本选项。

-n, --quiet, --silent

不自动打印 (模式空间)。

替代命令
sed 's/pattern/replacement/flags' inputFileName > outputFileName

替代命令 (在脚本中第一个字母 s) 将 inputFileName 的内容与提供的样板 (pattern) 匹配;当匹配成功则将样板替换为「替换文本 (replacement)」。 / (斜线) 是传统的定界字符 (delimiter),但;其实在 pattern 和 replacement 中都未出现的其他字符都可以当作定界字符。pattern 和 replacement 常称为 正规表示法

sed 命令中可以用单引号 (') 和双引号 ("),一般习惯采用单引号,但是如果要使用 shell 变量就需要使用双引号

flags 指定为 g 时,表示 全局更改。

将下列的 is 改成 is a:
echo 'this is book, that is bicycle' | sed -e 's/ is / is a /' (1)

this is a book, that is bicycle

以全局 (g) 更改:
echo 'this is book, that is bicycle' | sed 's/ is / is a /g'

this is a book, that is a bicycle
1 只有一组指令,可不需要 -e 选项。

正规表示法

正则表达式 (英语: Regular Expression,简写为 regex、regexp 或 RE),又称正规表示法。正规表示法分为基本正规表示法(Basic Regular Expression,BRE)和延伸正规表示法(Extended Regular Expression,ERE)。
sed 采用 ERE 的选项为 -E-r

正规表示法的特殊字符
. * ^ $ / \ [ ]

上述特殊字符作为文本字符时,需要在特殊字符前加入「转义字符 (Escape Character) \ (反斜线)」 来转译它。:

sed 的定界字符如果是 / (斜线),那么文本字符的「斜线」要转译成 \/,如果不是则不可转译。
改变定界字符的目的就是为了不要转译「斜线」,如果又转译那么在 BRE 不会出错,ERE 会出错 (参阅: 测试转译斜线)。

? + | ( ) { }

上述特殊字符作为文本字符时,在 BRE 不需转译,但在 ERE 需要转译。参阅:测试「特殊字符」转译

  • 作为 BRE 文本字符时,不需要 (也不可) 转译,但作为匹配字符时需转译。

  • 作为 ERE 文本字符时,需转译,但作为匹配字符时,不需要 (也不可) 转译。

sed 的脚本如果是使用单引号 ('),而文本中有单引号时,那么 bash 要转译文本字符为 \',反之如果是双引号 (") 则要转译 \"

bash 在双引号时,除了转译 " 也要注意字符 \ $ (作为文本字符,并非引用变量) 也需要转义字符
echo "\" \\ \$"
运行结果:
" \ $

当文本内容含「特殊字符」时,需注意 bash 是否正确,如将 $'pattern' 替换为 $'replacement'

echo "\$'pattern'" | sed "s/\$'pattern'/\$'replacement'/"
'replacement'

结果无误,但实际上是错的

显示整段命令:
echo "\$'pattern'" \| sed "s/\$'pattern'/\$'replacement'/"
'pattern' | sed s/$'pattern'/$'replacement'/ (1)
1 没有转译 $,但刚好 BRE 可在不需要转译的情况下运行,但如果改为 ERE 则会出错。
specChar='$'

3.14159 (BRE 转译)
3.14159 (BRE 不转译)
3.14159 (ERE 转译)
3$14    (ERE 不转译)

在写 bash 时,echo 会记得转译 $,但;当重点放在 sed 转译特殊字符时,往往会忽略 (忘记) 了 bash 在双引号 (") 时也需要转译 \ $

先显示整段命令,确认 bash 无误:
echo "\$'pattern'" \| sed "s/\\\$'pattern'/\\\$'replacement'/"
运行结果:
'pattern' | sed s/\$'pattern'/\$'replacement'/
以 BRE ERE 运行命令:
echo "\$'pattern'" | sed    "s/\\\$'pattern'/\\\$'replacement'/" &&\
echo "\$'pattern'" | sed -E "s/\\\$'pattern'/\\\$'replacement'/"
匹配字符
字符 .

匹配一个任意字符。

字符 ^

匹配起始位置。

字符 $

匹配结束位置。

字符 *

表示前置字符有任意个 (包含 0 个)。
a*b 表示 b 的前面有 0 个或多个 a,匹配 baaab。而 .* 则表示全部文本。

字符 ?

表示前置字符有 0 个或 1 个。BRE 需要使用 \?
a\?b 表示 b 的前面有 0 个或 1 个 a,匹配 bab,不匹配 aab
? 作为 BRE 匹配字符时,需要前置转义字符 (\),但作为文本字符时不可加入转义字符。
? 作为 ERE 匹配字符时,不需要 (也不可有) 转义字符,但作为文本字符时需要前置转义字符。

字符 +

表示前置字符有 1 个或多个,BRE 需要使用 \+
a\+b 表示 b 的前面有 1 个或多个 a,匹配 abaaab,不匹配 b

字符 |

表示指明两项之间的一个选择,cat|dog 表示可以匹配 cat 或者 dog,BRE 需使用 \|

monkey moose 隐藏:
echo 'meerkat monkey moose mule' | sed    's/monkey\|moose/hide/g' &&\
echo 'meerkat monkey moose mule' | sed -E 's/monkey|moose/hide/g'
meerkat hide hide mule
meerkat hide hide mule
字符 &

合乎匹配的文本。

前置任意字符的 ats,加上中括号
echo 'Bats and cats.' | sed 's/.ats/\[&\]/g'
[Bats] and [cats].
字符 []
中括号中可以包含表示字符集 (character sets) 的表达式,使用方法大概有如下几种:

[0-9]

表示 0-9 字符中的一个。

[0-9.]

表示数字和小数点。

[a-z]

表示 a-z 字符中的一个。

[A-Z]

表示大写字母。

[a-zA-Z0-9]

大小写字母和数字。

[abc]

表示字符 a 或者字符 b 或者字符 c。

[^0-9]

表示非数字的字符,^ 表示取反意思,只能放在中括号的开始处才有意义。

[-az]

表示字符 - 或者字符 a 或者字符 z,注意与 [a-z] 的区别,因为 - 字符没有放在 a 和 z 之间。

将动物前置 mo mu 隐藏 (找出前置 m 后接 [ou] 再接任意个 [a-z]*):
echo 'meerkat monkey moose mule' | sed    's/m[ou][a-z]*/hide /g' &&\
echo 'meerkat monkey moose mule' | sed -E 's/m[ou][a-z]*/hide /g'
meerkat hide  hide  hide
meerkat hide  hide  hide
将参数改名,匹配开始字符,任意个不等于「结束字符」的字符,再匹配结束字符
echo 'show(abc)' | sed    's/([^)]*)/(def)/' &&\
echo 'show(abc)' | sed -E 's/\([^\)]*\)/\(def\)/'
show(def)
show(def)
取代任意文本,要指定可取代那些字符;最好的方式是字符条件不等于 pattern 的结束字符 ([^pattern])。
字符 [[: :]]

sed 除了正规的字符集外还支持命名字符类 (named character classes),命名类必须在两个中括号内使用。

命名字符类的等效字符集如下:

[[:alnum:]]

[A-Za-z0-9]

大小写字母和数字。

[[:alpha:]]

[A-Za-z]

英文本母。

[[:blank:]]

[ \x09]

空格或制表字符 (Tab 键)。

[[:cntrl:]]

[\x00-\x19\x7F]

控制字符。

[[:digit:]]

[0-9]

数字。

[[:graph:]]

[!-~]

空白字符之外的 (可见) 字符。

[[:lower:]]

[a-z]

小写字母。

[[:print:]]

[ -~]

可打印的字符(类似 [[:graph:]],但包括空白字符)。

[[:punct:]]

[!-/:-@[-`{-~]

标点符号。

[[:space:]]

[ \f\n\r\t\v]

所有空白字符。

[[:upper:]]

[A-Z]

大写字母。

[[:xdigit:]]

[0-9a-fA-F]

16 进制字符。

[^[:digit:]]

[^0-9]

表示非数字的字符,^ 表示取反意思,只能放在第二个中括号的开始处才有意义。

字符 \

\a

Alert (警报) beep,哔声,等价于 \x07\cG

\b

匹配「单词」边界。注:单词为字母、数字或底线 (_)。

\B

匹配非单词边界。(\B 的匹配条件跟 \b 相反。)

\d

匹配数字 (含中文全型数字),等价于 [0-9]

\D

匹配非数字,等价于 [^0-9]

\f

匹配换页字符,等价于 \x0c\cL

\n

匹配换行字符,等价于 \x0a\cJ

\r

匹配确认键,等价于 \x0d\cM

\s

匹配空白字符;包括空格 (含中文全型空格)、换页字符、制表字符等等,等价于 [ \f\n\r\t\v]

\S

匹配非空白字符,等价于 [^ \f\n\r\t\v]

\t

匹配一个制表字符,等价于 \x09\cI

\v

匹配一个垂直制表字符,等价于 \x0b\cK

\w

匹配「单词」字符,等价于 [A-Za-z0-9_]

\W

匹配非单词的字符,等价于 [^A-Za-z0-9_]

\cx

匹配控制字符 x,例如,\cM 为一个 Ctrl+M 或确认键。

\xnn

16 进制数值 nn,如 \x0a 为换行。

\dnum

10 进制数值 num,如 \d10 为换行。

\u

在 sed 中是将字符转成大写,并非作为 Unicode 编码,参阅:特殊串行


匹配「单词」边界 (\b)。

将边界以中括号标示
echo 'at attach cattle cat' | sed -e 's/\b/\[&\]/g' &&\
echo 'at attach cattle cat' | sed -E 's/\b/\[&\]/g'
[]at[] []attach[] []cattle[] []cat[]
[]at[] []attach[] []cattle[] []cat[]

边界是一个抽象的匹配,在边界时条件为吻合。

先匹配文本 at 再匹配边界 \b
echo 'at attach cattle cat' | sed    's/at\b/&\[\]/g' &&\
echo 'at attach cattle cat' | sed -E 's/at\b/&\[\]/g'
at[] attach cattle cat[]
at[] attach cattle cat[]

匹配文本 at 条件之后要为边界才为吻合。

先匹配边界 \b 再匹配文本 at
echo 'at attach cattle cat' | sed    's/\bat/\[\]&/g' &&\
echo 'at attach cattle cat' | sed -E 's/\bat/\[\]&/g'
[]at []attach cattle cat
[]at []attach cattle cat

边界条件匹配后,再匹配文本 at 才为吻合。


无边界文本,匹配「边界」。
echo '12345678' | sed 's/\b/\[&\]/g'

[]12345678[]

在文本的前后边界时条件为吻合。

千位分隔符号,在一段连续的数字中,由右方开始算起,每隔三位数加进一个逗号。
在数字右方 (边界),开始算起接连三个数字,加入一个逗号。
用 sed 的说明:在匹配三个数字 ([0-9]{3}) 后要接「边界」(\b)。

匹配三个数字 ([0-9]{3}) 后要接「边界」(\b)
echo '12345678' | sed 's/[0-9]\{3\}\b/\[&\]/'

12345[678]
上述虽然正确,但如果数字为 123 也会合乎条件,匹配式有错。
echo '123' | sed 's/[0-9]\{3\}\b/\[&\]/'

[123]

条件需再加上,开始的「非边界」。
在「非边界」之后,接三个数字,在三个数字之后要接「边界」。

条件是

  1. 非边界 /B

  2. 三个连续数字 [0-9]{3}

  3. 边界 /b

echo '12345678' | sed    's/\B[0-9]\{3\}\b/\[&\]/' &&\
echo '12345678' | sed -E 's/\B[0-9]{3}\b/\[&\]/' &&\
echo '123'      | sed    's/\B[0-9]\{3\}\b/\[&\]/' &&\
echo '123'      | sed -E 's/\B[0-9]{3}\b/\[&\]/'
12345[678]
12345[678]
123
123

注:接下来要「分支」的动作,才能完成 12,345,678

\B \b 之间的匹配,可当做是由后面 (右边) 开始,向前面 (左边) 匹配。

「单词」有那些?

测试「单词」边界:
echo "abc +-*/=. 123 _" | sed 's/\b/X/g'
单词边界结果:
XabcX +-*/=. X123X X_X

单词为字母、数字或底线 (_)。


匹配换行 \n,sed 需要使用 -z 选预。

将换行替换为 ,
seq 3 | sed -z 's/\n/,/g'

如何处理 Unicode 编码?

Σ 的 Unicode 编码为 \u03a3
echo -n Σ | iconv -t JAVA
\u03a3
  • 直接采用 Unicode 字符。

  • echo -n Σ | od -tx1echo -n Σ | hexdump -C 转换成 16 进位字符 (cea3)。

  • 以 bash echo 来转换如 '$(echo -ne '\u03a3')'

  • 以 bash 转换如 '$'\u03a3''

echo Σ | sed 's/Σ/X/' &&\
echo Σ | sed 's/\xce\xa3/X/' &&\
echo Σ | sed 's/'$(echo -ne '\u03a3')'/X/' &&\
echo Σ | sed 's/'$'\u03a3''/X/'
X
X
X
X
字符 (pattern)

表示分组「Group」,在 ( ) 之间为匹配样板 (pattern),BRE 需使用 \(\)。可以通过回调参数 \1\2\3 至最大值为 \9 来表示分组匹配内容。

只打印匹配分组样板 monkey
echo 'mule monkey moose' | sed    's/.*\(monkey\).*/\1/' &&\
echo 'mule monkey moose' | sed -E 's/.*(monkey).*/\1/'
monkey
monkey
将参数 b a 互换 a1 b2
echo 'add(b, a)' | sed     's/add(\([^,]\+\), *\([^)]\+\)/add(\21, \12/' &&\
echo 'add(b, a)' | sed  -E 's/add\(([^,]+), *([^\)]+)/add(\21, \12/'
add(a1, b2)
add(a1, b2)

回调参数最大为 \9\21 是指回调第 2 组再加上数字 1

字符 {n,m}

{n}

匹配 n 次。如 o{2} 不能匹配 fox 中的一个 o,但是能匹配 google 中的两个 o

{n,}

至少匹配 n 次。如 o{2,} 不能匹配 fox 中的一个 o,但能匹配 goooogle 中的所有 o
o{1,} 等价于 o+o{0,} 则等价于 o*

{n,m}

至少匹配 n 次且最多匹配 m 次。如 o{1,3},将匹配 goooogle 前三个 o
o{0,1} 等价于 o?

BRE 需使用 \{n,m\}

匹配一个或多个小写 o,替换为一个大写 O:
echo 'meerkat monkey moose mule' | sed    's/o\{1,\}/O/g' &&\
echo 'meerkat monkey moose mule' | sed    's/o\+/O/g' &&\
echo 'meerkat monkey moose mule' | sed -E 's/o{1,}/O/g' &&\
echo 'meerkat monkey moose mule' | sed -E 's/o+/O/g'
meerkat mOnkey mOse mule
meerkat mOnkey mOse mule
meerkat mOnkey mOse mule
meerkat mOnkey mOse mule
字符 () 在 BRE (基本正规表示法) 及 ERE (延伸正规表示法) 的差异
文本字符 () 在 BRE 不是特殊字符不需要转译。如替换 (Hello) 取代成 hi:
echo '(Hello)' | sed 's/(Hello)/hi/'

hi
如果转译成 \(\),则变成 BRE 分组「Group」。
echo '(Hello)' | sed 's/\(Hello\)/hi/'

(hi)

但为什么会有这样的结果?
「分组」取得分组样板 Hello,将其分组样板替换为 hi
原始文本 (Hello)(「分组样板」),将其「分组样板」替换 hi 其结果为 (hi)

文本字符 () 在 ERE 需要转译:
echo '(Hello)' | sed -E 's/\(Hello\)/hi/'

hi
不转译 (),则为 ERE 分组「Group」。
echo '(Hello)' | sed -E 's/(Hello)/hi/'

(hi)

sed 指令 (script)

sed 进程由一个或多个 sed 指令 (script) 组成,由多个 -e 选项传入或者运行 -f 指定的指令档。

sed 指令 (script) 格式如下:
[选择行号] 命令 [选项]
多指令语法

指令的范例是将 2 替换为 2B3 替换为 3C

多指令使用 -e 选项
seq 5 | sed -e 's/2/2B/' -e 's/3/3C/' (1)
1 -e 之前要空格,-e 之后可不需要空格。
使用分号 (;)
seq 5 | sed 's/2/2C/; s/3/3C/' (1)
1 分号之后可不需要空格。
使用换行
seq 5 | sed 's/2/2B/
s/3/3C/
'
使用文件 (-f 选项):
cat > script.sed << EOF
s/2/2B/
s/3/3C/
EOF

seq 5 | sed -f script.sed
sed 管道
当读取跟输出的文件相同时,如果以下列方式运行:
sed command $File | sed command > $File (1)
cat $File | sed command | sed command > $File (1)
1 将得到一个空的 $File,原因是在创建管道时,输出管道 > $File 会先运行将创建一个空的 $File。
可使用临时文件来解决此问题:
sed command $File | sed command > tmpFile
mv tmpFile $File

cat $File | sed command | sed command > tmpFile
mv tmpFile $File

选择行号 (Addressing)

number

指定「行号 (number 数字)」仅匹配该「行号」。

first~step

first 行号 (数字) 开始,间隔 (~) step 行号 (数字)。1~2 表示选择奇数行。

$

匹配最后一行

/regexp/

匹配正则表达式 regexp 的「行」。

\%regexp%

同样是匹配正则表达式 regexp,但允许使用与 / (斜线) 不同的定界字符,表达式中不需要再转译「斜线」。
定界字符可为 % 亦可替换为其他单个字符。

/regexp/I
\%regexp%I

regexp 不区分大小写

/regexp/M
\%regexp%M

多行模式空间采用修饰字符 M 才能匹配每行的 ^$

n,m

上述可以采用逗号 (,) 区分两个位置;位置范围由第一个位置 (n) 至 (,) 第二个位置 (m)。第二个位置如果是「行号」,该数字小于第一个行号,则只有一行匹配。
第二个位置如果是 regexp,则匹配起始位置是第一个位置之后的开始行,该范围始终跨越至少两行。
注:如果 regexp 没有匹配,则范围为第一个位置至最后一行。

0,/regexp/

行号 0 可以用在地址规范中,例如 0,/regexp/,第二个位置是 regexp,则匹配起始位置是第一个位置 (0) 之后 (即为文件开头),sed 可以由第一行开始匹配。
注:如果 regexp 没有匹配,则范围为第一行至最后一行。

addr1,+N

addr1 行开始,再接着往下数 N 行,也就是 addr1addr1+N 行。
addr1 可为行号 (number) 或 正则表达式 (regexp)。

addr1,~N

addr1 行开始至 (,) 下一个倍数 (~) N 的行号。
addr1 可为行号 (number) 或 正则表达式 (regexp)。

number
第 2 行运行打印
seq 3 | sed -n '2 p' (1)

2
1 sed 使用 -n 选项 (不自动输出),由 p (打印) 命令来输出,另外 2 p 中间可不需要空格字符。
first~step
从第 2 行开始,每隔 (~) 3 行,运行打印
seq 9 | sed -n '2~3 p'

2
5
8
regexp
找出 B 行,运行打印
printf '%s\n' 1A 2B 3C | sed -n '/B/ p' &&\
printf '%s\n' 1A 2B 3C | sed -n '/b/I p'

2B
2B
找出不存在的 F,运行打印
printf '%s\n' 1A 2B 3C | sed -n '/F/ p'

没有输出。

\%regexp%
采用定界字符 %#,不需转译 /。找出含字符 / 的行,运行打印。
printf '%s\n' 1A 2/ 3C | sed -n '\%/% p' &&\
printf '%s\n' 1A 2/ 3C | sed -n '\#/# p' &&\
printf '%s\n' 1A 2/ 3C | sed -n '/\// p'

2/
2/
2/

$ 需要转译成 \$,表达式 \%regexp% 是允许使用与 / 不同的定界字符,并不是不需要「转译」。

找出 $ 行,运行打印
printf '%s\n' 1A 2$ 3C | sed -n '\%\$% p' &&\
printf '%s\n' 1A 2$ 3C | sed -n '/\$/ p'

2$
2$
/regexp/M

模式空间只有一行时,能匹配 ^$

打印开头 (^) 之后为 2 或者 2 在结束 ($) 之前的行。
seq 3 | sed -n '/^2/ l' &&\
seq 3 | sed -n '/2$/ l'
2$
2$

但在多行时,无法匹配 2,因为 2 不在整个数据的开头之后或结束之前。

下列将无输出
seq 3 | sed -n 'N;N; /^2/ p' &&\
seq 3 | sed -n 'N;N; /2$/ p'

采用修饰字符 M,则能匹配每行的 ^$

匹配第 2 行的 ^$
seq 3 | sed -n 'N;N; /^2/M l' &&\
seq 3 | sed -n 'N;N; /2$/M l'
1\n2\n3$
1\n2\n3$
n,m
第 2 行 至(,) 第 4 行运行打印
seq 100 | sed -n '2,4 p'

2
3
4
第 4 行 至(,) 第 2 行,只有第 4 行运行打印 (行号 4 至行号 2,只有行号 4 合乎)
printf '%s\n' 1A 2B 3C 4D 5E | sed -n '4,2 p'

4D
第 D 行 至(,) 第 2 行运行打印,只有 D 行运行打印 (D 行号为 4 至行号 2,只有行号 4 合乎)
printf '%s\n' 1A 2B 3C 4D 5E | sed -n '/D/,2 p'

4D
第 2 行 至(,) D 行运行打印
printf '%s\n' 1A 2B 3C 4D 5E | sed -n '2,/D/ p'

2B
3C
4D
第 4 行 至(,) B 行运行打印,regexp 没有匹配 B,打印 第 4 行至最后一行 (regexp 没有匹配,则范围由第一个位置至最后一行)。
printf '%s\n' 1A 2B 3C 4D 5E | sed -n '4,/B/ p'

4D
5E
第 1 行 至(,) A 行运行打印,regexp 没有匹配 A (匹配起始位置是第一个位置之后),打印 第 1 行至最后一行。
printf '%s\n' 1A 2B 3C 4D 5E | sed -n '1,/A/ p'

1A
2B
3C
4D
5E
0,regexp
第 0 行 至(,) A 行运行打印
printf '%s\n' 1A 2B 3C | sed -n '0,/A/ p'

1A
第 0 行 至(,) 不存在 F 行运行打印,regexp 没有匹配,打印全部。
printf '%s\n' 1A 2B 3C | sed -n '0,/F/ p'

1A
2B
3C
addr1,+N
从第 2 行开始,再接着往下数 (+) 3 行,也就是 2 至 5 行运行打印
seq 100 | sed -n '2,+3 p'

2
3
4
5
addr1,~N
由第 4 行开始 至(,) 下一个倍数 (~) 3 的行号,也是 4 至 6 运行打印。
seq 100 | sed -n '4,~3 p'

4
5
6

选择行号「分组」表达式

选择行号的 BRE 分组功能 \(regexp\),ERE 需使用 (regexp),所选取的内容为两组相同;分组匹配的内容跟回调内容 (\1) 相同,如匹配内容为 9 回调内容亦为 9 则匹配 99 的行。

打印接连二个相同字符的行
seq 87 110 | sed -En '/(.)\1/p'
88
99
100
110
回调参数可加上额外条件如 \19,回调参数最大为 \9\19 是指回调第 1 组再加上字符 9
seq 100 400 | sed -En '/(.)\19/p'
119
229
339
打印前置 9;中间接连两个相同字符;及尾端为 9 的行。
seq 9100 9400 | sed -En '/^9(.)\19$/p'
9119
9229
9339
匹配起始位置 (^) 及 匹配结束位置 ($) 说明:
  • /(.)\1/ 匹配二个相同字符。

  • /^(.)\1/ 匹配起始位置 ^,开始的字符再接续一个相同的字符。

  • /(.)\1$/ 匹配结束位置 $,回调匹配,最后的字符要跟前一个字符相同。

  • /^(.)\1$/ 同时匹配开始及结束,在匹配一个字符的情况,只有两个相同的字符合乎匹配。

seq 87 110 | sed -En '/^(.)\1/p'
88
99
110

seq 87 110 | sed -En '/(.)\1$/p'
88
99
100

seq 87 111 | sed -En '/^(.)\1$/p'
88
99

若表达式中可匹配多个数量如 *?,以 /(.*),\1/ 为例,将两组文本以字符 , 区分。
若分组表达式无其他条件,则分组匹配内容往往是 0 个字符,则匹配有字符 , 的行。

/(.*),\1/ 匹配有字符 , 的行
printf '%s\n' 1,1 12,123 321,21 12345 | sed -En '/(.*),\1/p'

1,1
12,123
321,21
/^(.*),\1/ 匹配第一组开始的文本跟第二组开始的文本。
printf '%s\n' 1,1 12,123 321,21 | sed -En '/^(.*),\1/p'

1,1
12,123
/(.*),\1$/ 回调匹配第二组文本跟第一组文本 (字符 , 之前)。
printf '%s\n' 1,1 12,123 321,21 | sed -En '/(.*),\1$/p'

1,1
321,21
/^(.*),\1$/ 同时匹配开始及结束,匹配两组相同的文本。
printf '%s\n' 1,1 12,123 321,21 | sed -En '/^(.*),\1$/p'

1,1

sed 基本命令

sed 基本命令

a

添加文本,在行后添加文本 (文本在下一行)。

c

取代文本,用文本取代行。

d

删除。

i

插入文本,在行之前插入文本 (文本在上一行)。

l

以明确形式打印 (打印控制字符)。

n

读取下一行。

p

打印。

q

离开。

r

读取文件。

s

替换 (搜索并替换)。

w

写到文件。

y

转换字符。

=

打印行号。

a (添加)、i (插入)

a text

在行后添加一行文本,输入的文本直到换行。自动忽略 a 命令后跟文本间的空格。

在第二行后添加 hello:
seq 3 | sed '2a hello'
1
2
hello
3

如果换行要接续则输入「行接续字符 \ (反斜线)」。

在第二行后添加一行 hello 再接续 world:
seq 3 | sed '2a hello\
world
' (1)
1 亦可采用「\n 换行」如 seq 3 | sed '2a hello\nworld'
1
2
hello
world
3
在第二行后添加 hello,在第三行添加 world:
seq 3 | sed '2a hello
3a world
'
1
2
hello
3
world
i text

在行前插入一行文本,输入的文本直到换行。自动忽略 i 命令后跟文本间的空格。

在第二行前插入 hello:
seq 3 | sed '2i hello'
1
hello
2
3

如果换行要接续则输入「行接续字符 \ (反斜线)」。

在第二行前插入一行 hello 再接续 world:
seq 3 | sed '2i hello\
world
' (1)
1 亦可采用「\n 换行」如 seq 3 | sed '2i hello\nworld'
1
hello
world
2
3
在第二行前插入 hello,在第三行前插入 world:
seq 3 | sed '2i hello
3i world
'
1
hello
2
world
3
a\
text

在行后添加文本,在新行添加文本,直到换行。忽略 a\ 之间的空格。

在第二行后添加 hello (前置一个空格):
seq 3 | sed '2a\
 hello'
1
2
 hello
3
如果换行要接续则输入「行接续字符 \ (反斜线)」:
seq 3 | sed '2a\
 hello\
world
s/./x/' (1)
1 本例再增加替换命令,替换 (一行只有) 一个任意字符为 x
x
x
 hello
world
x
a\ 命令和文本可以分为两个 -e 参数,使编写脚本更加容易:
seq 3 | sed -e '2a\' -e ' hello\nworld' -e 's/./x/'
i\
text

在行前插入文本,在新行输入文本,直到换行。忽略 i\ 之间的空格。

在第二行前插入 hello (前置一个空格):
seq 3 | sed '2i\
 hello'
1
 hello
2
3
如果换行要接续则输入「行接续字符 \ (反斜线)」:
seq 3 | sed '2i\
 hello\
world
s/./x/'
x
 hello
world
x
x
i\ 命令和文本可以分为两个 -e 参数,使编写脚本更加容易:
seq 3 | sed -e '2i\' -e ' hello\nworld' -e 's/./x/'

c (取代)

用文本取代行,输入的文本直到换行。自动忽略 c 命令后跟文本间的空格。

第二行取代成 hello:
seq 3 | sed '2c hello'
1
hello
3

如果换行要接续则输入「行接续字符 \ (反斜线)」。

第二行取代成 hello 文本再接续 world:
seq 3 | sed '2c hello\
world (1)
'
1 亦可采用「\n 换行」如 seq 3 | sed '2c hello\nworld'
1
hello
world
3
第二行取代成 hello,第三行取代成 world:
seq 3 | sed '2c hello
3c world
'
1
hello
world
c\
text

用文本取代行,输入的文本直到换行。忽略 c\ 之间的空格。

在第二行取代成 hello (前置一个空格):
seq 3 | sed '2c\
 hello'
1
 hello
3

如果换行要接续则输入「行接续字符 \ (反斜线)」。

seq 3 | sed '2c\
 hello\
world
s/./x/'
x
 hello
world
x
c\ 命令和文本可以分为两个 -e 参数,使编写脚本更加容易:
seq 3 | sed -e '2c\' -e ' hello\nworld' -e 's/./x/'
x
 hello
world
x

d (删除)

删除第 2 行
seq 5 | sed 2d
1
3
4
5

n (读取下一行)

读取下二行
seq 10 | sed -n 'n;n;p'

sed 先读取第 1 行,再读取 2 行,第 3 行打印。下一个循环 (cycle),由第 4 行开始,读取 2 行,第 6 行 打印,再次运行下一个循环。

3
6
9

p (打印)

第 2 行运行打印
seq 3 | sed -n '2 p' (1)
1 sed 使用 -n 选项 (不自动输出),由 p (打印) 命令来输出,另外 2 p 中间可不需要空格字符。

qQ (离开)

离开 sed 不再处理任何命令或输入。

命令格式

q[exit-code]

Q[exit-code]

Q 命令和 q 一样,但 Q 离开时不会打印。

读取下一行后运行离开,只会输出前 2 行:
seq 3 | sed 'n;q'
1
2
离开时,一并回传代号 255
seq 3 | sed 'n;q255' ; echo $? (1)
1 echo $? 打印回传代号。
1
2
255
列出 /etc/passwd 的前 10 行
sed '10q' /etc/passwd

s (替换)

替换命令格式 s/pattern/replacement/flags

flags

g

全局更改

i, I

不区分大小写。

m, M

多行模式 (multi-line mode)。

p

打印

w

写入文件 (多个 flag 时,需放在最后)

number

number 次匹配时,运行替换。

flags 可用 (不定数量的) 空格字符区分。

flags 示例
number
取代 a 为 X:
echo BAnana | sed 's/a/X/'

BAnXna
取代第 2 个 a 为 X (flags number 为 2):
echo BAnana | sed 's/a/X/2'

BAnanX
g
以 flags g (全局更改) 全部取代 a 为 X:
echo BAnana | sed 's/a/X/g'

BAnXnX
i, I
以 flags gi (不区分大小写) 取代 a 为 X:
echo BAnana |sed 's/a/X/gi'

BXnXnX
p
sed 采用 -n 命令行选项不自动输出:
echo BAnana |sed -n 's/a/X/'

无输出

s 命令采用 flags p (打印) 输出:
echo BAnana |sed -n 's/a/X/p'

BAnXna
w
s 命令采用 flags w (写入) 到标准输出:
echo BAnana |sed -n 's/a/X/w /dev/stdout'

BAnXna
m, M

在说明多行模式 (multi-line mode) 之前,先准备好多行的文本内容。

sed 先读取第 1 行, 运行 2 次命令 N (读取下一行),读取后的「文本内容」会有 3 行。
注:「文本内容」称为「模式空间 (pattern space)」。

seq 3 | sed 'N;N'
1
2
3
s 替换命令缺省行为

sed 缺省行为将模式空间中所有的数据一起处理,整个数据只有一个开始 (^) 和结束($),而匹配一个任意字符 (.) 也是整个数据一起匹配。

替换「开始 (^)」为 ^,替换「结束 ($) 」为 $
seq 3 | sed 'N;N; s/^/\^/g; s/$/\$/g'
^1
2
3$
任意数量的字符 (.*) 加上中括号
seq 3 | sed 'N;N; s/.*/\[&\]/g'
[1
2
3]
s 替换命令多行模式

在多行模式 (multi-line mode) 下,^$ 表示每一行的开始和结束,而 . 只会匹配同一行。

替换「开始 (^)」为 ^,替换「结束 ($) 」为 $
seq 3 | sed 'N;N; s/^/\^/mg; s/$/\$/mg'
^1$
^2$
^3$
任意数量的字符 (.*) 加上中括号
seq 3 | sed 'N;N; s/.*/\[&\]/mg'
[1]
[2]
[3]

可由一个由反斜线和字母组成的特殊串行,其意义如下:

\L

将「替换文本 (replacement)」转成小写,直到 \U\E

\l

将第一个字符转为小写。

\U

将替换转成大写,直到 \L\E

\u

将第一个字符转成大写。

\E

停止 (由 \L\U 开始的) 大小写转换。

\U 示例
匹配前置任意字符的 at,将合乎匹配的文本 (&) 转成大写 (\U)。
echo 'bat monkey and cat' | sed 's/.at/\U&/g'
BAT monkey and CAT
\u 示例
匹配前置任意字符的 at,将合乎匹配的文本 (&) 第一个字改成大写(\u)。
echo 'bat monkey and cat' | sed 's/.at/\u&/g'
Bat monkey and Cat

y (转换字符)

y/原始字符集/目的字符集/
替原始字符集的个数和目的字符集个数必须相等,字符集内每个字符一一对应。

转换 a-j0-9:
echo hi mary | sed 'y/abcdefghij/0123456789/'

78 m0ry

l (明确形式打印)

明确形式打印

无法打印的字符 (如换行 \n) 和字符 \ 以 C 风格的形式转译;超出长度的尾端会以「接续字符 \」拆分,末尾会标示 $
注:字符 $ 是由命令 l 明确表达,该字符实际上并不存在。

可由命令选项 l N 或命令行选项 sed -l Nsed --line-length=N 指定输出最大长度 N

明确形式打印 (控制字符)
echo -e 'Hello\tworld' | sed -n 'l'
Hello\tworld$
以命令限制长度 8
echo -e 'Hello\tworld' | sed -n 'l8'
以命令行选项 -l 限制长度 8
echo -e 'Hello\tworld' | sed -l8 -n -e 'l'
Hello\t\ (1)
world$
1 当长度不足时,尾端会打印「接续字符 \」。

下列可将 将换行替换为 ,

seq 3 | sed -z 's/\n/,/g'

但为什么? 因为命令行选项 -z 会将所有的换行,变成一行文本。

观察命令行选项 z 的行为
seq 3 | sed -zn 'l' (1)
1 由于运行了明确打印 (l),以命令行选项 (-n) 禁止「缺省打印」,避免混淆。
1\n2\n3\n$

= (打印行号)

printf '%s\n' a b c | sed =
1
a
2
b
3
c

sed 先打印行号,再打印原始内容,原始的一行变成二行。

列出 /etc/passwd 共有几行,选择最后行 ($),再打印行号 (=),并使用命令行选项 -n 不打印原始内容。
sed -n '$=' /etc/passwd

注:「打印行号」不会影响模式空间。

sed 高端命令

在说明高端命令之前,先说明「模式空间 (pattern space)」。

sed 的缺省动作为一次读取一行到「模式空间」,然后运行指令,接着处理下一行;继续「循环 (cycle)」直到文件结束 (EOF)。

d 删除命令是指删除「模式空间」,p 打印命令是指打印「模式空间」,sed 的「缺省打印」(命令行选项无 -n) 也是打印 (在处理所有指令后剩下的) 「模式空间」。

sed 高端命令

d

删除模式空间,然后重启循环 (cycle)。

D

删除模式空间内的第一行,并以模式空间 (删除后) 的结果「重启指令 (script)」。
注:如果 (删除前的) 模式空间不含换行,则为一般循环跟 d 命令相同 (会读取新行)。

n

(读取) 下一行拷贝至模式空间 next (copy),「缺省打印」模式空间。

N

(读取) 下一行添加至模式空间 Next (append)。

p

p (小写) 打印模式空间。

P

P (大写) 打印模式空间内的第一行。

q

离开,「缺省打印」(目前的) 模式空间。

Q

离开 (不打印)。

z

清除模式空间。

{}

命令组,一次运行多个命令,命令组格式为 { 命令 [;命令] }

#

注解

!

否定 (反动作)

注:命令行有 -n 选项时,则不会「缺省打印」。

高端命令补充
D 命令

删除模式空间内的第一行,剩余第二行(含以后),若模式空间只有一行则全部删除,另一种说法是「删除模式空间的内容直到第一个换行字符,若无换行时则删除全部」。
本节中将 D 的后续动作称为「重启指令」(重新运行指令),不采用通用的说法「重启循环」,避免混淆。重启指令时,sed 并不会读取新的输入行,而是按照指令运行动作。

n 命令

读取下一行拷贝至模式空间,先前的「模式空间」将会消失,该命令可「缺省打印」先前 (未读取前) 的「模式空间」。

N 命令

读取下一行添加至模式空间,另一种说法是「先添加换行至模式空间,再将读取行添加至模式空间」。
模式空间的结果为:「目前 (上次) 的模式空间」 + \n + 读取行 (本次读取的模式空间)」。

P (大写) 命令

打印模式空间内的第一行,若模式空间只有一行也会打印,另一种说法是「打印模式空间内的第一个字直到换行字符,若无换行则打印全部」。

命令范例:
seq 5 | sed 'n;l;d'
范例结果:
1
2$
3
4$
5
流程说明
  1. 在每个循环的开始,sed 读取一行至模式空间 (在第一循环为 1)。

  2. 命令 n「缺省打印」上次模式空间(在第一循环为 1),命令 n 读取下一行至模式空间 (在第一循环为 2)。

  3. 命令 l 明确打印模式空间时加入尾端标示 (在第一循环为 2$)。

  4. 命令 d 重启循环。

  5. 在下一个循环中,sed 读取一行至模式空间 (例如 3),命令 n 读取下一行至模式空间 (例如 4)。

将范例中如下修改
seq 5 | sed -n 'n;p;d'
结果为打印双数行
2
4
实际上并不需要以命令 d 来重启循环,sed 的缺省动作已经会循环。
seq 5 | sed -n 'n;p'
在上述的「流程说明」步骤说明如下
  1. 非文件结束 (EOF) 时继续循环。

n;p 为下一行打印,那么打印 3 倍数行则为「下一行再下一行然后打印」,指令为 n;n;p (亦可采用 选择行号0~3 p)。
打印单数行为「先打印再读取下一行」,指令为 p;n (或采用选择行号的 1~2 p)。

在 sed 的 sed 4.7 (含以上) 版本,可在命令行中加入 --debug 选项,可得知进程情况
seq 5 | sed -n 'n;p' --debug
SED PROGRAM:
  n
  p
INPUT:   'STDIN' line 1
PATTERN: 1
COMMAND: n
PATTERN: 2
COMMAND: p
2
END-OF-CYCLE:
INPUT:   'STDIN' line 3
PATTERN: 3
COMMAND: n
PATTERN: 4
COMMAND: p
4
END-OF-CYCLE:
INPUT:   'STDIN' line 5
PATTERN: 5
COMMAND: n (1)
END-OF-CYCLE:
1 打印双数行在打印第 4 行后的循环;在第 5 行运行 n;p 时,在运行 n 命令之后已经 EOF,sed 会中断循环并结束。

多行处理

N 命令示例 (加入行号)
printf '%s\n' a b c | sed = | sed -e 'N; s/\n/ /'
1 a
2 b
3 c
  1. 第一个 sed,先将每一行,打印行号 (=) 成为二行,行号在第一行,原始内容的第一行变成第二行。

  2. 第二个 sed,以命令 N 将两行合并成一行,两行中间为换行 (\n),再以命令 s 将换行替换为空格。

N; s/\n/ / 流程说明
  1. 在每个循环的开始,sed 读取一行至模式空间 (在第一循环为 1)。

  2. 命令 N 读取下一行 (第一循环为 a),添加至模式空间 (第一循环为 1\na)。

  3. 命令 s 将换行替换为空格 (第一循环为 1 a),运行后「缺省打印」1 a

  4. 非文件结束 (EOF) 时继续循环。

  5. 在下一个循环中,sed 读取一行至模式空间 (例如 2),命令 N 读取下一行 (例如 b),添加至模式空间 (例如 2\nb)。

N、D 命令示例
seq 5 | sed 'N;l;D'
1\n2$
2\n3$
3\n4$
4\n5$
5
  1. sed 将第一行读入模式空间(即为 1)。

  2. 在每个进程的开始,命令 N 添加至模式空间(第一个流程即为 1\n2)。

  3. 命令 l 打印模式空间的内容 (第一个流程即为 1\n2$)。

  4. 命令 D 删除模式空间内的第一行(在第一个流程结束时模式空间留下 2)。
    然后「重启进程」 (并不会读取新行,由第 2 步骤开始运行)。

  5. 在下一个流程中,命令 N 添加至模式空间(例如 2\n3)。

  6. 整个进程只有一个循环,循环结束时模式空间留下 5

N、P、D 命令示例 (删除重复的行)
printf '%s\n' 1A 2B 2B 3C | sed -n  '$!N; /^\(.*\)\n\1$/!P; D' &&\
printf '%s\n' 1A 2B 2B 3C | sed -En '$!N; /^(.*)\n\1$/!P; D'
1A
2B
3C
1A
2B
3C
N、P、D 命令说明
printf '%s\n' 1A 2B 2B 3C | sed -n '$!N; /^\(.*\)\n\1$/!l; D'
1A\n2B$
2B\n3C$
3C$
  1. sed 将第一行读入模式空间(即为 1A)。

  2. 在每个进程的开始,命令 $!N 在非最后行时添加至模式空间(第一个流程即为 1A\n2B)。

  3. /^\(.*\)\n\1$/ 将两组文本以 \n 区分,匹配两组相同文本的「行号」,!l 行号不匹配时运行明确打印(第一个流程打印 1A\n2B$,若命令为 P 则打印 1A)。

  4. 命令 D 删除模式空间内的第一行(在第一个流程结束时模式空间留下 2B),然后「重启进程」。

  5. 在第二个流程中命令 $!N 添加模式空间(例如 2B\n2B),行号匹配不运行 !l,命令 D(模式空间留下 2B)。

  6. 在第三个流程中命令 $!N 添加模式空间(例如 2B\n3C),行号不匹配运行 !l(第三个流程打印 2B\n3C$),命令 D(模式空间留下 3C)。

  7. 在第四个流程中,已为最后一行不运行 $!N(模式空间为 3C),行号不匹配运行 !l(第四个流程打印 3C$)。

  8. 运行命令 D 时,模式空间不含换行字符为一般循环并且清除模式空间,sed 结束进程。

一个包含人名、年龄的记录,打印年龄为 38 的人名
(
cat << EOF
Name:John
Year-old:38

Name:Marry
Year-old:43

Name:Helen
Year-old:38
EOF
) |
sed -n '/Name/ {
     N
     /Year-old:38/ !d
     P
}'
Name:John
Name:Helen
  1. /Name/ 匹配行号 Name

  2. N 添加模式空间(第一个流程即为 Name:John\nYear-old:38)。

  3. /Year-old:38/ 匹配行号年龄为 38,若不是则运行 d 重启循环。

  4. P 打印模式空间的第一行(第一个流程打印 Name:John)。

  5. 在下一个循环中,年龄为 43,行号不匹配,运行 d 重启循环。


空间操作

sed 空间操作命令

h

将模式空间拷贝到保持空间 hold (copy)。

H

将模式空间添加至保持空间 Hold (append)。

g

将保持空间拷贝到模式空间 get pattern space (copy)。

G

将保持空间添加至模式空间 Get pattern space (append)。

x

交换空间 (交换保持空间和模式空间互换) exchange。

空间操作命令补充
H 命令

将模式空间添加至保持空间,另一种说法是「添加换行至保持空间,再将模式空间添加至保持空间」。
保持空间的结果为:「目前 (上次) 的保持空间」 + \n + 模式空间」。

G 命令

将保持空间添加至模式空间,另一种说法是「添加换行至模式空间,再将保持空间添加至模式空间」。
模式空间的结果为:「目前 (上次) 的模式空间」 + \n + 保持空间」。

空间操作示例
echo -e '1\n\n3\n4\n5' | sed -n '/./{H;$!d}; x; l'
\n1$
\n3\n4\n5$

第一个表达式 /./{H;$!d} 对所有具值的 (非空) 行运行「命令组」,将 (当前的) 模式空间添加到保持空间,非最后一行时删除模式空间并重新循环。
「选择行号」后面只能接一个命令,采用「{} 命令组」,将多个命令「组合」成一个。

  1. 第 1 行具值,模式空间为 1 添加到保持空间 (H) 为 \n1 (保持空间初始为空,添加至下一行)。非最后行 ($!),由 d 删除模式空间并重启循环。

  2. 第 2 行是空行,第一个表达式不会运行动作。
    再来 sed 运行交换空间 (x),将保持空间 (\n1) 跟模式空间 (NUL) 互换,互换后;保持空间为 NUL,模式空间为 \n1
    再明确打印模式空间 (l) 为 \n1$

  3. 第 3 行具值,模式空间为 3,命令 H 添加保留空间后为 \n3 (在前一步骤保持空间为 NUL),非最后行重启循环。

  4. 第 4 行具值,模式空间为 4,命令 H 添加保留空间后为 \n3\n4,非最后行重启循环。

  5. 第 5 行具值,模式空间为 5,命令 H 添加保留空间后为 \n3\n4\n5,最后行不重启循环。

  6. 再来 sed 运行 x; l 输出 \n3\n4\n5$ 后结束进程。

将上述的示例修改如下
echo -e '1\n\n3\n4\n5' | sed '/./{H;$!d}; x; s/^/\nSTART-->/; s/$/\n<--END/'
结果为按空白行分段
START-->
1
<--END

START-->
3
4
5
<--END

如何倒序?
采用 G 将保持空间添加至模式空间,当保持空间是前一行时,添加后则为反排。(先添加再保留)。

先确认想法,是否可行
seq 3 |  sed -n 'G;h;l' (1)
1 在测试 sed 的命令时,跟前例一样;以 l 明确打印比较清楚。
1\n$
2\n1\n$
3\n2\n1\n$

但每次打印,并不正确。

改成最后一行才印
seq 3 |  sed -n 'G;h;$l'
3\n2\n1\n$

大致正确,但上述的第一行也加入换行 (因为是反排,所以是最后字符)。

改成,只在第二笔 (含) 以后才添加至模式空间,也就是第一笔不添加。
seq 3 |  sed -n '1! G; h; $l'
3\n2\n1$
或者正向思考,第一行只做保留,第二行以后;添加后再保留,结果也一样。
seq 3 |  sed -n '1h; 2,${G;h}; $l'

分支

分支 (branch),意思是跳跃 (jump) 或者是 go to。

sed 分支命令

:label

定义 label 为一个或多个字母作为标签

b

(无条件) 分支。

t

s 命令合乎匹配时分支。

T

s 命令不匹配时分支。

btT 命令后面可接标签,命令跟标签之间可为 0 个或多个空白。分支至标签时依据指令运行动作,但如果省略标签,则分支命令会重启循环 (sed 的缺省动作会读取新的输入行)。

千位分隔符号范例
确定替换命令 (s) 正确处理千位符号
echo '12345678' | sed    's/\B[0-9]\{3\}\b/,&/' &&\
echo '12345678' | sed -E 's/\B[0-9]{3}\b/,&/' &&\
echo '123'      | sed    's/\B[0-9]\{3\}\b/,&/' &&\
echo '123'      | sed -E 's/\B[0-9]{3}\b/,&/'
12345,678
12345,678
123
123
以分支完成所有的千位符号
echo '12345678' | sed    ':X s/\B[0-9]\{3\}\b/,&/; t X' &&\
echo '12345678' | sed -E ':X s/\B[0-9]{3}\b/,&/; t X'
12,345,678
12,345,678
流程如下:
  1. 第一次匹配 678,模式空间为 12345,678,合乎匹配分支到 (t) 标签 X,继续匹配。

  2. 第二次匹配 345,模式空间为 12,345,678,合乎匹配分支到标签 X 并继续。

  3. 第三次不合乎匹配,不分支并结束进程。

以分支运行匹配条件错误范例
echo '12345678' | sed ':X s/[0-9]{3}\b/,&/; t X' --debug
流程如下:
  1. 第 1 次匹配 678,模式空间为 12345,678,合乎匹配分支到 (t) 标签 X,继续匹配。

  2. 第 2 次匹配 345,模式空间为 12,345,678,合乎匹配分支到标签 X 并继续。

  3. 第 3 次匹配 345,模式空间为 12,,345,678,合乎匹配分支到标签 X 并继续。

  4. 第 4 次匹配 345,模式空间为 12,,,345,678,合乎匹配分支到标签 X 并继续。

  5. sed 一直在运行没有结束,这表示了发生了「无穷循环 (infinite loop)」,可采用命令行选项 --debug 来调试。

由上述的第 3 次流程中已知,匹配条件有错误。当发生无穷循环时,应先确定替换命令 (s) 有正确处理。

替换命令匹配条件错误 (参阅:正规表示法-边界):
echo ',345,' | sed 's/[0-9]{3}\b/,&/'
,,345,

将所有行合并至模式空间。

seq 3 | sed -n ':X; N; $! bX; l'
1\n2\n3$
  1. :X 创建标签 X

  2. N 下一行添加至模式空间

  3. $! 非最后一行,bX (无条件) 分支到标签 X,继续运行指令。

上述的范例,即为命令行选项 -z
seq 3 | sed -nz 'l'
1\n2\n3\n$
将换行替换成 ,
seq 3 | sed -z           's/\n/,/g' &&\
seq 3 | sed ':X; N; $!bX; s/\n/,/g'
1,2,3,1,2,3

读写命令 (rRwW)

sed 读写命令

r

读取整个文件,插入到输出流中。

R

依次读取文件一行,插入到输出流中。

w

将模式空间写入文件。

W

将模式空间内的第一行写入文件。

rR

在循环结束时或在读取下一个输入时,将文件内容插入到输出流中。

  • 不会影响模式空间

  • 一律打印,忽略命令行选项 -n

  • 若无法读取文件,则将其视为一个空档,并不会出现错误。

读取整个文件 (命令 r),如输出 /etc/passwd 3 次
seq 3 | sed -n 'r /etc/passwd'
依次读取文件一行 (命令 R),如输出 /etc/passwd 前 3 行
seq 3 | sed -n 'R /etc/passwd'
wW

打开的文件会不断添加,直到 sed 命令结束。

将模式空间写入 (命令 w) 到标准输出
seq 3 | sed -n 'N; N; w /dev/stdout'
1
2
3
将模式空间内的第一行写入 (命令 W) 到标准输出,如模式空间每次读取 3 行:
seq 9 | sed -n 'N; N; W /dev/stdout'
1
4
7
采用命令 w 输出 1 4 7。
seq 9 | sed -n -e 'w /dev/stdout' -e 'n;n'

在文件名称之后的命令,必须使用选项 -e 分开表达式。

测试「特殊字符」转译

确定哪些「特殊字符」在 BRE、ERE 需要以转义字符 (\) 转译。

选一个确定不需要转译的字符
specChar='a'
确定 bash 变量转换正确:
echo "3${specChar}14" \| sed -e "s/3\\${specChar}14/3\.14159/" &&\
echo "3${specChar}14" \| sed -e "s/3${specChar}14/3.14159/" &&\
echo "3${specChar}14" \| sed -E "s/3\\${specChar}14/3\.14159/" &&\
echo "3${specChar}14" \| sed -E "s/3${specChar}14/3.14159/"
3a14 | sed -e s/3\a14/3\.14159/
3a14 | sed -e s/3a14/3.14159/
3a14 | sed -E s/3\a14/3\.14159/
3a14 | sed -E s/3a14/3.14159/
测试命令:
echo "3${specChar}14" | sed -e "s/3\\${specChar}14/3\.14159/" &&\
echo "3${specChar}14" | sed -e "s/3${specChar}14/3.14159/" &&\
echo "3${specChar}14" | sed -E "s/3\\${specChar}14/3\.14159/" &&\
echo "3${specChar}14" | sed -E "s/3${specChar}14/3.14159/"
测试结果:
3a14    (BRE 转译)
3.14159 (BRE 不转译)
3a14    (ERE 转译)
3.14159 (ERE 不转译)
BRE ERE 不转译跟转译后相同的字符「! @ # % & , - " =」:
specChar='!'
specChar='@'
specChar='#'
specChar='%'
specChar='&'
specChar='_'
specChar=','
specChar='-'
specChar='"'
specChar='='

3.14159 (BRE 转译)
3.14159 (BRE 不转译)
3.14159 (ERE 转译)
3.14159 (ERE 不转译)
BRE ERE 一律要转译的字符「. $ * ^ / \ [ ]」:
specChar='.'

3.14159 (BRE 转译)
3.14159 (BRE 不转译)
3.14159 (ERE 转译)
3.14159 (ERE 不转译)

不转译也正确,但因为「.」为匹配字符,应纳入「BRE ERE 一律要转译的字符」。

specChar='$'

3.14159 (BRE 转译)
3.14159 (BRE 不转译)
3.14159 (ERE 转译)
3$14    (ERE 不转译)

specChar='*'

3.14159
3*3.14159
3.14159
3*3.14159

specChar='^'

3.14159
3.14159
3.14159
3^14

specChar='/' (定界字符)

3.14159 (BRE 转译)
Error   (BRE 不转译)
3.14159 (ERE 转译)
Error   (ERE 不转译)

specChar='\'

3.14159
Error
3.14159
Error

specChar='['

3.14159
Error
3.14159
Error

specChar=']'

3.14159
3.14159
3.14159
3.14159

BRE ERE 不转译跟转译后结果相同,但为了配合「[」,将「]」纳入「BRE ERE 一律要转译的字符」。
测试转译斜线 (/),将 sed 的定界字符改为 _
specChar='/'

echo "3${specChar}14" | sed -e "s_3\\${specChar}14_3\.14159_" &&\
echo "3${specChar}14" | sed -e "s_3${specChar}14_3.14159_" &&\
echo "3${specChar}14" | sed -E "s_3\\${specChar}14/3_.14159_" &&\
echo "3${specChar}14" | sed -E "s_3${specChar}14_3.14159_"
3.14159 (BRE 转译)
3.14159 (BRE 不转译)
3/14    (ERE 转译)
3.14159 (ERE 不转译)
BRE ERE 一律不转译的字符「` ' < >」:
specChar='`'

3`14    (BRE 转译)
3.14159 (BRE 不转译)
3`14    (ERE 转译)
3.14159 (ERE 不转译) 

specChar="'"

3'14
3.14159
3'14
3.14159

specChar='<'

3<14
3.14159
3<14
3.14159

specChar='>'

3>14
3.14159
3>14
3.14159
BRE 不转译 ERE 要转译的字符 「+ ? | ( ) { }」:
specChar='+'

3+14    (BRE 转译)
3.14159 (BRE 不转译)
3.14159 (ERE 转译)
3+14    (ERE 不转译)

specChar='?'

3?3.14159
3.14159
3.14159
3?3.14159

specChar='|'

3.14159|14
3.14159
3.14159
3.14159|14

specChar='('

Error
3.14159
3.14159
Error

specChar=')'

Error
3.14159
3.14159
Error

specChar='{'

Error
3.14159
3.14159
Error

specChar='}'

3.14159
3.14159
3.14159
3.14159

BRE ERE 不转译跟转译后结果相同,但为了配合「{」,将「}」纳入「BRE 不转译 ERE 要转译的字符」。