SED手册
发表于 : 2006-12-05 11:52
SED 手册
目录:
1. Introduction
何时使用 sed
何处获得 sed
sed 能做那些编辑动作
sed 如何工作
使用 sed
执行命令列上的编辑指令
sed 的编辑指令
地址(address)参数的表示法
有那些函数参数
执行档案内的编辑指令
执行多个文件檔的编辑
执行输出的控制
范例
替换文件中的数据
搬动文件中的数据
删除文件中的数据
搜寻文件中的数据
介绍函数参数
s
d
a
i
c
p
l
r
w
y
!
n
q
=
#
N
D
P
h
H
g
G
x
b
t
附录 A : 常用的 regular expression
附录 B : HP-UX Release 9.01 与 SunOS 5.4 内 sed 对 regular expression 中各种特殊字符的接受能力
参考数据
批注
Introduction
________________________________________
1.Introduction
Sed(Stream EDitor)为 UNIX 系统上提供将编辑工作自动化的编辑器 , 使用者无需直接编辑数据。使用者可利用 sed 所提供 20 多种不同的函数参数 , 组合(批注 [1])它们完成不同的编辑动作。此外 , 由于 sed 都以行为单位编辑文件 , 故其亦是行编辑器(line editor)。
一般 sed 最常用在编辑那些需要不断重复某些编辑动作的文件上 , 例如将文件中的某个字符串替换成另一个字符串等等。这些相较于一般 UNIX 编辑器(交谈式的, 如 vi、emacs)用手动的方式修改文件 , sed 用起来较省力。下面几节将分别介绍:
• 何时使用 sed
• 何处获得 sed
• sed 能做那些编辑动作
• sed 如何工作
1.1 何时使用 sed
在修改文件时 , 如果不断地重复某些编辑动作 , 则可用 sed 自动一次执行这些编辑动作。例如要使 received 文件内 1000 封电子信件内的发信人属名 "Tom" 改成 "John" , 此时只要在命令列上执行一简单的 sed 命令就可把档内所有的 "Tom" 字符串替换成 "John"。
再者 , 当文件需要许多不同编辑动作时 , 则 sed 一次可执行那些不同的编辑动作。例如 sed 能一次执行完将文件中所有空白行删除、替换字符串、并将使用者输入的文字添加在文件的第六行等等不同的编辑动作。
1.2 何处获得 sed
一般的 UNIX 系统 , 本身即附有 sed。不同的 UNIX 系统所附的 sed 版本亦不尽相同。若读者所使用的 UNIX 系统上未附有 sed , 则可透过 anonymous ftp 到下列地方去取得 :
phi.sinica.edu.tw:/pub/GNU/gnu
gete.sinica.edu.tw:/unix/gnu
ftp.edu.tw:/UNIX/gnu
ftp.csie.nctu.edu.tw:/pub/Unix/GNU
ftp.fcu.edu.tw: /pub3/UNIX/gnu
axp350.ncu.edu.tw:/Packages/gnu
leica.ccu.edu.tw :/pub2/gnu
mail.ncku.edu.tw :/pub/unix/gnu
bbs.ccit.edu.tw :/pub1/UNIX/gnu
prep.ai.mit.edu.tw:/pub/gnu
1.3 sed 能做那些编辑动作
sed 可删除(delete)、改变(change)、添加(append)、插入(insert)、合并、交换文件中的数据行 , 或读入其它文件的数据到文件中 , 也可替换(substuite)它们其中的字符串、或转换(tranfer)其中的字母等等。例如将文件中的连续空白行删成一行、 "local" 字符串替换成 "remote" 、"t" 字母转换成 "T"、将第 10 行数据与第 11 数据合并等。
1.4 sed 如何工作
如同其它 UNIX 命令 , sed 由标准输入读入编辑文件并由标准输出送出结果。下图表示 sed 将资料行 "Unix" 替换成 "UNIX" ,
在图中 , 上方 standard input 为标准输入 , 是读取数据之处 ; standard output 为标准输出 , 是送出结果之处 ; 中间 sed 方块的下面两个虚线方块表示 sed 的工作流程。其中 , 左边虚线方块表示 sed 将标准输入数据置入 pattern space , 右边虚线方块表示 sed 将 pattern space 中编辑完毕后的数据送到标准输出。
在虚线方块中 , 两个实线方块分别表示 pattern space 与 sed script。其中 , pattern space 为一缓冲区 , 它是 sed 工作场所 ; 而 sed script 则表示一组执行的编辑指令。
在图中, 左边虚线方块 "Unix" 由标准输入置入 pattern space ; 接着 , 在右边虚线方块中 , sed 执行 sed script 中的编辑指令 s/Unix/UNIX/ (批注 [2]) , 结果 "Unix" 被替换成 "UNIX" , 之后 , "UNIX" 由 pattern space 送到标准输出。
总合上述所言 , 当 sed 由标准输入读入一行数据并放入 pattern space 时 , sed 依照 sed script 的编辑指令逐一对 pattern space 内的数据执行编辑 , 之后 , 再由 pattern space 内的结果送到标准输出 , 接着再将下一行数据读入。如此重复执行上述动作 , 直至读完所有数据行为止。
总合上述所言 , 当 sed 由标准输入读入一行数据并放入 pattern space 时 , sed 依照 sed script 的编辑指令逐一对 pattern space 内的数据执行编辑 , 之后 , 再由 pattern space 内的结果送到标准输出 , 接着再将下一行数据读入。如此重复执行上述动作 , 直至读完所有数据行为止。
使用 sed
Sed 命令列可分成编辑指令与文件文件部份。其中 , 编辑指令负责控制所有的编辑工作 ; 檔檔表示所处理的档案。sed 的编辑指令均由位址(address)与函数(function)两部份组成 , 其中 , 在执行时 , sed 利用它的地址参数来决定编辑的对象;而用它的函数参数(批注[3])编辑。
此外 , sed 编辑指令 , 除了可在命令列上执行 , 也可在档案内执行。其中差别只是在命令列上执行时 , 其前必须加上选项 -e ; 而在档案(批注[4])内时 , 则只需在其文件名前加上选项 -f。另外 , sed 执行编辑指令是依照它们在命令列上或檔内的次序。
下面各节 , 将介绍执行命令列上的编辑指令 、sed 编辑指令、执行档案内的编辑指令、执行多个档案的编辑、及执行 sed 输出控制。
2.1 执行命令列上的编辑指令
2.2 sed 编辑指令
2.3 执行档案内的编辑指令
2.4 执行多个档案的编辑
2.5 执行 sed 输出控制
2.1.执行命令列上的编辑指令
当编辑指令(参照[section 2.2])在命令列上执行时 , 其前必须加上选项 -e 。其命令格式如下 :
sed -e '编辑指令1' -e '编辑指令2' ... 文件檔
其中 , 所有编辑指令都紧接在选项 -e 之后 , 并置于两个 " ' " 特殊字符间。另外 , 命令上编辑指令的执行是由左而右。
一般编辑指令不多时 , 使用者通常直接在命令上执行它们。例如 , 删除 yel.dat 内 1 至 10 行数据 , 并将其余文字中的 "yellow" 字符串改成 "black" 字符串。此时 , 可将编辑指令直接在命令上执行 , 其命令如下 :
sed -e '1,10d' -e 's/yellow/black/g' yel.dat
在命令中 , 编辑指令 '1,10d'(批注[5])执行删除 1 至 10 行数据 ; 编辑指令 's/yellow/black/g'(批注[6]) , "yellow" 字符串替换(substuite)成 "black" 字符串。
2.2 sed 的编辑指令
sed 编辑指令的格式如下 :
[address1[,address2]]function[argument]
其中 , 地址参数 address1 、address2 为行数或 regular expression 字符串 , 表示所执行编辑的数据行 ; 函数参数 function[argument] 为 sed 的内定函数 , 表示执行的编辑动作。
下面两小节 , 将仔细介绍地址参数的表示法与有哪些函数参数供选择。
2.2.1 地址(address)参数的表示法
实际上 , 地址参数表示法只是将要编辑的数据行 , 用它们的行数或其中的字符串来代替表示它们。下面举几个例子说明(指令都以函数参数 d(参照[section4.2]) 为例) :
删除文件内第 10 行数据 , 则指令为 10d。
删除含有 "man" 字符串的数据行时 , 则指令为 /man/d。
删除档内第 10 行到第 200 行数据, 则指令为 10,200d。
删除档内第 10 行到含 "man" 字符串的数据行 , 则指令为 10,/man/d。
接下来 , 以地址参数的内容与其个数两点 , 完整说明指令中位址参数的表示法(同样也以函数参数 d 为例)。
地址参数的内容:
地址为十进制数 : 此数字表示行数。当指令执行时 , 将对符合此行数的数据执行函数参数指示的编辑动作。例如 , 删除数据文件中的第 15 行数据 , 则指令为 15d(参照[section4.2])。其余类推 ,如删除数据文件中的第 m 行数据 , 则指令为 md 。
地址为 regular expression(参照[附录 A]):
当数据行中有符合 regular expression 所表示的字符串时 , 则执行函数参数指示的编辑动作。另外 , 在 regular expression 前后必须加上 "/"。例如指令为 /t.*t/d , 表示删除所有含两 "t" 字母的数据行。其中 , "." 表示任意字符; "*" 表示其前字符可重复任意次 , 它们结合 ".*" 表示两 "t" 字母间的任意字符串。
地址参数的个数 : 在指令中 , 当没有地址参数时 , 表示全部数据行执行函数参数所指示的编辑动作; 当只有一地址参数时 , 表示只有符合地址的数据行才编辑 ; 当有两个地址参数 , 如 address1,address2 时 , 表示对数据区执行编辑 , address1 代表起始数据行 , address2 代表结束资料行。对于上述内容 , 以下面例子做具体说明。
例如指令为
d
其表示删除文件内所有数据行。
例如指令为
5d
其表示删除文件内第五行资料。
例如指令为
1,/apple/d
其表示删除资料区 , 由檔内第一行至内有 "apple" 字符串的数据行。
例如指令为
/apple/,/orange/d
其表示删除资料区 , 由檔内含有 "apple" 字符串至含有 "orange" 字符串的数据行
2.2.2 有那些函数(function)参数
下页表仲介绍所有 sed 的函数参数(参照[chapter 4])的功能。
函数参数 功能
: label 建立 script file 内指令互相参考的位置。
# 建立批注
{ } 集合有相同位址参数的指令。
! 不执行函数参数。
= 印出资料行数( line number )。
a\ 添加使用者输入的数据。
b label 将执行的指令跳至由 : 建立的参考位置。
c\ 以使用者输入的数据取代数据。
d 删除数据。
D 删除 pattern space 内第一个 newline 字母 \ 前的数据。
g 拷贝数据从 hold space。
G 添加资料从 hold space 至 pattern space 。
h 拷贝数据从 pattern space 至 hold space 。
H 添加资料从 pattern space 至 hold space 。
l 印出 l 资料中的 nonprinting character 用 ASCII 码。
i\ 插入添加使用者输入的数据行。
n 读入下一笔资料。
N 添加下一笔资料到 pattern space。
p 印出资料。
P 印出 pattern space 内第一个 newline 字母 \ 前的数据。
q 跳出 sed 编辑。
r 读入它檔内容。
s 替换字符串。
t label 先执行一替换的编辑指令 , 如果替换成牛p>;则将编辑指令跳至 : label 处执行。
w 写资料到它文件内。
x 交换 hold space 与 pattern space 内容。
y 转换(transform)字符。
虽然 , sed 只有上表所述几个拥有基本编辑功能的函数 , 但由指令中位址参数和指令与指令间的配合 , 也能使 sed 完成大部份的编辑任务。
2.3 执行档案内的编辑指令
当执行的指令太多 , 在命令列上撰写起来十分混乱 , 此时 , 可将这些指令整理储存在档案(譬如檔名为 script_file )内 , 用选项 -f script_file , 则让 sed 执行 script_file 内的编辑指令。其命令的格示如下 :
sed -f script_file 文件檔
其中 , 执行 script_file 内编辑指令的顺序是由上而下。例如上一节的例子 , 其可改成如下命令:
sed -f ysb.scr yel.dat
其中 , ysb.scr 檔的内容如下 :
1,10d
s/yellow/black/g
另外 , 在命令列上可混合使用选项 -e 与 -f , sed 执行指令顺序依然是由命令列的左到右, 如执行至 -f 后档案内的指令 , 则由上而下执行。
2.4 执行多个文件檔的编辑
在 sed 命令列上 , 一次可执行编辑多个檔檔 , 它们跟在编辑指令之后。例如 , 替换 white.dat、red.dat、black.dat 文件内的 "yellow" 字符串成 "blue" , 其命令如下:
sed -e 's/yellow/blue/g' white.dat red.dat black.dat
上述命令执行时 , sed 依 white.dat、red.dat、black.dat 顺序 , 执行编辑指令 s/yellow/blue/(请参照[section 4.1] , 进行字符串的替换。
2.5.执行输出的控制
在命令列上的选项 -n (批注[7]) 表示输出由编辑指令控制。由前章内容得知 , sed 会 "自动的" 将数据由 pattern space 输送到标准输出檔。但借着选项 -n , 可将 sed 这 "自动的" 的动作改成 "被动的" 由它所执行的编辑指令(批注[8])来决定结果是否输出。
由上述可知 , 选项 -n 必须与编辑指令一起配合 , 否则无法获得结果。例如 , 印出 white.dat 文件内含有 "white" 字符串的数据行 , 其命令如下:
sed -n -e '/white/p' white.dat
上面命令中 , 选项 -n 与编辑指令 /white/p (参照[section4.6]) 一起配合控制输出。其中 , 选项 -n 将输出控制权移给编辑指令;/white/p 将数据行中含有 "white" 字符串印出屏幕。
3.范例
一般在实际使用编辑器的过程中 , 常需要执行替换文件中的字符串、搬移、删除、与搜寻数据行等等动作。当然 , 一般交谈式编辑器(如 vi、emacs)都能做得到上述功能 , 但文件一旦有大量上述编辑需求时 , 则用它们编辑十分没有效率。本章将用举例的方式说明如何用 sed 自动执行这些编辑功能。此外 , 在本章范例中 , 均以下述方式描述檔的需求 :
将文件中...数据 , 执行...(动作)
如此 , 目的是为了能将它们迅速的转成编辑指令。其中 , " ...数据" 部份 , 转成指令中的位址参数表示 ; "执行...动作" 部份 , 则转成函数参数表示 。另外 , 当 "执行...动作" 要由数个函数参数表示时 , 则可利用 "{ "与 " }" 集合这些函数参数(批注[9]) , 其指令形式如下 :
地址参数{
函数参数1
函数参数2
函数参数3
.
:
}
上述指令表示 , 将对符合地址参数的数据 , 依次执行函数参数1、函数参数2、函数参数3 ... 表示的动作。下面各节 , 分别举例说明 sed 替换数据、移动、删除数据、及搜寻数据的命令。
3.1 替换文件中的数据
3.2 搬动文件中的数据
3.3 删除文件中的数据
3.4 搜寻文件中的数据
3.1 替换文件中的数据
Sed 可替换文件中的字符串、数据行、甚至数据区。其中 , 表示替换字符串的指令中的函数参数为 s(参照[section4.1]); 表示替换数据行、或数据区的指令中的函数参数为 c(参照[section4.5])。上述情况以下面三个例子说明。上述情况以下面三个例子说明。
例一. 将文件中含 "machine" 字符串的数据行中的 "phi" 字符串 , 替换成为 "beta" 字符串。其命令列如下 :
sed -e '/machine/s/phi/beta/g' input.dat(以后文件檔都以 input.dat 代表)
例二. 将文件中第 5 行数据 , 替换成句子 "Those who in quarrels interpose, must often wipe a bloody nose."。其命令列如下
sed -e '5c\
Those must often wipe a bloody nose.
' input.dat
例三. 将文件中 1 至 100 行的资料区 , 替换成如下两行资料 :
How are you?
data be deleted!
则其命令列如下
sed -e '1,100c\
How are you?\
data be deleted!
' input.dat
3.2 搬动文件中的数据
使用者可用 sed 中的 hold space 暂存编辑中的数据、用函数参数 w(参照[section4.9])将文件数据搬动到它文件内储存、或用函数参数 r(参照[section4.8])将它檔内容搬到文件内。Hold space 是 sed 用来暂存 pattern space 内数据的缓存器 , 当 sed 执行函数参数 h、H(参照[section4.19])时 , 会将 pattern space 资料暂存到 hold space;当执行函数参数 x、g、G(参照[section4.22])时 , 会将暂存的资料取到 pattern space 。下面举三个例子说明。
例一. 将文件中的前 100 数据 , 搬到文件中第 300 后输出。其命令列如下 :
sed -f mov.scr 文件檔
mov.scr 檔的内容为
1,100{
H
d
}
300G
其中 ,
1,100{
H
d
}
它表示将文件中的前 100 数据 , 先储存(参照[section4.19])在 hold space 之后删除 ;指令 300G (参照[section4.22]) 表示 , 将 hold space 内的资料 , 添加在文件中的第 300 数据后输出。
例二. 将文件中含 "phi" 字符串的数据行 , 搬至 mach.inf 檔中储存。其命令列如下 :
sed -e '/phi/w mach.inf' 文件檔
例三. 将 mach.inf 檔内容 , 搬至文件中含 "beta" 字符串的数据行。其命令列如下 :
sed -e '/beta/r mach.inf' 文件檔
另外 , 由于 sed 是一 stream(参照[section1.4])编辑器 , 故理论上输出后的文件数据不可能再搬回来编辑。
3.3 删除文件中的数据
因为 sed 是一行编辑器 , 所以 sed 很容易删除个别数据行或整个数据区。一般用函数参数 d(参照[section4.2])或 D(参照[section4.17]) 来表示。下面举两个例子说明。
将檔内所有空白行全部删除。其命令列为
sed -e '/^$/d' 文件檔
regular expression(批注[附录 A]) , ^$ 表示空白行。 其中 , ^ 限制其后字符串必须在行首; $ 限制其前字符串必须在行尾。
将文件内连续的空白行 , 删除它们成为一行。其命令列为
sed -e '/^$/{
N
/^$/D
}' 文件檔
其中 , 函数参数 N(参照[section4.16])表示 , 将空白行的下一行资料添加至 pattern space 内。函数参数 /^$/D 表示 , 当添加的是空白行时 , 删除第一行空白行 , 而且剩下的空白行则再重新执行指令一次。指令重新执行一次 , 删除一行空白行 , 如此反复直至空白行后添加的为非空白行为止 , 故连续的空白行最后只剩一空白行被输出。
3.4 搜寻文件中的数据
Sed 可以执行类似 UNIX 命令 grep 的功能。理论上 , 可用 regular expression(参照[附录 A])。例如 , 将檔中含有 "gamma" 字符串的数据行输出。则其命令列如下:
sed -n -e '/gamma/p' 文件檔
但是 , sed 是行编辑器 , 它的搜寻基本上是以一行为单位。因此 , 当一些字符串因换行而被拆成两部份时 , 一般的方法即不可行。此时 , 就必须以合并两行的方式来搜寻这些数据。其情况如下面例子:
例. 将文件中含 "omega" 字符串的数据输出。其命令列如下
sed -f gp.scr 文件檔
gp.scr 檔的内容如下 :
/omega/b
N
h
s/.*\n//
/omega/b
g
D
在上述 sed script(批注[10]), 因借着函数参数 b 形成类似 C 语言中的 case statement 结构 , 使得 sed 可分别处理当数据内含 "omega" 字符串 ; 当 "omega" 字符串被拆成两行 ; 以及数据内没有"omega" 字符串的情况。接下来就依上述的三种情况 , 将 sed script 分成下面三部份来讨论。
当数据内含 "omega" , 则执行编辑指令
/omega/b
它表示当资料内含 "omega" 字符串时 , sed 不用再对它执行后面的指令 , 而直接将它输出。
当数据内没有"omega" , 则执行编辑指令如下
N
h
s/.*\n//
/omega/b
其中 , 函数参数 N(参照[section 4.16]) , 它表示将下一行资料读入使得 pattern space 内含前后两行数据。函数参数 h(参照[section 4.19]) , 它表示将 pattern space 内的前后两行资料存入 hold space 。函数参数 s/.*\n// , 它表示将 pattern space 内的前后两行资料合并(批注[11])成一行。/omega/b , 它表示如果合并后的数据内含 "omega" 字符串 , 则不用再执行它之后的指令 , 而将此数据自动输出 ;
当合并后的数据依旧不含 "omega" , 则执行编辑指令如下
g
D
其中 , 函数参数 g(参照[section4.21]) , 它表示将 hold space 内合并前的两行资料放回 pattern space。 函数参数 D(参照[section4.17]) , 它表示删除两行资料中的第一行资料 , 并让剩下的那行数据 , 重新执行 sed script。如此 , 无论的资料行内或行间的字符串才可搜寻完全。
介绍函数参数
本章将以一节一个函数参数的方式 ,介绍所有 sed 提供的函数参数 , 其中有
| s | d | a | i | c | p | l | r | w | y | ! | n | q | = | # | N | D | P | h | H | g | G | x | b | t |
另外 , 在各节中 , 首先简单介绍函数参数功能 , 接着说明函数参数与地址参数配合的格式 , 而其中也一并描述 sed 执行此函数参数的工作情形。
4.1 s
函数参数 s 表示替换(substitute)文件内字符串。其指令格式如下 :
[address1[ ,address2]] s/pattern/replacemen/[flag]
对上述格式有下面几点说明 :
函数参数 s 最多与两个地址参数配合。
关于 "s/pattern/replacement/[flag]"(批注[12]) 有下面几点说明:
pattern : 它为 reguler expression 字符串。它表示文件中要被替换的字符串。
replacement : 它为一般字符串。但其内出现下列字符有特别意义 :
& : 代表其前 pattern 字符串。例如
sed -e 's/test/& my car/' 资料文件名
指令中 , & 代表 pattern 字符串 "test"。故执行后 , 数据文件的 "test" 被替换成 "test my car"。
\n : 代表 pattern 中被第 n 个 \( 、\)(参照[附录 A]) 所括起来的字符串。例如
sed -e 's/\(test\) \(my\) \(car\)/[\2 \3 \1]/' 资料文件名
指令中 , \1 表示 "test"、\2 表示 "my"、\1 表示 "car" 字符串。故执行后 , 数据文件的 "test my car" 被替换成 "[my car test]"。
\ : 可用它来还原一些特殊符号(如上述的 & 与 \ )本身字面上的意义 , 或用它来代表换行。
flag : 主要用它来控制一些替换情况 :
当 flag 为 g 时 , 代表替换所有符合(match)的字符串 。
当 flag 为十进制数 m 时 , 代表替换行内第 m 个符合的字符串。
当 flag 为 p 时 , 代表替换第一个符合 pattern 的字符串后 , 将数据输出标准输出文件。
当 flag 为 w wfile 时 , 代表替换第一个符合 pattern 的字符串后 , 输出到 wfile 檔内(如果 wfile 不存在 , 则会重新开启名为 wfile 的档案)。
当没有 flag 时 , 则将资料行内第一个符合 pattern 的字符串以 replacement 字符串来替换 。
delimiter : 在 "/pattern/replace/[flag] " 中 "/" 被当成一 delimiter。除了空白(blank)、换行(newline) 之外 , 使用者可用任何字符作为 delimiter。例如下述编辑指令
s#/usr#/usr1#g
上述命令中 \verb|#| 为 delimiter。如果用 "/" 做 delimiter , 则 sed 会将 pattern 与 replacement 中的 "/" 当成 delimiter 而发生错误。
范例:
题目 : 替换 input.dat 檔(后面如果没有特别指定 , 均假设檔檔名为 input.dat)内 "1996" 字符串成 "1997" , 同时将这些数据行存入 year97.dat 檔内。
说明 : 用函数参数 s 指示 sed 将 "1996" 字符串替换成 "1997" , 另外用 s argument 中的 flag w 指示 sed 将替换过的资料行存入 year97.dat 檔内。
sed 命令列:
sed -e 's/1996/1997/w year97.dat' input.dat
4.2 d
函数参数 d 表示删除数据行 , 其指令格式如下:
[address1[ ,address2]] d
对上述格式有下面几点说明:
函数参数 d 最多与两个地址参数配合。
sed 执行删除动作情况如下 :
将 pattern space 内符合地址参数的数据删除。
将下一笔资料读进 pattern space 。
重新执行 sed script。
范例 : 可参考 section 3.3。
4.3 a
函数参数 a 表示将资料添加到文件中。其指令格式如下:
[address1] a\ 使用者所输入的数据
对上述格式有下面几点说明:
函数参数 a 最多与一个地址参数配合。
函数参数 a 紧接着 "\" 字符用来表示此行结束 , 使用者所输入的数据必须从下一行输入。如果数据超过一行 , 则须在每行的结尾加入"\"。
sed 执行添加动作情况如下 : 当 pattern space 内数据输出后 , sed 跟着输出使用者所输入的数据。
范例 :
题目: 添加 "多任务操作系统" 在含 "UNIX" 字符串的数据行后。假设 input.dat 檔的内容如下 :
UNIX
说明: 用函数参数 a 将所输入的数据添加在含 "UNIX" 字符串的数据行后。
sed 命令列如下 :
sed -e '/UNIX/a\
多任务操作系统
' input.dat
执行上述命令后 , 其输出结果如下 :
UNIX
多任务操作系统
4.4 i
函数参数 i 表示将资料插入文件中。其指令格式如下:
[address1] i\ 使用者所输入的数据
对上述格式有下面几点说明:
函数参数 i 最多与一个地址参数配合。
函数参数 i 紧接着 "\" 字符用来表示此行结束 , 使用者所输入的数据必须从下一行输入。如果数据超过一行 , 则须在每行的结尾加入"\"。
sed 执行插入动作的情况如下 : 在 pattern space 内数据输出前 , sed 先输出使用者所输入的数据。
范例 :
题目: 将 "文章版权属于中央研究院" 插在 input.dat 檔中含 "院长 : 囗囗囗" 的数据行之前。假设 input.dat 檔内容如下 :
院长 : 囗囗囗
说明: 用函数参数 i 将数据行 "文章版权属于中央研究院" 插在含 "院长 : 囗囗囗" 的数据行之前。
sed 命令列如下:
sed -e '/院长 : 囗囗囗/i\
文章版权属于中央研究院
' input.dat
执行上述命令后的输出如下 :
文章版权属于中央研究院
院长 : 囗囗囗
4.5 c
函数参数 c 表示改变文件中的数据。其格式如下:
[address1[ ,address2]]c\ 使用者所输入的数据
对上述格式有下面几点说明:
函数参数 c 最多与两个地址参数配合。
函数参数 c 紧接着 "\" 字符用来表示此行结束 , 使用者所输入的数据必须从下一行输入。如果数据超过一行 , 则须在每行的结尾加入"\"。
sed 执行改变动作的情况 : 在 pattern space 内数据输出时 , sed 改变它成为使用者所输入的数据。
范例 : 参考 section 3.1 之例二、三。
4.6 p
函数参数 p 表示印出资料。其指令格式如下 :
[address1[ , address2]] p
对于上述格式有下面几点说明 :
函数参数 p 最多与两个地址参数配合。
sed 执行印出动作的情况如下 : sed 拷备一份 pattern space 内容至标准输出檔。
范例 : 参考 section 3.4 开头的内容。
4.7 l
函数参数 l , 除可将资料中的 nonprinting character 以 ASCII码列出外 , 其于均与函数参数 p 相同。例如 , 将下面 input.dat 檔中的 ^[ 以 ASCII 码印出
The Great ^[ is a movie starring Steve McQueen.
执行命令 sed -e 'l' input.dat 后 , 则输出结果如下 :
The Great \003 is a movie starring Steve McQueen.
The Great is a movie starring Steve McQueen.
上述第二行数据为 sed 的自动输出(请参照批注[]。
4.8 r
函数参数 r 表示读入它档案内容到檔中。其指令格式如下 :
[address1] r 它檔名称
对于上述格式有下面几点说明 :
函数参数 r 最多与一个地址参数配合。
在指令中 , 函数参数 r 与它檔名称间 , 只能有一空格。
sed 执行读入动作的情况如下 : 在 pattern space 内数据输出后 , sed 读出它檔的内容跟着输出。当它檔不存在时 , sed 照样执行其它指令而不会有任何错误讯息产生。
范例 : 参考 section 3.1 之例三。
4.9 w
函数参数 w 表示将檔中的写到它檔内。其指令格式如下 :
[address1[ ,address2]] w 它檔名称
对于上述格式有下面几点说明 :
函数参数 w 最多与两个地址参数配合。
在指令中 , 函数参数 w 与它檔名称间 , 只能有一空格。
sed 执行写出动作的情况如 : 将 pattern space 内资料写到它文件内。数据写入时 , 会取代(overwrite)原来档案内的数据。另外 , 当它檔不存在时 , sed 会重新产生(creat)它。
范例:参考 section 3.1 之例二。
4.10 y
函数参数 y 表示转换数据中的字符。其指令格式如下 :
[address1[ ,address2]]y /xyz.../abc.../
对于上述格式有下面几点说明 :
函数参数最多配合两个地址参数。
指令中 , /abc.../xyz.../(x、y、z、a、b、c 代表某些字符) 为 y 的 argument 。其中 abc... 与 xyz... 的字符个数必须相同。
sed 执行转换时 , 将 pattern space 内数据内的 a 字符转换成 x 字符 、b 字符转换成 y 字符 、c 字符转换成 z 字符 ...。
范例:
题目: 将 input.dat 文件中的小写字母改成大写。假设 input.dat 檔的内容如下 :
Sodd's Second Law:
Sooner or later, the worst possible set of
circumstances is bound to occur.
说明:利用函数参数 y 指示 sed 做字母大小的转换。
sed 命令列如下 :
sed -e '
y/abcdefghijklmnopqrstuvwxyz/ABCDEFGHIJKLMNOPQRSTUVWXYZ/
' input.dat
执行上述命令输出结果如下 :
SODD'S SECOND LAW:
SOONER OR LATER, THE WORST POSSIBLE SET OF
CIRCUMSTANCES IS BOUND TO OCCUR.
4.11 !
函数参数 ! 表示不执行函数参数。当有如下指令时 ,
[address1[ , address2]] ! 函数参数
表示 , 对符合地址参数之数据不执行函数参数。例如删除 , 除了含 "1996" 字符串 , 所有数据行 , 则执行如下命令
sed -e '/1996/!d' input.dat
4.12 n
函数参数 n 表示读入下一行资料。其指令格式如下:
[address1[ ,address2]] n
对上述格式有下面几点说明 :
函数参数 n 最多配合两个地址参数。
sed 执行读入下一行动作的情况如下 :
输出在 pattern space 的数据。
将下一笔资料读到 pattern space。
执行下一个编辑指令。
范例(可与[section4.18]中的范例比较):
题目 : 输出 input.dat 文件内偶数行资料。假设 input.dat 檔内容如下:
The
UNIX
Operation
System
说明: 在命令列上
以选项 -n , 将数据输出的控制权(参照[section2.5])转给指令。
利用函数参数 n 将下一行数据(偶数行)取代 pattern space 内的资料行(奇数行)。
利用函数参数 p 将 pattern space 内的数据(偶数行)输出。
最后 , 整个输出只有原先文件内的偶数行数据。
sed 命令列如下 :
sed -n -e 'n' -e 'p' infro.dat
执行上述命令后 , 输出的结果如下 :
UNIX
System
4.13 q
函数参数 q 表示跳离 sed 。其指令格式如下:
[address1] q
对上述格式有下面几点说明 :
函数参数 q 最多配合一个地址参数。
sed 执行跳离动作时 , 它停止输入 pattern space 数据 , 同时停止数据送到标准输出文件。
范例 :
题目: 对文件文件执行 script_file 内的编辑指令 , 除非遇到 "Linux" 字符串。
说明: 无论 script_file 内是何种指令 , 使用者只要在命令列上用指令/Linux/q , 函数参数 q 会强迫 sed 遇到 "Linux" 时做跳离动作。
sed 命令列如下 :
sed -e '/Linux/q' -f script_file input.dat
4.14 =
函数参数 = 表示印出资料的行数。其指令格式如下:
[address1 ,[address2]] =
对上述格式有下面几点说明 :
函数参数 = 最多配合两个地址参数。
执行时 , 行数将在数据输出前先输出。
范例 :
题目: 印出 input.dat 文件内资料行数。假设 input.dat 的内容如下 :
The UNIX
Operating System
说明 : 用函数参数 = 来印出资料的行数。
sed 命令列如下 :
sed -e '=' input.dat
执行上述命令后 , 输出的结果如下 :
1
The UNIX
2
Operating System
4.15 #
在 script file 内 , 函数参数 # 后的文字为注解。当注解文字超过多行时 , 其行间须以 "\" 换行字符相隔。
4.16 N
函数参数 N 表示添加下一笔资料在 pattern space 内。其指令格式如下:
[address1 ,[address2]] N
对上述格式有下面几点说明 :
函数参数 N 最多配合两个地址参数。
sed 执行时 , 将下一行数据读入并添加在 pattern space 内 , 数据行间以换行字符(embedded newline character)分隔。此外 , 在替换时 , 换行字符可用 \n 来 match。
范例 :
题目: 将下述两行数据合并。假设 input.dat 的内容如下 :
The UNIX
Operating System
说明 : 先利用函数参数 N 将两行数据置于 pattern space 内 , 在利用函数参数 s/\n/ / 将两行数据间的分隔号 \n 以空白替代 , 如此两行数据变成一行输出。
sed 命令列如下 :
sed -e 'N' -e 's/\n/ /' input.dat
执行上述命令后 , 其输出的结果如下:
The UNIX Operating System
4.17 D
函数参数 D 表示删除 pattern space 内的第一行资料。其指令格式如下:
[address1,address2]D
对上述格式有下面几点说明 :
函数参数 D 最多配合两个地址参数。
函数参数 D 与 d 的比较如下 :
当 pattern space 内只有一数据行时 , D 与 d 作用相同。
当 pattern space 内有多行资料行时
D 表示只删除 pattern space 内第一行资料 ; d 则全删除。
D 表示执行删除后 , pattern space 内不添加下一笔数据 , 而将剩下的数据重新执行 sed script ; d 则读入下一行后执行 sed script。
范例 : 参考 section 3.3 的第二个例子。
4.18 P
函数参数 P 表示印出 pattern space 内的第一行资料。其指令格式如下:
[address1,address2] P
对上述格式有下面几点说明 :
函数参数 P 最多配合两个地址参数。
P 与 p , 除了面对的 pattern space 内的数据行数不同外 , 其它均相同。
范例(可与[section4.12]中的范例):
题目 : 输出 input.dat 文件内奇数行资料。假设 input.dat 檔内容如下:
The
UNIX
System
说明: 在命令列上
以选项 -n , 将数据输出的控制权(参照[section2.5])转给指令。
利用函数参数 N 将偶数行添加至 pattern space 内奇数行后。
利用函数参数 P 将 pattern space 内的第一行(奇数行)输出。
在奇数行输出后 , pattern space 内剩下的数据行(偶数行)则被放弃输出。最后 , 整个输出只有原先的奇数行数据。
sed 命令列 :
sed -n -e 'N' -e 'P' infro.dat
执行上述命令后 , 输出的结果如下 :
The
System
4.19 h
函数参数 h 表示暂存 pattern space 的资料至 hold space。其指令格式如下:
[address1 ,[address2]] h
对上述格式有下面几点说明 :
函数参数 h 最多配合两个地址参数。
sed 执行暂存动作时 , 会盖掉(overwrite) hold space 内原来的数据。
当 sed 全部执行结束时 , hold space 内数据会自动清除。
范例 :参考 section 3.4 的例子。
4.20 H
函数参数 H 与 h 唯一差别是 , sed 执行 h 时 , 数据盖掉(overwrite) hold space 内原来的数据 , 而 H , 数据则是 "添加(append)" 在 hold space 原来数据后。例题请参考 section 3.2 之例一。
4.21 g
函数参数 g 表示与函数参数 h 相反的动作 , 它表示将 hold space 内资料放回 pattern space 内。其指令格式如下 :
[address1,address2]g
函数参数 g 最多配合两个地址参数。
sed 执行放回动作时 , 数据盖掉(overwrite)(批注[13]) pattern space 内原来的数据。
例题 :参考 section 3.4 的例子。
4.22 G
函数参数 G 与 g 唯一差别是 , sed 执行 g 时 , 数据盖掉(overwrite) pattern space 内原来的数据 , 而 G , 数据则是 "添加(append)" 在 pattern space 原来数据后。例子请参考 section 3.2 例一。
4.23 x
函数参数 x 表示交换 hold space 与 pattern space 内的数据。其指令格式如下 :
[address1 ,[address2]] x
函数参数 x 大部份与其它处理 hold space 的函数参数一起配合。例如 , 将 input.dat 文件内第 1 行资料取代第 3 行资料。此时 , 用函数参数 h 与 x 来配合。其中 , 以函数参数 h 将第 1 资料存入 hold space ; 当第 3 行数据出现在 pattern space , 以函数参数 x 交换 hold space 与 pattern space 的内容。如此 , 第 3 行资料就被第 1 资料替代。其命令列如下:
sed -e '1h' -e '3x' input.dat
4.24 b、:label
函数参数 : 与函数参数 b 可在 sed script 内建立类似 BASIC 语言中 GOTO 指令的功能。其中 , 函数参数 : 建立标记;函数参数 b 将下一个执行的指令 branch 到标记处执行。函数参数 : 与 b , 在 script file 内配合的情况如下
.
.
.
编辑指令m1
:记号
编辑指令m2
.
.
.
[address1,[address2]]b [记号]
其中 , 当 sed 执行至指令 [address1,[address2]]b [记号] 时 , 如 pattern space 内的数据符合地址参数 , 则 sed 将下一个执行的位置 branch 至由 :记号(批注[14])设定的标记处 , 也就是再由 "编辑指令m2" ... 执行。另外 , 如果指令中函数参数 b 后没有记号 , 则 sed 将下一个执行的指令 branch 到 script file 的最后 , 利用此可使 sed script 内有类似 C 语言中的 case statement 结构。
范例 :
题目: 将 input.dat 文件内数据行的开头字母重复印 40 次。假设 input.dat 檔的内容如下 :
A
B
C
说明: 用指令 b p1 与 :p1 构成执行增加字母的循环(loop) , 同时在字母出现 40 个时 , 也用指令 b 来跳出循环。下面就以文件内第一行数据 "A" 为例 , 描述它如何连续多添加 39 个 "A" 在同一行:
用指令 s/A/AA/(参照 section4.1)将 "A" 替换成 "AA"。
用指令 b p1 与 :p1 构成循环(loop) , 它目的使上述动作被反复的执行。每执行一次循环 , 则数据行上的 "A" 就多出一个。例如 , 第一次循环数据行变成 "AA" , 第二次循环资料行变成 "AAA" ...。
用指令 [ABC]\{40\}/b(批注[15]) 来作为停止循环的条件。当数据行有连续 40 个 A 出现时 , 函数参数 b 将执行的指令跳到最后 , 停止对此行的编辑。
同样 , 对其他数据行也如同上述的方式执行。
sed 命令列如下 :
sed -e '{
:p1
/A/s/A/AA/
/B/s/B/BB/
/C/s/C/CC/
/[ABC]\{40\}/b
b p1
}' input.dat
4.25 t
基本上 , 函数参数 t 与 函数参数 b 的功能类似 , 除了在执行 t 的 branch 前 , 会先去测试其前的替换指令有没有执行替换成功外。在 script file 内的情况如下:
.
.
.
编辑指令m1
:记号
编辑指令m2
.
.
.
s/.../.../
[address1,[address2]]t [记号]
编辑指令m3
其中 , 与函数参数 b 不同处在于 , 执行函数参数 t branch 时 , 会先检查其前一个替换指令成功与否。如成功 , 则执行 branch ; 不成功 , 则不 branch , 而继续执行下一个编辑指令 , 例如上面的编辑指令m3。
范例:
题目 : 将 input.dat 文件中资料 A1 替换成 C1、C1 替换成 B1、B1 替换成 A1。input.dat 檔的内容如下:
代号
B1
A1
B1
C1
A1
C1
说明 : input.dat 文件中全部数据行只需要执行一次替换动作 , 但为避免数据被替换多次 , 所以利用函数参数 t 在 sed script 内形成一类似 C 语言中 case statement 结构 , 使每行数据替换一次后能立即用函数参数 t 跳离替换编辑。
sed 命令列 :
sed -e '{
s/A1/C1/
t
s/C1/B1/
t
s/B1/A1/
t
}' input.dat
常用的 regular expression
--------------------------------------------------------------------------------
常用的 regular expression
普通字符 由普通字符所组成的 regular expression 其意义与原字符串字面意义相同。
^字符串 限制字符串必须出现于行首 。
$字符串 限制字符串必须出现行尾。
. 表示任意一字符。
[...] 字符集合, 用以表示两中括号间所有字符当中的任一个 ,如 [^...]表示两中括号间所有字符以外的字符。
-& 字符集合中可用"&"指定字符的范围。
* 用以形容其前的字符(或字符集合)可重复任意多次 。
\n 表示嵌入新行字符(imbedded new line character)。
\(...\) 于 regular expression 中使用"\(" "\)"来括住一部份的 regular expression ; 其后可用"\1"来表示第一个被"\(" "\)"括住的部份。若 regular expression 中使用数次的"\(" "\)"来括住不同的部份 , 则依次使用"\1","\2","\3",...(最多可到"\9"。
另外 , 在不同平台上 , regular expression 会有一些不同的限制 , 详细情况参照 appendix B。
HP-UX Release 9.01 与 SunOS 5.4 内 sed 对 regular expression 中各种特殊字符的接受能力
regular expression 的特殊字符 HP-UX Release 9.01 SunOS 5.4
. 接受 接受
* 接受 接受
^ 接受 接受
$ 接受 接受
\ 接受 接受
[ ] 接受 接受
\( \) 与 \1 ... \9 合用 接受 接受
\{重复次数\} 接受 接受
\{下限,上限\} 接受 接受
\下限,\} 接受 接受
\< \>; 不接受 接受
+ 不接受 不接受
? 不接受 不接受
| 不接受 不接受
( ) 不接受 不接受
& 接受 接受
批注
批注一.
就是后面将会提到的 sed script。
批注二.
指令 s/Unix/UNIX/ 表示将 "Unix" 替换成 "UNIX"。请参照 section 4.1。
批注三.
在指令中有 20 几个函数参数可供选择。
批注四.
以后这档案称作 script file。
批注五.
编辑指令 1,10d 中 , 地址参数为 1,10 , 故 1 至 10 行的数据执行函数参数 d 所指定的删除动作。
批注六.
编辑指令 s/yellow/black/g 中 , 由于没有地址参数 , 故所有的数据行都要执行函数参数 s/yellow/black/g 所指定替换动作。在函数参数 s/yellow/black/g 中 , /yellow/black/g 为 s 的 argument , 其表示替换资料行中所有的 "yellow" 成 "black"。
批注七.
其命令格式如下 :
sed -n [-e 编辑指令].. [-f script_file].. [文件檔..]
批注八.
这些编辑指令中的函数参数可能是 p、l、s 的其中之一。
批注九.
在有些情况下 , 也可用编辑指令代替函数参数。例如 section3.3 之例二。
批注十.
这里 , sed script 是指 gp.scr 檔的内容。它表示这一次 sed 执行的编辑指令。
批注十一.
此函数参数 , 表示替换掉(除掉) pattern space 内两行间的换行记号。 故 pattern space 内只有一行资料。
批注十二.
/pattern/replacement/[flag] 为函数参数 s 的 argument。
批注十三.
注意此时 , 虽然资料是放回 pattern space , 但 hold space 的内容还是不变。
批注十四.
注意 ":" 与记号间不可有空格。
批注十五.
地址参数 [ABC]\{40\} , 表示 40 个 A 字母或 40 个 B 字母或 40 个 C 字母。其中 [ABC] 表示 "A" 或 "B" 或 "C"; 其后的 \{40\} 表示其前的字母有 40 个。regular expression 请参照附录 A
sed使用(个人认为写的很经典)
在本文章系列中,Daniel Robbins 将为您演示如何使用功能十分强大(但常被遗忘)的 UNIX 流编辑器 sed。sed 是用批处理方式编辑文件或以十分有效的方式创建 shell 脚本以修改现有文件的理想工具。
挑选编辑器
在 UNIX 世界中有很多文本编辑器可供我们选择。思考一下 -- vi、emacs 和 jed 以及很多其它工具都会浮现在脑海中。我们都有自己已逐渐了解并且喜爱的编辑器(以及我们喜爱的组合键)。有了可信赖的编辑器,我们可以轻松处理任何数量与 UNIX 有关的管理或编程任务。
虽然交互式编辑器很棒,但却有其限制。尽管其交互式特性可以成为强项,但也有其不足之处。考虑一下需要对一组文件执行类似更改的情形。您可能会本能地运行自己所喜爱的编辑器,然后手工执行一组烦琐、重复和耗时的编辑任务。然而,有一种更好的方法。
进入 sed
如果可以使编辑文件的过程自动化,以便用“批处理”方式编辑文件,甚至编写可以对现有文件进行复杂更改的脚本,那将太好了。幸运的是,对于这种情况,有一种更好的方法 -- 这种更好的方法称为 "sed"。
sed 是一种几乎包括在所有 UNIX 平台(包括 Linux)的轻量级流编辑器。sed 有许多很好的特性。首先,它相当小巧,通常要比您所喜爱的脚本语言小很多倍。其次,因为 sed 是一种流编辑器,所以,它可以对从如管道这样的标准输入接收的数据进行编辑。因此,无需将要编辑的数据存储在磁盘上的文件中。因为可以轻易将数据管道输出到 sed,所以,将 sed 用作强大的 shell 脚本中长而复杂的管道很容易。试一下用您所喜爱的编辑器去那样做。
GNU sed
对 Linux 用户来说幸运的是,最好的 sed 版本之一恰好是 GNU sed,其当前版本是 3.02。每一个 Linux 发行版都有(或至少应该有)GNU sed。GNU sed 之所以流行不仅因为可以自由分发其源代码,还因为它恰巧有许多对 POSIX sed 标准便利、省时的扩展。另外,GNU 没有 sed 早期专门版本的很多限制,如行长度限制 -- GNU 可以轻松处理任意长度的行。
最新的 GNU sed
在研究这篇文章之时我注意到:几个在线 sed 爱好者提到 GNU sed 3.02a。奇怪的是,在 ftp.gnu.org(有关这些链接,请参阅参考资料)上找不到 sed 3.02a,所以,我只得在别处寻找。我在 alpha.gnu.org的 /pub/sed 中找到了它。于是我高兴地将其下载、编译然后安装,而几分钟后我发现最新的 sed 版本却是 3.02.80 -- 可在 alpha.gnu.org上 3.02a 源代码旁边找到其源代码。安装完 GNU sed 3.02.80 之后,我就完全准备好了。
alpha.gnu.org
alpha.gnu.org (请参阅 参考资料)是新的和实验性 GNU 源代码的所在地。然而,您还会在那里发现许多优秀、稳定的源代码。出于某种原因,不是许多 GNU 开发人员忘记将稳定的源代码移至 ftp.gnu.org,就是它们的 "beta" 期间格外长(2 年!)。例如,sed 3.02a 已有两年,甚至 3.02.80 也有一年,但它们仍不能(在 2000 年 8 月写本文章时)在 ftp.gnu.org 上获得。
正确的 sed
在本系列中,将使用 GNU sed 3.02.80。在即将出现的本系列后续文章中,某些(但非常少)最高级的示例将不能在 GNU sed 3.02 或 3.02a 中使用。如果您使用的不是 GNU sed,那么结果可能会不同。现在为什么不花些时间安装 GNU sed 3.02.80 呢?那样,不仅可以为本系列的余下部分作好准备,而且还可以使用可能是目前最好的 sed。
sed 示例
sed 通过对输入数据执行任意数量用户指定的编辑操作(“命令”)来工作。sed 是基于行的,因此按顺序对每一行执行命令。然后,sed 将其结果写入标准输出 (stdout),它不修改任何输入文件。
让我们看一些示例。头几个会有些奇怪,因为我要用它们演示 sed 如何工作,而不是执行任何有用的任务。然而,如果您是 sed 新手,那么理解它们是十分重要的。下面是第一个示例:
$ sed -e 'd' /etc/services
如果输入该命令,将得不到任何输出。那么,发生了什么?在该例中,用一个编辑命令 'd' 调用 sed。sed 打开 /etc/services 文件,将一行读入其模式缓冲区,执行编辑命令(“删除行”),然后打印模式缓冲区(缓冲区已为空)。然后,它对后面的每一行重复这些步骤。这不会产生输出,因为 "d" 命令除去了模式缓冲区中的每一行!
在该例中,还有几件事要注意。首先,根本没有修改 /etc/services。这还是因为 sed 只读取在命令行指定的文件,将其用作输入 -- 它不试图修改该文件。第二件要注意的事是 sed 是面向行的。'd' 命令不是简单地告诉 sed 一下子删除所有输入数据。相反,sed 逐行将 /etc/services 的每一行读入其称为模式缓冲区的内部缓冲区。一旦将一行读入模式缓冲区,它就执行 'd' 命令,然后打印模式缓冲区的内容(在本例中没有内容)。我将在后面为您演示如何使用地址范围来控制将命令应用到哪些行 -- 但是,如果不使用地址,命令将应用到 所有行。
第三件要注意的事是括起 'd' 命令的单引号的用法。养成使用单引号来括起 sed 命令的习惯是个好注意,这样可以禁用 shell 扩展。
另一个 sed 示例
下面是使用 sed 从输出流除去 /etc/services 文件第一行的示例:
$ sed -e '1d' /etc/services | more
如您所见,除了前面有 '1' 之外,该命令与第一个 'd' 命令十分类似。如果您猜到 '1' 指的是第一行,那您就猜对了。与第一个示例中只使用 'd' 不同的是,这一次使用的 'd' 前面有一个可选的数字地址。通过使用地址,可以告诉 sed 只对某一或某些特定行进行编辑。
地址范围
现在,让我们看一下如何指定地址 范围。在本例中,sed 将删除输出的第 1 到 10 行:
$ sed -e '1,10d' /etc/services | more
当用逗号将两个地址分开时,sed 将把后面的命令应用到从第一个地址开始、到第二个地址结束的范围。在本例中,将 'd' 命令应用到第 1 到 10 行(包括这两行)。所有其它行都被忽略。
带规则表达式的地址
现在演示一个更有用的示例。假设要查看 /etc/services 文件的内容,但是对查看其中包括的注释部分不感兴趣。如您所知,可以通过以 '#' 字符开头的行在 /etc/services 文件中放置注释。为了避免注释,我们希望 sed 删除以 '#' 开始的行。以下是具体做法:
$ sed -e '/^#/d' /etc/services | more
试一下该例,看看发生了什么。您将注意到,sed 成功完成了预期任务。现在,让我们分析发生的情况。
要理解 '/^#/d' 命令,首先需要对其剖析。首先,让我们除去 'd' -- 这是我们前面所使用的同一个删除行命令。新增加的是 '/^#/' 部分,它是一种新的 规则表达式地址。规则表达式地址总是由斜杠括起。它们指定一种模式,紧跟在规则表达式地址之后的命令将仅适用于正好与该特定模式匹配的行。
因此,'/^#/' 是一个规则表达式。但是,它做些什么呢?很明显,现在该复习规则表达式了。
规则表达式复习
可以使用规则表达式来表示可能会在文本中发现的模式。您在 shell 命令行中用过 '*' 字符吗?这种用法与规则表达式类似,但并不相同。下面是可以在规则表达式中使用的特殊字符:
字符 描述
与行首匹配
与行末尾匹配
与任一个字符匹配
将与 前一个字符的零或多个出现匹配
[ ] 与 [ ] 之内的所有字符匹配
感受规则表达式的最好方法可能是看几个示例。所有这些示例都将被 sed 作为合法地址接受,这些地址出现在命令的左边。下面是几个示例:
规则 表达式 描述
/./ 将与包含至少一个字符的任何行匹配
/../ 将与包含至少两个字符的任何行匹配
/^#/ 将与以 '#' 开始的任何行匹配
/^$/ 将与所有空行匹配
/}^/ 将与以 '}'(无空格)结束的任何行匹配
/} *^/ 将与以 '}' 后面跟有 零或多个空格结束的任何行匹配
/[abc]/ 将与包含小写 'a'、'b' 或 'c' 的任何行匹配
/^[abc]/ 将与以 'a'、'b' 或 'c' 开始的任何行匹配
在这些示例中,鼓励您尝试几个。花一些时间熟悉规则表达式,然后尝试几个自己创建的规则表达式。可以如下使用 regexp:
$ sed -e '/regexp/d' /path/to/my/test/file | more
这将导致 sed 删除任何匹配的行。然而,通过告诉 sed 打印regexp 匹配并删除不匹配的内容,而不是与之相反的方法,会更有利于熟悉规则表达式。可以用以下命令这样做:
$ sed -n -e '/regexp/p' /path/to/my/test/file | more
请注意新的 '-n' 选项,该选项告诉 sed 除非明确要求打印模式空间,否则不这样做。您还会注意到,我们用 'p' 命令替换了 'd' 命令,如您所猜想的那样,这明确要求 sed 打印模式空间。就这样,将只打印匹配部分。
有关地址的更多内容
目前为止,我们已经看到了行地址、行范围地址和 regexp 地址。但是,还有更多的可能。我们可以指定两个用逗号分开的规则表达式,sed 将与所有从匹配第一个规则表达式的第一行开始,到匹配第二个规则表达式的行结束(包括该行)的所有行匹配。例如,以下命令将打印从包含 "BEGIN" 的行开始,并且以包含 "END" 的行结束的文本块:
$ sed -n -e '/BEGIN/,/END/p' /my/test/file | more
如果没发现 "BEGIN",那么将不打印数据。如果发现了 "BEGIN",但是在这之后的所有行中都没发现 "END",那么将打印所有后续行。发生这种情况是因为 sed 面向流的特性 -- 它不知道是否会出现 "END"。
C 源代码示例
如果只要打印 C 源文件中的 main() 函数,可输入:
$ sed -n -e '/main[[:space:]]*(/,/^}/p' sourcefile.c | more
该命令有两个规则表达式 '/main[[:space:]]*(/' 和 '/^}/',以及一个命令 'p'。第一个规则表达式将与后面依次跟有任意数量的空格或制表键以及开始圆括号的字符串 "main" 匹配。这应该与一般 ANSI C main() 声明的开始匹配。
在这个特别的规则表达式中,出现了 '[[:space:]]' 字符类。这只是一个特殊的关键字,它告诉 sed 与 TAB 或空格匹配。如果愿意的话,可以不输入 '[[:space:]]',而输入 '[',然后是空格字母,然后是 -V,然后再输入制表键字母和 ']' -- Control-V 告诉 bash 要插入“真正”的制表键,而不是执行命令扩展。使用 '[[:space:]]' 命令类(特别是在脚本中)会更清楚。
好,现在看一下第二个 regexp。'/^}' 将与任何出现在新行行首的 '}' 字符匹配。如果代码的格式很好,那么这将与 main() 函数的结束花括号匹配。如果格式不好,则不会正确匹配 -- 这是执行模式匹配任务的一件棘手之事。
因为是处于 '-n' 安静方式,所以 'p' 命令还是完成其惯有任务,即明确告诉 sed 打印该行。试着对 C 源文件运行该命令 -- 它应该输出整个 main() { } 块,包括开始的 "main()" 和结束的 '}'。
既然已经触及了基本知识,我们将在后两篇文章中加快步伐。如果想看一些更丰富的 sed 资料,请耐心一些 -- 马上就有!同时,您可能想查看下列 sed 和规则表达式资源。
sed 是十分强大和小巧的文本流编辑器。在本文章系列的第二篇中,Daniel Robbins 为您演示如何使用 sed 来执行字符串替换、创建更大的 sed 脚本以及如何使用 sed 的附加、插入和更改行命令。
sed 是很有用(但常被遗忘)的 UNIX 流编辑器。在以批处理方式编辑文件或以有效方式创建 shell 脚本来修改现有文件方面,它是十分理想的工具。本文是 前一篇介绍 sed 文章的续篇。
替换!
让我们看一下 sed 最有用的命令之一,替换命令。使用该命令,可以将特定字符串或匹配的规则表达式用另一个字符串替换。下面是该命令最基本用法的示例:
$ sed -e 's/foo/bar/' myfile.txt
上面的命令将 myfile.txt 中每行第一次出现的 'foo'(如果有的话)用字符串 'bar' 替换,然后将该文件内容输出到标准输出。请注意,我说的是每行第一次出现,尽管这通常不是您想要的。在进行字符串替换时,通常想执行全局替换。也就是说,要替换每行中的 所有出现,如下所示:
$ sed -e 's/foo/bar/g' myfile.txt
在最后一个斜杠之后附加的 'g' 选项告诉 sed 执行全局替换。
关于 's///' 替换命令,还有其它几件要了解的事。首先,它是一个命令,并且只是一个命令,在所有上例中都没有指定地址。这意味着,'s///' 还可以与地址一起使用来控制要将命令应用到哪些行,如下所示:
$ sed -e '1,10s/enchantment/entrapment/g' myfile2.txt
上例将导致用短语 'entrapment' 替换所有出现的短语 'enchantment',但是只在第一到第十行(包括这两行)上这样做。
$ sed -e '/^$/,/^END/s/hills/mountains/g' myfile3.txt
该例将用 'mountains' 替换 'hills',但是,只从空行开始,到以三个字符 'END' 开始的行结束(包括这两行)的文本块上这样做。
关于 's///' 命令的另一个妙处是 '/' 分隔符有许多替换选项。如果正在执行字符串替换,并且规则表达式或替换字符串中有许多斜杠,则可以通过在 's' 之后指定一个不同的字符来更改分隔符。例如,下例将把所有出现的 /usr/local 替换成 /usr:
$ sed -e 's:/usr/local:/usr:g' mylist.txt
在该例中,使用冒号作为分隔符。如果需要在规则表达式中指定分隔符字符,可以在它前面加入反斜杠。
规则表达式混乱
目前为止,我们只执行了简单的字符串替换。虽然这很方便,但是我们还可以匹配规则表达式。例如,以下 sed 命令将匹配从 '<' 开始、到 '>' 结束、并且在其中包含任意数量字符的短语。下例将删除该短语(用空字符串替换):
$ sed -e 's/<.*>//g' myfile.html
这是要从文件除去 HTML 标记的第一个很好的 sed 脚本尝试,但是由于规则表达式的特有规则,它不会很好地工作。原因何在?当 sed 试图在行中匹配规则表达式时,它要在行中查找 最长的匹配。在我的 前一篇 sed 文章中,这不成问题,因为我们使用的是 'd' 和 'p' 命令,这些命令总要删除或打印整行。但是,在使用 's///' 命令时,确实有很大不同,因为规则表达式匹配的整个部分将被目标字符串替换,或者,在本例中,被删除。这意味着,上例将把下行:
<b>This</b> is what <b>I</b> meant.
变成:
meant.
我们要的不是这个,而是:
This is what I meant.
幸运的是,有一种简便方法来纠正该问题。我们不输入“'<' 字符后面跟有一些字符并以 '>' 字符结束”的规则表达式,而只需输入一个“'<' 字符后面跟有任意数量非 '>' 字符并以 '>' 字符结束”的规则表达式。这将与最短、而不是最长的可能性匹配。新命令如下:
$ sed -e 's/<[^>]*>//g' myfile.html
在上例中,'[^>]' 指定“非 '>'”字符,其后的 '*' 完成该表达式以表示“零或多个非 '>' 字符”。对几个 html 文件测试该命令,将它们管道输出到 "more",然后仔细查看其结果。
更多字符匹配
'[ ]' 规则表达式语法还有一些附加选项。要指定字符范围,只要字符不在第一个或最后一个位置,就可以使用 '-',如下所示:
'[a-x]*'
这将匹配零或多个全部为 'a'、'b'、'c'...'v'、'w'、'x' 的字符。另外,可以使用 '[:space:]' 字符类来匹配空格。以下是可用字符类的相当完整的列表:
字符类 描述
[:alnum:] 字母数字 [a-z A-Z 0-9]
[:alpha:] 字母 [a-z A-Z]
[:blank:] 空格或制表键
[:cntrl:] 任何控制字符
[:digit:] 数字 [0-9]
[:graph:] 任何可视字符(无空格)
[:lower:] 小写 [a-z]
[:print:] 非控制字符
[:punct:] 标点字符
[:space:] 空格
[:upper:] 大写 [A-Z]
[:xdigit:] 十六进制数字 [0-9 a-f A-F]
尽可能使用字符类是很有利的,因为它们可以更好地适应非英语 locale(包括某些必需的重音字符等等).
高级替换功能
我们已经看到如何执行简单甚至有些复杂的直接替换,但是 sed 还可以做更多的事。实际上可以引用匹配规则表达式的部分或全部,并使用这些部分来构造替换字符串。作为示例,假设您正在回复一条消息。下例将在每一行前面加上短语 "ralph said: ":
$ sed -e 's/.*/ralph said: &/' origmsg.txt
输出如下:
ralph said: Hiya Jim, ralph said: ralph said:
I sure like this sed stuff! ralph said:
该例的替换字符串中使用了 '&' 字符,该字符告诉 sed 插入整个匹配的规则表达式。因此,可以将与 '.*' 匹配的任何内容(行中的零或多个字符的最大组或整行)插入到替换字符串中的任何位置,甚至多次插入。这非常好,但 sed 甚至更强大。
那些极好的带反斜杠的圆括号
's///' 命令甚至比 '&' 更好,它允许我们在规则表达式中定义 区域,然后可以在替换字符串中引用这些特定区域。作为示例,假设有一个包含以下文本的文件:
foo bar oni eeny meeny miny larry curly moe jimmy the weasel
现在假设要编写一个 sed 脚本,该脚本将把 "eeny meeny miny" 替换成 "Victor eeny-meeny Von miny" 等等。要这样做,首先要编写一个由空格分隔并与三个字符串匹配的规则表达式。
'.* .* .*'
现在,将在其中每个感兴趣的区域两边插入带反斜杠的圆括号来定义区域:
'\(.*\) \(.*\) \(.*\)'
除了要定义三个可在替换字符串中引用的逻辑区域以外,该规则表达式的工作原理将与第一个规则表达式相同。下面是最终脚本:
$ sed -e 's/\(.*\) \(.*\) \(.*\)/Victor \1-\2 Von \3/' myfile.txt
如您所见,通过输入 '\x'(其中,x 是从 1 开始的区域号)来引用每个由圆括号定界的区域。输入如下:
Victor foo-bar Von oni Victor eeny-meeny Von miny Victor larry-curly Von moe Victor jimmy-the Von weasel
随着对 sed 越来越熟悉,您可以花最小力气来进行相当强大的文本处理。您可能想如何使用熟悉的脚本语言来处理这种问题 -- 能用一行代码轻易实现这样的解决方案吗?
组合使用
在开始创建更复杂的 sed 脚本时,需要有输入多个命令的能力。有几种方法这样做。首先,可以在命令之间使用分号。例如,以下命令系列使用 '=' 命令和 'p' 命令,'=' 命令告诉 sed 打印行号,'p' 命令明确告诉 sed 打印该行(因为处于 '-n' 模式)。
$ sed -n -e '=;p' myfile.txt
无论什么时候指定了两个或更多命令,都按顺序将每个命令应用到文件的每一行。在上例中,首先将 '=' 命令应用到第 1 行,然后应用 'p' 命令。接着,sed 继续处理第 2 行,并重复该过程。虽然分号很方便,但是在某些场合下,它不能正常工作。另一种替换方法是使用两个 -e 选项来指定两个不同的命令:
$ sed -n -e '=' -e 'p' myfile.txt
然而,在使用更为复杂的附加和插入命令时,甚至多个 '-e' 选项也不能帮我们的忙。对于复杂的多行脚本,最好的方法是将命令放入一个单独的文件中。然后,用 -f 选项引用该脚本文件:
$ sed -n -f mycommands.sed myfile.txt
这种方法虽然可能不太方便,但总是管用。
一个地址的多个命令
有时,可能要指定应用到一个地址的多个命令。这在执行许多 's///' 以变换源文件中的字和语法时特别方便。要对一个地址执行多个命令,可在文件中输入 sed 命令,然后使用 '{ }' 字符将这些命令分组,如下所示:
1,20{ s/[Ll]inux/GNU\/Linux/g s/samba/Samba/g s/posix/POSIX/g }
上例将把三个替换命令应用到第 1 行到第 20 行(包括这两行)。还可以使用规则表达式地址或者二者的组合:
目录:
1. Introduction
何时使用 sed
何处获得 sed
sed 能做那些编辑动作
sed 如何工作
使用 sed
执行命令列上的编辑指令
sed 的编辑指令
地址(address)参数的表示法
有那些函数参数
执行档案内的编辑指令
执行多个文件檔的编辑
执行输出的控制
范例
替换文件中的数据
搬动文件中的数据
删除文件中的数据
搜寻文件中的数据
介绍函数参数
s
d
a
i
c
p
l
r
w
y
!
n
q
=
#
N
D
P
h
H
g
G
x
b
t
附录 A : 常用的 regular expression
附录 B : HP-UX Release 9.01 与 SunOS 5.4 内 sed 对 regular expression 中各种特殊字符的接受能力
参考数据
批注
Introduction
________________________________________
1.Introduction
Sed(Stream EDitor)为 UNIX 系统上提供将编辑工作自动化的编辑器 , 使用者无需直接编辑数据。使用者可利用 sed 所提供 20 多种不同的函数参数 , 组合(批注 [1])它们完成不同的编辑动作。此外 , 由于 sed 都以行为单位编辑文件 , 故其亦是行编辑器(line editor)。
一般 sed 最常用在编辑那些需要不断重复某些编辑动作的文件上 , 例如将文件中的某个字符串替换成另一个字符串等等。这些相较于一般 UNIX 编辑器(交谈式的, 如 vi、emacs)用手动的方式修改文件 , sed 用起来较省力。下面几节将分别介绍:
• 何时使用 sed
• 何处获得 sed
• sed 能做那些编辑动作
• sed 如何工作
1.1 何时使用 sed
在修改文件时 , 如果不断地重复某些编辑动作 , 则可用 sed 自动一次执行这些编辑动作。例如要使 received 文件内 1000 封电子信件内的发信人属名 "Tom" 改成 "John" , 此时只要在命令列上执行一简单的 sed 命令就可把档内所有的 "Tom" 字符串替换成 "John"。
再者 , 当文件需要许多不同编辑动作时 , 则 sed 一次可执行那些不同的编辑动作。例如 sed 能一次执行完将文件中所有空白行删除、替换字符串、并将使用者输入的文字添加在文件的第六行等等不同的编辑动作。
1.2 何处获得 sed
一般的 UNIX 系统 , 本身即附有 sed。不同的 UNIX 系统所附的 sed 版本亦不尽相同。若读者所使用的 UNIX 系统上未附有 sed , 则可透过 anonymous ftp 到下列地方去取得 :
phi.sinica.edu.tw:/pub/GNU/gnu
gete.sinica.edu.tw:/unix/gnu
ftp.edu.tw:/UNIX/gnu
ftp.csie.nctu.edu.tw:/pub/Unix/GNU
ftp.fcu.edu.tw: /pub3/UNIX/gnu
axp350.ncu.edu.tw:/Packages/gnu
leica.ccu.edu.tw :/pub2/gnu
mail.ncku.edu.tw :/pub/unix/gnu
bbs.ccit.edu.tw :/pub1/UNIX/gnu
prep.ai.mit.edu.tw:/pub/gnu
1.3 sed 能做那些编辑动作
sed 可删除(delete)、改变(change)、添加(append)、插入(insert)、合并、交换文件中的数据行 , 或读入其它文件的数据到文件中 , 也可替换(substuite)它们其中的字符串、或转换(tranfer)其中的字母等等。例如将文件中的连续空白行删成一行、 "local" 字符串替换成 "remote" 、"t" 字母转换成 "T"、将第 10 行数据与第 11 数据合并等。
1.4 sed 如何工作
如同其它 UNIX 命令 , sed 由标准输入读入编辑文件并由标准输出送出结果。下图表示 sed 将资料行 "Unix" 替换成 "UNIX" ,
在图中 , 上方 standard input 为标准输入 , 是读取数据之处 ; standard output 为标准输出 , 是送出结果之处 ; 中间 sed 方块的下面两个虚线方块表示 sed 的工作流程。其中 , 左边虚线方块表示 sed 将标准输入数据置入 pattern space , 右边虚线方块表示 sed 将 pattern space 中编辑完毕后的数据送到标准输出。
在虚线方块中 , 两个实线方块分别表示 pattern space 与 sed script。其中 , pattern space 为一缓冲区 , 它是 sed 工作场所 ; 而 sed script 则表示一组执行的编辑指令。
在图中, 左边虚线方块 "Unix" 由标准输入置入 pattern space ; 接着 , 在右边虚线方块中 , sed 执行 sed script 中的编辑指令 s/Unix/UNIX/ (批注 [2]) , 结果 "Unix" 被替换成 "UNIX" , 之后 , "UNIX" 由 pattern space 送到标准输出。
总合上述所言 , 当 sed 由标准输入读入一行数据并放入 pattern space 时 , sed 依照 sed script 的编辑指令逐一对 pattern space 内的数据执行编辑 , 之后 , 再由 pattern space 内的结果送到标准输出 , 接着再将下一行数据读入。如此重复执行上述动作 , 直至读完所有数据行为止。
总合上述所言 , 当 sed 由标准输入读入一行数据并放入 pattern space 时 , sed 依照 sed script 的编辑指令逐一对 pattern space 内的数据执行编辑 , 之后 , 再由 pattern space 内的结果送到标准输出 , 接着再将下一行数据读入。如此重复执行上述动作 , 直至读完所有数据行为止。
使用 sed
Sed 命令列可分成编辑指令与文件文件部份。其中 , 编辑指令负责控制所有的编辑工作 ; 檔檔表示所处理的档案。sed 的编辑指令均由位址(address)与函数(function)两部份组成 , 其中 , 在执行时 , sed 利用它的地址参数来决定编辑的对象;而用它的函数参数(批注[3])编辑。
此外 , sed 编辑指令 , 除了可在命令列上执行 , 也可在档案内执行。其中差别只是在命令列上执行时 , 其前必须加上选项 -e ; 而在档案(批注[4])内时 , 则只需在其文件名前加上选项 -f。另外 , sed 执行编辑指令是依照它们在命令列上或檔内的次序。
下面各节 , 将介绍执行命令列上的编辑指令 、sed 编辑指令、执行档案内的编辑指令、执行多个档案的编辑、及执行 sed 输出控制。
2.1 执行命令列上的编辑指令
2.2 sed 编辑指令
2.3 执行档案内的编辑指令
2.4 执行多个档案的编辑
2.5 执行 sed 输出控制
2.1.执行命令列上的编辑指令
当编辑指令(参照[section 2.2])在命令列上执行时 , 其前必须加上选项 -e 。其命令格式如下 :
sed -e '编辑指令1' -e '编辑指令2' ... 文件檔
其中 , 所有编辑指令都紧接在选项 -e 之后 , 并置于两个 " ' " 特殊字符间。另外 , 命令上编辑指令的执行是由左而右。
一般编辑指令不多时 , 使用者通常直接在命令上执行它们。例如 , 删除 yel.dat 内 1 至 10 行数据 , 并将其余文字中的 "yellow" 字符串改成 "black" 字符串。此时 , 可将编辑指令直接在命令上执行 , 其命令如下 :
sed -e '1,10d' -e 's/yellow/black/g' yel.dat
在命令中 , 编辑指令 '1,10d'(批注[5])执行删除 1 至 10 行数据 ; 编辑指令 's/yellow/black/g'(批注[6]) , "yellow" 字符串替换(substuite)成 "black" 字符串。
2.2 sed 的编辑指令
sed 编辑指令的格式如下 :
[address1[,address2]]function[argument]
其中 , 地址参数 address1 、address2 为行数或 regular expression 字符串 , 表示所执行编辑的数据行 ; 函数参数 function[argument] 为 sed 的内定函数 , 表示执行的编辑动作。
下面两小节 , 将仔细介绍地址参数的表示法与有哪些函数参数供选择。
2.2.1 地址(address)参数的表示法
实际上 , 地址参数表示法只是将要编辑的数据行 , 用它们的行数或其中的字符串来代替表示它们。下面举几个例子说明(指令都以函数参数 d(参照[section4.2]) 为例) :
删除文件内第 10 行数据 , 则指令为 10d。
删除含有 "man" 字符串的数据行时 , 则指令为 /man/d。
删除档内第 10 行到第 200 行数据, 则指令为 10,200d。
删除档内第 10 行到含 "man" 字符串的数据行 , 则指令为 10,/man/d。
接下来 , 以地址参数的内容与其个数两点 , 完整说明指令中位址参数的表示法(同样也以函数参数 d 为例)。
地址参数的内容:
地址为十进制数 : 此数字表示行数。当指令执行时 , 将对符合此行数的数据执行函数参数指示的编辑动作。例如 , 删除数据文件中的第 15 行数据 , 则指令为 15d(参照[section4.2])。其余类推 ,如删除数据文件中的第 m 行数据 , 则指令为 md 。
地址为 regular expression(参照[附录 A]):
当数据行中有符合 regular expression 所表示的字符串时 , 则执行函数参数指示的编辑动作。另外 , 在 regular expression 前后必须加上 "/"。例如指令为 /t.*t/d , 表示删除所有含两 "t" 字母的数据行。其中 , "." 表示任意字符; "*" 表示其前字符可重复任意次 , 它们结合 ".*" 表示两 "t" 字母间的任意字符串。
地址参数的个数 : 在指令中 , 当没有地址参数时 , 表示全部数据行执行函数参数所指示的编辑动作; 当只有一地址参数时 , 表示只有符合地址的数据行才编辑 ; 当有两个地址参数 , 如 address1,address2 时 , 表示对数据区执行编辑 , address1 代表起始数据行 , address2 代表结束资料行。对于上述内容 , 以下面例子做具体说明。
例如指令为
d
其表示删除文件内所有数据行。
例如指令为
5d
其表示删除文件内第五行资料。
例如指令为
1,/apple/d
其表示删除资料区 , 由檔内第一行至内有 "apple" 字符串的数据行。
例如指令为
/apple/,/orange/d
其表示删除资料区 , 由檔内含有 "apple" 字符串至含有 "orange" 字符串的数据行
2.2.2 有那些函数(function)参数
下页表仲介绍所有 sed 的函数参数(参照[chapter 4])的功能。
函数参数 功能
: label 建立 script file 内指令互相参考的位置。
# 建立批注
{ } 集合有相同位址参数的指令。
! 不执行函数参数。
= 印出资料行数( line number )。
a\ 添加使用者输入的数据。
b label 将执行的指令跳至由 : 建立的参考位置。
c\ 以使用者输入的数据取代数据。
d 删除数据。
D 删除 pattern space 内第一个 newline 字母 \ 前的数据。
g 拷贝数据从 hold space。
G 添加资料从 hold space 至 pattern space 。
h 拷贝数据从 pattern space 至 hold space 。
H 添加资料从 pattern space 至 hold space 。
l 印出 l 资料中的 nonprinting character 用 ASCII 码。
i\ 插入添加使用者输入的数据行。
n 读入下一笔资料。
N 添加下一笔资料到 pattern space。
p 印出资料。
P 印出 pattern space 内第一个 newline 字母 \ 前的数据。
q 跳出 sed 编辑。
r 读入它檔内容。
s 替换字符串。
t label 先执行一替换的编辑指令 , 如果替换成牛p>;则将编辑指令跳至 : label 处执行。
w 写资料到它文件内。
x 交换 hold space 与 pattern space 内容。
y 转换(transform)字符。
虽然 , sed 只有上表所述几个拥有基本编辑功能的函数 , 但由指令中位址参数和指令与指令间的配合 , 也能使 sed 完成大部份的编辑任务。
2.3 执行档案内的编辑指令
当执行的指令太多 , 在命令列上撰写起来十分混乱 , 此时 , 可将这些指令整理储存在档案(譬如檔名为 script_file )内 , 用选项 -f script_file , 则让 sed 执行 script_file 内的编辑指令。其命令的格示如下 :
sed -f script_file 文件檔
其中 , 执行 script_file 内编辑指令的顺序是由上而下。例如上一节的例子 , 其可改成如下命令:
sed -f ysb.scr yel.dat
其中 , ysb.scr 檔的内容如下 :
1,10d
s/yellow/black/g
另外 , 在命令列上可混合使用选项 -e 与 -f , sed 执行指令顺序依然是由命令列的左到右, 如执行至 -f 后档案内的指令 , 则由上而下执行。
2.4 执行多个文件檔的编辑
在 sed 命令列上 , 一次可执行编辑多个檔檔 , 它们跟在编辑指令之后。例如 , 替换 white.dat、red.dat、black.dat 文件内的 "yellow" 字符串成 "blue" , 其命令如下:
sed -e 's/yellow/blue/g' white.dat red.dat black.dat
上述命令执行时 , sed 依 white.dat、red.dat、black.dat 顺序 , 执行编辑指令 s/yellow/blue/(请参照[section 4.1] , 进行字符串的替换。
2.5.执行输出的控制
在命令列上的选项 -n (批注[7]) 表示输出由编辑指令控制。由前章内容得知 , sed 会 "自动的" 将数据由 pattern space 输送到标准输出檔。但借着选项 -n , 可将 sed 这 "自动的" 的动作改成 "被动的" 由它所执行的编辑指令(批注[8])来决定结果是否输出。
由上述可知 , 选项 -n 必须与编辑指令一起配合 , 否则无法获得结果。例如 , 印出 white.dat 文件内含有 "white" 字符串的数据行 , 其命令如下:
sed -n -e '/white/p' white.dat
上面命令中 , 选项 -n 与编辑指令 /white/p (参照[section4.6]) 一起配合控制输出。其中 , 选项 -n 将输出控制权移给编辑指令;/white/p 将数据行中含有 "white" 字符串印出屏幕。
3.范例
一般在实际使用编辑器的过程中 , 常需要执行替换文件中的字符串、搬移、删除、与搜寻数据行等等动作。当然 , 一般交谈式编辑器(如 vi、emacs)都能做得到上述功能 , 但文件一旦有大量上述编辑需求时 , 则用它们编辑十分没有效率。本章将用举例的方式说明如何用 sed 自动执行这些编辑功能。此外 , 在本章范例中 , 均以下述方式描述檔的需求 :
将文件中...数据 , 执行...(动作)
如此 , 目的是为了能将它们迅速的转成编辑指令。其中 , " ...数据" 部份 , 转成指令中的位址参数表示 ; "执行...动作" 部份 , 则转成函数参数表示 。另外 , 当 "执行...动作" 要由数个函数参数表示时 , 则可利用 "{ "与 " }" 集合这些函数参数(批注[9]) , 其指令形式如下 :
地址参数{
函数参数1
函数参数2
函数参数3
.
:
}
上述指令表示 , 将对符合地址参数的数据 , 依次执行函数参数1、函数参数2、函数参数3 ... 表示的动作。下面各节 , 分别举例说明 sed 替换数据、移动、删除数据、及搜寻数据的命令。
3.1 替换文件中的数据
3.2 搬动文件中的数据
3.3 删除文件中的数据
3.4 搜寻文件中的数据
3.1 替换文件中的数据
Sed 可替换文件中的字符串、数据行、甚至数据区。其中 , 表示替换字符串的指令中的函数参数为 s(参照[section4.1]); 表示替换数据行、或数据区的指令中的函数参数为 c(参照[section4.5])。上述情况以下面三个例子说明。上述情况以下面三个例子说明。
例一. 将文件中含 "machine" 字符串的数据行中的 "phi" 字符串 , 替换成为 "beta" 字符串。其命令列如下 :
sed -e '/machine/s/phi/beta/g' input.dat(以后文件檔都以 input.dat 代表)
例二. 将文件中第 5 行数据 , 替换成句子 "Those who in quarrels interpose, must often wipe a bloody nose."。其命令列如下
sed -e '5c\
Those must often wipe a bloody nose.
' input.dat
例三. 将文件中 1 至 100 行的资料区 , 替换成如下两行资料 :
How are you?
data be deleted!
则其命令列如下
sed -e '1,100c\
How are you?\
data be deleted!
' input.dat
3.2 搬动文件中的数据
使用者可用 sed 中的 hold space 暂存编辑中的数据、用函数参数 w(参照[section4.9])将文件数据搬动到它文件内储存、或用函数参数 r(参照[section4.8])将它檔内容搬到文件内。Hold space 是 sed 用来暂存 pattern space 内数据的缓存器 , 当 sed 执行函数参数 h、H(参照[section4.19])时 , 会将 pattern space 资料暂存到 hold space;当执行函数参数 x、g、G(参照[section4.22])时 , 会将暂存的资料取到 pattern space 。下面举三个例子说明。
例一. 将文件中的前 100 数据 , 搬到文件中第 300 后输出。其命令列如下 :
sed -f mov.scr 文件檔
mov.scr 檔的内容为
1,100{
H
d
}
300G
其中 ,
1,100{
H
d
}
它表示将文件中的前 100 数据 , 先储存(参照[section4.19])在 hold space 之后删除 ;指令 300G (参照[section4.22]) 表示 , 将 hold space 内的资料 , 添加在文件中的第 300 数据后输出。
例二. 将文件中含 "phi" 字符串的数据行 , 搬至 mach.inf 檔中储存。其命令列如下 :
sed -e '/phi/w mach.inf' 文件檔
例三. 将 mach.inf 檔内容 , 搬至文件中含 "beta" 字符串的数据行。其命令列如下 :
sed -e '/beta/r mach.inf' 文件檔
另外 , 由于 sed 是一 stream(参照[section1.4])编辑器 , 故理论上输出后的文件数据不可能再搬回来编辑。
3.3 删除文件中的数据
因为 sed 是一行编辑器 , 所以 sed 很容易删除个别数据行或整个数据区。一般用函数参数 d(参照[section4.2])或 D(参照[section4.17]) 来表示。下面举两个例子说明。
将檔内所有空白行全部删除。其命令列为
sed -e '/^$/d' 文件檔
regular expression(批注[附录 A]) , ^$ 表示空白行。 其中 , ^ 限制其后字符串必须在行首; $ 限制其前字符串必须在行尾。
将文件内连续的空白行 , 删除它们成为一行。其命令列为
sed -e '/^$/{
N
/^$/D
}' 文件檔
其中 , 函数参数 N(参照[section4.16])表示 , 将空白行的下一行资料添加至 pattern space 内。函数参数 /^$/D 表示 , 当添加的是空白行时 , 删除第一行空白行 , 而且剩下的空白行则再重新执行指令一次。指令重新执行一次 , 删除一行空白行 , 如此反复直至空白行后添加的为非空白行为止 , 故连续的空白行最后只剩一空白行被输出。
3.4 搜寻文件中的数据
Sed 可以执行类似 UNIX 命令 grep 的功能。理论上 , 可用 regular expression(参照[附录 A])。例如 , 将檔中含有 "gamma" 字符串的数据行输出。则其命令列如下:
sed -n -e '/gamma/p' 文件檔
但是 , sed 是行编辑器 , 它的搜寻基本上是以一行为单位。因此 , 当一些字符串因换行而被拆成两部份时 , 一般的方法即不可行。此时 , 就必须以合并两行的方式来搜寻这些数据。其情况如下面例子:
例. 将文件中含 "omega" 字符串的数据输出。其命令列如下
sed -f gp.scr 文件檔
gp.scr 檔的内容如下 :
/omega/b
N
h
s/.*\n//
/omega/b
g
D
在上述 sed script(批注[10]), 因借着函数参数 b 形成类似 C 语言中的 case statement 结构 , 使得 sed 可分别处理当数据内含 "omega" 字符串 ; 当 "omega" 字符串被拆成两行 ; 以及数据内没有"omega" 字符串的情况。接下来就依上述的三种情况 , 将 sed script 分成下面三部份来讨论。
当数据内含 "omega" , 则执行编辑指令
/omega/b
它表示当资料内含 "omega" 字符串时 , sed 不用再对它执行后面的指令 , 而直接将它输出。
当数据内没有"omega" , 则执行编辑指令如下
N
h
s/.*\n//
/omega/b
其中 , 函数参数 N(参照[section 4.16]) , 它表示将下一行资料读入使得 pattern space 内含前后两行数据。函数参数 h(参照[section 4.19]) , 它表示将 pattern space 内的前后两行资料存入 hold space 。函数参数 s/.*\n// , 它表示将 pattern space 内的前后两行资料合并(批注[11])成一行。/omega/b , 它表示如果合并后的数据内含 "omega" 字符串 , 则不用再执行它之后的指令 , 而将此数据自动输出 ;
当合并后的数据依旧不含 "omega" , 则执行编辑指令如下
g
D
其中 , 函数参数 g(参照[section4.21]) , 它表示将 hold space 内合并前的两行资料放回 pattern space。 函数参数 D(参照[section4.17]) , 它表示删除两行资料中的第一行资料 , 并让剩下的那行数据 , 重新执行 sed script。如此 , 无论的资料行内或行间的字符串才可搜寻完全。
介绍函数参数
本章将以一节一个函数参数的方式 ,介绍所有 sed 提供的函数参数 , 其中有
| s | d | a | i | c | p | l | r | w | y | ! | n | q | = | # | N | D | P | h | H | g | G | x | b | t |
另外 , 在各节中 , 首先简单介绍函数参数功能 , 接着说明函数参数与地址参数配合的格式 , 而其中也一并描述 sed 执行此函数参数的工作情形。
4.1 s
函数参数 s 表示替换(substitute)文件内字符串。其指令格式如下 :
[address1[ ,address2]] s/pattern/replacemen/[flag]
对上述格式有下面几点说明 :
函数参数 s 最多与两个地址参数配合。
关于 "s/pattern/replacement/[flag]"(批注[12]) 有下面几点说明:
pattern : 它为 reguler expression 字符串。它表示文件中要被替换的字符串。
replacement : 它为一般字符串。但其内出现下列字符有特别意义 :
& : 代表其前 pattern 字符串。例如
sed -e 's/test/& my car/' 资料文件名
指令中 , & 代表 pattern 字符串 "test"。故执行后 , 数据文件的 "test" 被替换成 "test my car"。
\n : 代表 pattern 中被第 n 个 \( 、\)(参照[附录 A]) 所括起来的字符串。例如
sed -e 's/\(test\) \(my\) \(car\)/[\2 \3 \1]/' 资料文件名
指令中 , \1 表示 "test"、\2 表示 "my"、\1 表示 "car" 字符串。故执行后 , 数据文件的 "test my car" 被替换成 "[my car test]"。
\ : 可用它来还原一些特殊符号(如上述的 & 与 \ )本身字面上的意义 , 或用它来代表换行。
flag : 主要用它来控制一些替换情况 :
当 flag 为 g 时 , 代表替换所有符合(match)的字符串 。
当 flag 为十进制数 m 时 , 代表替换行内第 m 个符合的字符串。
当 flag 为 p 时 , 代表替换第一个符合 pattern 的字符串后 , 将数据输出标准输出文件。
当 flag 为 w wfile 时 , 代表替换第一个符合 pattern 的字符串后 , 输出到 wfile 檔内(如果 wfile 不存在 , 则会重新开启名为 wfile 的档案)。
当没有 flag 时 , 则将资料行内第一个符合 pattern 的字符串以 replacement 字符串来替换 。
delimiter : 在 "/pattern/replace/[flag] " 中 "/" 被当成一 delimiter。除了空白(blank)、换行(newline) 之外 , 使用者可用任何字符作为 delimiter。例如下述编辑指令
s#/usr#/usr1#g
上述命令中 \verb|#| 为 delimiter。如果用 "/" 做 delimiter , 则 sed 会将 pattern 与 replacement 中的 "/" 当成 delimiter 而发生错误。
范例:
题目 : 替换 input.dat 檔(后面如果没有特别指定 , 均假设檔檔名为 input.dat)内 "1996" 字符串成 "1997" , 同时将这些数据行存入 year97.dat 檔内。
说明 : 用函数参数 s 指示 sed 将 "1996" 字符串替换成 "1997" , 另外用 s argument 中的 flag w 指示 sed 将替换过的资料行存入 year97.dat 檔内。
sed 命令列:
sed -e 's/1996/1997/w year97.dat' input.dat
4.2 d
函数参数 d 表示删除数据行 , 其指令格式如下:
[address1[ ,address2]] d
对上述格式有下面几点说明:
函数参数 d 最多与两个地址参数配合。
sed 执行删除动作情况如下 :
将 pattern space 内符合地址参数的数据删除。
将下一笔资料读进 pattern space 。
重新执行 sed script。
范例 : 可参考 section 3.3。
4.3 a
函数参数 a 表示将资料添加到文件中。其指令格式如下:
[address1] a\ 使用者所输入的数据
对上述格式有下面几点说明:
函数参数 a 最多与一个地址参数配合。
函数参数 a 紧接着 "\" 字符用来表示此行结束 , 使用者所输入的数据必须从下一行输入。如果数据超过一行 , 则须在每行的结尾加入"\"。
sed 执行添加动作情况如下 : 当 pattern space 内数据输出后 , sed 跟着输出使用者所输入的数据。
范例 :
题目: 添加 "多任务操作系统" 在含 "UNIX" 字符串的数据行后。假设 input.dat 檔的内容如下 :
UNIX
说明: 用函数参数 a 将所输入的数据添加在含 "UNIX" 字符串的数据行后。
sed 命令列如下 :
sed -e '/UNIX/a\
多任务操作系统
' input.dat
执行上述命令后 , 其输出结果如下 :
UNIX
多任务操作系统
4.4 i
函数参数 i 表示将资料插入文件中。其指令格式如下:
[address1] i\ 使用者所输入的数据
对上述格式有下面几点说明:
函数参数 i 最多与一个地址参数配合。
函数参数 i 紧接着 "\" 字符用来表示此行结束 , 使用者所输入的数据必须从下一行输入。如果数据超过一行 , 则须在每行的结尾加入"\"。
sed 执行插入动作的情况如下 : 在 pattern space 内数据输出前 , sed 先输出使用者所输入的数据。
范例 :
题目: 将 "文章版权属于中央研究院" 插在 input.dat 檔中含 "院长 : 囗囗囗" 的数据行之前。假设 input.dat 檔内容如下 :
院长 : 囗囗囗
说明: 用函数参数 i 将数据行 "文章版权属于中央研究院" 插在含 "院长 : 囗囗囗" 的数据行之前。
sed 命令列如下:
sed -e '/院长 : 囗囗囗/i\
文章版权属于中央研究院
' input.dat
执行上述命令后的输出如下 :
文章版权属于中央研究院
院长 : 囗囗囗
4.5 c
函数参数 c 表示改变文件中的数据。其格式如下:
[address1[ ,address2]]c\ 使用者所输入的数据
对上述格式有下面几点说明:
函数参数 c 最多与两个地址参数配合。
函数参数 c 紧接着 "\" 字符用来表示此行结束 , 使用者所输入的数据必须从下一行输入。如果数据超过一行 , 则须在每行的结尾加入"\"。
sed 执行改变动作的情况 : 在 pattern space 内数据输出时 , sed 改变它成为使用者所输入的数据。
范例 : 参考 section 3.1 之例二、三。
4.6 p
函数参数 p 表示印出资料。其指令格式如下 :
[address1[ , address2]] p
对于上述格式有下面几点说明 :
函数参数 p 最多与两个地址参数配合。
sed 执行印出动作的情况如下 : sed 拷备一份 pattern space 内容至标准输出檔。
范例 : 参考 section 3.4 开头的内容。
4.7 l
函数参数 l , 除可将资料中的 nonprinting character 以 ASCII码列出外 , 其于均与函数参数 p 相同。例如 , 将下面 input.dat 檔中的 ^[ 以 ASCII 码印出
The Great ^[ is a movie starring Steve McQueen.
执行命令 sed -e 'l' input.dat 后 , 则输出结果如下 :
The Great \003 is a movie starring Steve McQueen.
The Great is a movie starring Steve McQueen.
上述第二行数据为 sed 的自动输出(请参照批注[]。
4.8 r
函数参数 r 表示读入它档案内容到檔中。其指令格式如下 :
[address1] r 它檔名称
对于上述格式有下面几点说明 :
函数参数 r 最多与一个地址参数配合。
在指令中 , 函数参数 r 与它檔名称间 , 只能有一空格。
sed 执行读入动作的情况如下 : 在 pattern space 内数据输出后 , sed 读出它檔的内容跟着输出。当它檔不存在时 , sed 照样执行其它指令而不会有任何错误讯息产生。
范例 : 参考 section 3.1 之例三。
4.9 w
函数参数 w 表示将檔中的写到它檔内。其指令格式如下 :
[address1[ ,address2]] w 它檔名称
对于上述格式有下面几点说明 :
函数参数 w 最多与两个地址参数配合。
在指令中 , 函数参数 w 与它檔名称间 , 只能有一空格。
sed 执行写出动作的情况如 : 将 pattern space 内资料写到它文件内。数据写入时 , 会取代(overwrite)原来档案内的数据。另外 , 当它檔不存在时 , sed 会重新产生(creat)它。
范例:参考 section 3.1 之例二。
4.10 y
函数参数 y 表示转换数据中的字符。其指令格式如下 :
[address1[ ,address2]]y /xyz.../abc.../
对于上述格式有下面几点说明 :
函数参数最多配合两个地址参数。
指令中 , /abc.../xyz.../(x、y、z、a、b、c 代表某些字符) 为 y 的 argument 。其中 abc... 与 xyz... 的字符个数必须相同。
sed 执行转换时 , 将 pattern space 内数据内的 a 字符转换成 x 字符 、b 字符转换成 y 字符 、c 字符转换成 z 字符 ...。
范例:
题目: 将 input.dat 文件中的小写字母改成大写。假设 input.dat 檔的内容如下 :
Sodd's Second Law:
Sooner or later, the worst possible set of
circumstances is bound to occur.
说明:利用函数参数 y 指示 sed 做字母大小的转换。
sed 命令列如下 :
sed -e '
y/abcdefghijklmnopqrstuvwxyz/ABCDEFGHIJKLMNOPQRSTUVWXYZ/
' input.dat
执行上述命令输出结果如下 :
SODD'S SECOND LAW:
SOONER OR LATER, THE WORST POSSIBLE SET OF
CIRCUMSTANCES IS BOUND TO OCCUR.
4.11 !
函数参数 ! 表示不执行函数参数。当有如下指令时 ,
[address1[ , address2]] ! 函数参数
表示 , 对符合地址参数之数据不执行函数参数。例如删除 , 除了含 "1996" 字符串 , 所有数据行 , 则执行如下命令
sed -e '/1996/!d' input.dat
4.12 n
函数参数 n 表示读入下一行资料。其指令格式如下:
[address1[ ,address2]] n
对上述格式有下面几点说明 :
函数参数 n 最多配合两个地址参数。
sed 执行读入下一行动作的情况如下 :
输出在 pattern space 的数据。
将下一笔资料读到 pattern space。
执行下一个编辑指令。
范例(可与[section4.18]中的范例比较):
题目 : 输出 input.dat 文件内偶数行资料。假设 input.dat 檔内容如下:
The
UNIX
Operation
System
说明: 在命令列上
以选项 -n , 将数据输出的控制权(参照[section2.5])转给指令。
利用函数参数 n 将下一行数据(偶数行)取代 pattern space 内的资料行(奇数行)。
利用函数参数 p 将 pattern space 内的数据(偶数行)输出。
最后 , 整个输出只有原先文件内的偶数行数据。
sed 命令列如下 :
sed -n -e 'n' -e 'p' infro.dat
执行上述命令后 , 输出的结果如下 :
UNIX
System
4.13 q
函数参数 q 表示跳离 sed 。其指令格式如下:
[address1] q
对上述格式有下面几点说明 :
函数参数 q 最多配合一个地址参数。
sed 执行跳离动作时 , 它停止输入 pattern space 数据 , 同时停止数据送到标准输出文件。
范例 :
题目: 对文件文件执行 script_file 内的编辑指令 , 除非遇到 "Linux" 字符串。
说明: 无论 script_file 内是何种指令 , 使用者只要在命令列上用指令/Linux/q , 函数参数 q 会强迫 sed 遇到 "Linux" 时做跳离动作。
sed 命令列如下 :
sed -e '/Linux/q' -f script_file input.dat
4.14 =
函数参数 = 表示印出资料的行数。其指令格式如下:
[address1 ,[address2]] =
对上述格式有下面几点说明 :
函数参数 = 最多配合两个地址参数。
执行时 , 行数将在数据输出前先输出。
范例 :
题目: 印出 input.dat 文件内资料行数。假设 input.dat 的内容如下 :
The UNIX
Operating System
说明 : 用函数参数 = 来印出资料的行数。
sed 命令列如下 :
sed -e '=' input.dat
执行上述命令后 , 输出的结果如下 :
1
The UNIX
2
Operating System
4.15 #
在 script file 内 , 函数参数 # 后的文字为注解。当注解文字超过多行时 , 其行间须以 "\" 换行字符相隔。
4.16 N
函数参数 N 表示添加下一笔资料在 pattern space 内。其指令格式如下:
[address1 ,[address2]] N
对上述格式有下面几点说明 :
函数参数 N 最多配合两个地址参数。
sed 执行时 , 将下一行数据读入并添加在 pattern space 内 , 数据行间以换行字符(embedded newline character)分隔。此外 , 在替换时 , 换行字符可用 \n 来 match。
范例 :
题目: 将下述两行数据合并。假设 input.dat 的内容如下 :
The UNIX
Operating System
说明 : 先利用函数参数 N 将两行数据置于 pattern space 内 , 在利用函数参数 s/\n/ / 将两行数据间的分隔号 \n 以空白替代 , 如此两行数据变成一行输出。
sed 命令列如下 :
sed -e 'N' -e 's/\n/ /' input.dat
执行上述命令后 , 其输出的结果如下:
The UNIX Operating System
4.17 D
函数参数 D 表示删除 pattern space 内的第一行资料。其指令格式如下:
[address1,address2]D
对上述格式有下面几点说明 :
函数参数 D 最多配合两个地址参数。
函数参数 D 与 d 的比较如下 :
当 pattern space 内只有一数据行时 , D 与 d 作用相同。
当 pattern space 内有多行资料行时
D 表示只删除 pattern space 内第一行资料 ; d 则全删除。
D 表示执行删除后 , pattern space 内不添加下一笔数据 , 而将剩下的数据重新执行 sed script ; d 则读入下一行后执行 sed script。
范例 : 参考 section 3.3 的第二个例子。
4.18 P
函数参数 P 表示印出 pattern space 内的第一行资料。其指令格式如下:
[address1,address2] P
对上述格式有下面几点说明 :
函数参数 P 最多配合两个地址参数。
P 与 p , 除了面对的 pattern space 内的数据行数不同外 , 其它均相同。
范例(可与[section4.12]中的范例):
题目 : 输出 input.dat 文件内奇数行资料。假设 input.dat 檔内容如下:
The
UNIX
System
说明: 在命令列上
以选项 -n , 将数据输出的控制权(参照[section2.5])转给指令。
利用函数参数 N 将偶数行添加至 pattern space 内奇数行后。
利用函数参数 P 将 pattern space 内的第一行(奇数行)输出。
在奇数行输出后 , pattern space 内剩下的数据行(偶数行)则被放弃输出。最后 , 整个输出只有原先的奇数行数据。
sed 命令列 :
sed -n -e 'N' -e 'P' infro.dat
执行上述命令后 , 输出的结果如下 :
The
System
4.19 h
函数参数 h 表示暂存 pattern space 的资料至 hold space。其指令格式如下:
[address1 ,[address2]] h
对上述格式有下面几点说明 :
函数参数 h 最多配合两个地址参数。
sed 执行暂存动作时 , 会盖掉(overwrite) hold space 内原来的数据。
当 sed 全部执行结束时 , hold space 内数据会自动清除。
范例 :参考 section 3.4 的例子。
4.20 H
函数参数 H 与 h 唯一差别是 , sed 执行 h 时 , 数据盖掉(overwrite) hold space 内原来的数据 , 而 H , 数据则是 "添加(append)" 在 hold space 原来数据后。例题请参考 section 3.2 之例一。
4.21 g
函数参数 g 表示与函数参数 h 相反的动作 , 它表示将 hold space 内资料放回 pattern space 内。其指令格式如下 :
[address1,address2]g
函数参数 g 最多配合两个地址参数。
sed 执行放回动作时 , 数据盖掉(overwrite)(批注[13]) pattern space 内原来的数据。
例题 :参考 section 3.4 的例子。
4.22 G
函数参数 G 与 g 唯一差别是 , sed 执行 g 时 , 数据盖掉(overwrite) pattern space 内原来的数据 , 而 G , 数据则是 "添加(append)" 在 pattern space 原来数据后。例子请参考 section 3.2 例一。
4.23 x
函数参数 x 表示交换 hold space 与 pattern space 内的数据。其指令格式如下 :
[address1 ,[address2]] x
函数参数 x 大部份与其它处理 hold space 的函数参数一起配合。例如 , 将 input.dat 文件内第 1 行资料取代第 3 行资料。此时 , 用函数参数 h 与 x 来配合。其中 , 以函数参数 h 将第 1 资料存入 hold space ; 当第 3 行数据出现在 pattern space , 以函数参数 x 交换 hold space 与 pattern space 的内容。如此 , 第 3 行资料就被第 1 资料替代。其命令列如下:
sed -e '1h' -e '3x' input.dat
4.24 b、:label
函数参数 : 与函数参数 b 可在 sed script 内建立类似 BASIC 语言中 GOTO 指令的功能。其中 , 函数参数 : 建立标记;函数参数 b 将下一个执行的指令 branch 到标记处执行。函数参数 : 与 b , 在 script file 内配合的情况如下
.
.
.
编辑指令m1
:记号
编辑指令m2
.
.
.
[address1,[address2]]b [记号]
其中 , 当 sed 执行至指令 [address1,[address2]]b [记号] 时 , 如 pattern space 内的数据符合地址参数 , 则 sed 将下一个执行的位置 branch 至由 :记号(批注[14])设定的标记处 , 也就是再由 "编辑指令m2" ... 执行。另外 , 如果指令中函数参数 b 后没有记号 , 则 sed 将下一个执行的指令 branch 到 script file 的最后 , 利用此可使 sed script 内有类似 C 语言中的 case statement 结构。
范例 :
题目: 将 input.dat 文件内数据行的开头字母重复印 40 次。假设 input.dat 檔的内容如下 :
A
B
C
说明: 用指令 b p1 与 :p1 构成执行增加字母的循环(loop) , 同时在字母出现 40 个时 , 也用指令 b 来跳出循环。下面就以文件内第一行数据 "A" 为例 , 描述它如何连续多添加 39 个 "A" 在同一行:
用指令 s/A/AA/(参照 section4.1)将 "A" 替换成 "AA"。
用指令 b p1 与 :p1 构成循环(loop) , 它目的使上述动作被反复的执行。每执行一次循环 , 则数据行上的 "A" 就多出一个。例如 , 第一次循环数据行变成 "AA" , 第二次循环资料行变成 "AAA" ...。
用指令 [ABC]\{40\}/b(批注[15]) 来作为停止循环的条件。当数据行有连续 40 个 A 出现时 , 函数参数 b 将执行的指令跳到最后 , 停止对此行的编辑。
同样 , 对其他数据行也如同上述的方式执行。
sed 命令列如下 :
sed -e '{
:p1
/A/s/A/AA/
/B/s/B/BB/
/C/s/C/CC/
/[ABC]\{40\}/b
b p1
}' input.dat
4.25 t
基本上 , 函数参数 t 与 函数参数 b 的功能类似 , 除了在执行 t 的 branch 前 , 会先去测试其前的替换指令有没有执行替换成功外。在 script file 内的情况如下:
.
.
.
编辑指令m1
:记号
编辑指令m2
.
.
.
s/.../.../
[address1,[address2]]t [记号]
编辑指令m3
其中 , 与函数参数 b 不同处在于 , 执行函数参数 t branch 时 , 会先检查其前一个替换指令成功与否。如成功 , 则执行 branch ; 不成功 , 则不 branch , 而继续执行下一个编辑指令 , 例如上面的编辑指令m3。
范例:
题目 : 将 input.dat 文件中资料 A1 替换成 C1、C1 替换成 B1、B1 替换成 A1。input.dat 檔的内容如下:
代号
B1
A1
B1
C1
A1
C1
说明 : input.dat 文件中全部数据行只需要执行一次替换动作 , 但为避免数据被替换多次 , 所以利用函数参数 t 在 sed script 内形成一类似 C 语言中 case statement 结构 , 使每行数据替换一次后能立即用函数参数 t 跳离替换编辑。
sed 命令列 :
sed -e '{
s/A1/C1/
t
s/C1/B1/
t
s/B1/A1/
t
}' input.dat
常用的 regular expression
--------------------------------------------------------------------------------
常用的 regular expression
普通字符 由普通字符所组成的 regular expression 其意义与原字符串字面意义相同。
^字符串 限制字符串必须出现于行首 。
$字符串 限制字符串必须出现行尾。
. 表示任意一字符。
[...] 字符集合, 用以表示两中括号间所有字符当中的任一个 ,如 [^...]表示两中括号间所有字符以外的字符。
-& 字符集合中可用"&"指定字符的范围。
* 用以形容其前的字符(或字符集合)可重复任意多次 。
\n 表示嵌入新行字符(imbedded new line character)。
\(...\) 于 regular expression 中使用"\(" "\)"来括住一部份的 regular expression ; 其后可用"\1"来表示第一个被"\(" "\)"括住的部份。若 regular expression 中使用数次的"\(" "\)"来括住不同的部份 , 则依次使用"\1","\2","\3",...(最多可到"\9"。
另外 , 在不同平台上 , regular expression 会有一些不同的限制 , 详细情况参照 appendix B。
HP-UX Release 9.01 与 SunOS 5.4 内 sed 对 regular expression 中各种特殊字符的接受能力
regular expression 的特殊字符 HP-UX Release 9.01 SunOS 5.4
. 接受 接受
* 接受 接受
^ 接受 接受
$ 接受 接受
\ 接受 接受
[ ] 接受 接受
\( \) 与 \1 ... \9 合用 接受 接受
\{重复次数\} 接受 接受
\{下限,上限\} 接受 接受
\下限,\} 接受 接受
\< \>; 不接受 接受
+ 不接受 不接受
? 不接受 不接受
| 不接受 不接受
( ) 不接受 不接受
& 接受 接受
批注
批注一.
就是后面将会提到的 sed script。
批注二.
指令 s/Unix/UNIX/ 表示将 "Unix" 替换成 "UNIX"。请参照 section 4.1。
批注三.
在指令中有 20 几个函数参数可供选择。
批注四.
以后这档案称作 script file。
批注五.
编辑指令 1,10d 中 , 地址参数为 1,10 , 故 1 至 10 行的数据执行函数参数 d 所指定的删除动作。
批注六.
编辑指令 s/yellow/black/g 中 , 由于没有地址参数 , 故所有的数据行都要执行函数参数 s/yellow/black/g 所指定替换动作。在函数参数 s/yellow/black/g 中 , /yellow/black/g 为 s 的 argument , 其表示替换资料行中所有的 "yellow" 成 "black"。
批注七.
其命令格式如下 :
sed -n [-e 编辑指令].. [-f script_file].. [文件檔..]
批注八.
这些编辑指令中的函数参数可能是 p、l、s 的其中之一。
批注九.
在有些情况下 , 也可用编辑指令代替函数参数。例如 section3.3 之例二。
批注十.
这里 , sed script 是指 gp.scr 檔的内容。它表示这一次 sed 执行的编辑指令。
批注十一.
此函数参数 , 表示替换掉(除掉) pattern space 内两行间的换行记号。 故 pattern space 内只有一行资料。
批注十二.
/pattern/replacement/[flag] 为函数参数 s 的 argument。
批注十三.
注意此时 , 虽然资料是放回 pattern space , 但 hold space 的内容还是不变。
批注十四.
注意 ":" 与记号间不可有空格。
批注十五.
地址参数 [ABC]\{40\} , 表示 40 个 A 字母或 40 个 B 字母或 40 个 C 字母。其中 [ABC] 表示 "A" 或 "B" 或 "C"; 其后的 \{40\} 表示其前的字母有 40 个。regular expression 请参照附录 A
sed使用(个人认为写的很经典)
在本文章系列中,Daniel Robbins 将为您演示如何使用功能十分强大(但常被遗忘)的 UNIX 流编辑器 sed。sed 是用批处理方式编辑文件或以十分有效的方式创建 shell 脚本以修改现有文件的理想工具。
挑选编辑器
在 UNIX 世界中有很多文本编辑器可供我们选择。思考一下 -- vi、emacs 和 jed 以及很多其它工具都会浮现在脑海中。我们都有自己已逐渐了解并且喜爱的编辑器(以及我们喜爱的组合键)。有了可信赖的编辑器,我们可以轻松处理任何数量与 UNIX 有关的管理或编程任务。
虽然交互式编辑器很棒,但却有其限制。尽管其交互式特性可以成为强项,但也有其不足之处。考虑一下需要对一组文件执行类似更改的情形。您可能会本能地运行自己所喜爱的编辑器,然后手工执行一组烦琐、重复和耗时的编辑任务。然而,有一种更好的方法。
进入 sed
如果可以使编辑文件的过程自动化,以便用“批处理”方式编辑文件,甚至编写可以对现有文件进行复杂更改的脚本,那将太好了。幸运的是,对于这种情况,有一种更好的方法 -- 这种更好的方法称为 "sed"。
sed 是一种几乎包括在所有 UNIX 平台(包括 Linux)的轻量级流编辑器。sed 有许多很好的特性。首先,它相当小巧,通常要比您所喜爱的脚本语言小很多倍。其次,因为 sed 是一种流编辑器,所以,它可以对从如管道这样的标准输入接收的数据进行编辑。因此,无需将要编辑的数据存储在磁盘上的文件中。因为可以轻易将数据管道输出到 sed,所以,将 sed 用作强大的 shell 脚本中长而复杂的管道很容易。试一下用您所喜爱的编辑器去那样做。
GNU sed
对 Linux 用户来说幸运的是,最好的 sed 版本之一恰好是 GNU sed,其当前版本是 3.02。每一个 Linux 发行版都有(或至少应该有)GNU sed。GNU sed 之所以流行不仅因为可以自由分发其源代码,还因为它恰巧有许多对 POSIX sed 标准便利、省时的扩展。另外,GNU 没有 sed 早期专门版本的很多限制,如行长度限制 -- GNU 可以轻松处理任意长度的行。
最新的 GNU sed
在研究这篇文章之时我注意到:几个在线 sed 爱好者提到 GNU sed 3.02a。奇怪的是,在 ftp.gnu.org(有关这些链接,请参阅参考资料)上找不到 sed 3.02a,所以,我只得在别处寻找。我在 alpha.gnu.org的 /pub/sed 中找到了它。于是我高兴地将其下载、编译然后安装,而几分钟后我发现最新的 sed 版本却是 3.02.80 -- 可在 alpha.gnu.org上 3.02a 源代码旁边找到其源代码。安装完 GNU sed 3.02.80 之后,我就完全准备好了。
alpha.gnu.org
alpha.gnu.org (请参阅 参考资料)是新的和实验性 GNU 源代码的所在地。然而,您还会在那里发现许多优秀、稳定的源代码。出于某种原因,不是许多 GNU 开发人员忘记将稳定的源代码移至 ftp.gnu.org,就是它们的 "beta" 期间格外长(2 年!)。例如,sed 3.02a 已有两年,甚至 3.02.80 也有一年,但它们仍不能(在 2000 年 8 月写本文章时)在 ftp.gnu.org 上获得。
正确的 sed
在本系列中,将使用 GNU sed 3.02.80。在即将出现的本系列后续文章中,某些(但非常少)最高级的示例将不能在 GNU sed 3.02 或 3.02a 中使用。如果您使用的不是 GNU sed,那么结果可能会不同。现在为什么不花些时间安装 GNU sed 3.02.80 呢?那样,不仅可以为本系列的余下部分作好准备,而且还可以使用可能是目前最好的 sed。
sed 示例
sed 通过对输入数据执行任意数量用户指定的编辑操作(“命令”)来工作。sed 是基于行的,因此按顺序对每一行执行命令。然后,sed 将其结果写入标准输出 (stdout),它不修改任何输入文件。
让我们看一些示例。头几个会有些奇怪,因为我要用它们演示 sed 如何工作,而不是执行任何有用的任务。然而,如果您是 sed 新手,那么理解它们是十分重要的。下面是第一个示例:
$ sed -e 'd' /etc/services
如果输入该命令,将得不到任何输出。那么,发生了什么?在该例中,用一个编辑命令 'd' 调用 sed。sed 打开 /etc/services 文件,将一行读入其模式缓冲区,执行编辑命令(“删除行”),然后打印模式缓冲区(缓冲区已为空)。然后,它对后面的每一行重复这些步骤。这不会产生输出,因为 "d" 命令除去了模式缓冲区中的每一行!
在该例中,还有几件事要注意。首先,根本没有修改 /etc/services。这还是因为 sed 只读取在命令行指定的文件,将其用作输入 -- 它不试图修改该文件。第二件要注意的事是 sed 是面向行的。'd' 命令不是简单地告诉 sed 一下子删除所有输入数据。相反,sed 逐行将 /etc/services 的每一行读入其称为模式缓冲区的内部缓冲区。一旦将一行读入模式缓冲区,它就执行 'd' 命令,然后打印模式缓冲区的内容(在本例中没有内容)。我将在后面为您演示如何使用地址范围来控制将命令应用到哪些行 -- 但是,如果不使用地址,命令将应用到 所有行。
第三件要注意的事是括起 'd' 命令的单引号的用法。养成使用单引号来括起 sed 命令的习惯是个好注意,这样可以禁用 shell 扩展。
另一个 sed 示例
下面是使用 sed 从输出流除去 /etc/services 文件第一行的示例:
$ sed -e '1d' /etc/services | more
如您所见,除了前面有 '1' 之外,该命令与第一个 'd' 命令十分类似。如果您猜到 '1' 指的是第一行,那您就猜对了。与第一个示例中只使用 'd' 不同的是,这一次使用的 'd' 前面有一个可选的数字地址。通过使用地址,可以告诉 sed 只对某一或某些特定行进行编辑。
地址范围
现在,让我们看一下如何指定地址 范围。在本例中,sed 将删除输出的第 1 到 10 行:
$ sed -e '1,10d' /etc/services | more
当用逗号将两个地址分开时,sed 将把后面的命令应用到从第一个地址开始、到第二个地址结束的范围。在本例中,将 'd' 命令应用到第 1 到 10 行(包括这两行)。所有其它行都被忽略。
带规则表达式的地址
现在演示一个更有用的示例。假设要查看 /etc/services 文件的内容,但是对查看其中包括的注释部分不感兴趣。如您所知,可以通过以 '#' 字符开头的行在 /etc/services 文件中放置注释。为了避免注释,我们希望 sed 删除以 '#' 开始的行。以下是具体做法:
$ sed -e '/^#/d' /etc/services | more
试一下该例,看看发生了什么。您将注意到,sed 成功完成了预期任务。现在,让我们分析发生的情况。
要理解 '/^#/d' 命令,首先需要对其剖析。首先,让我们除去 'd' -- 这是我们前面所使用的同一个删除行命令。新增加的是 '/^#/' 部分,它是一种新的 规则表达式地址。规则表达式地址总是由斜杠括起。它们指定一种模式,紧跟在规则表达式地址之后的命令将仅适用于正好与该特定模式匹配的行。
因此,'/^#/' 是一个规则表达式。但是,它做些什么呢?很明显,现在该复习规则表达式了。
规则表达式复习
可以使用规则表达式来表示可能会在文本中发现的模式。您在 shell 命令行中用过 '*' 字符吗?这种用法与规则表达式类似,但并不相同。下面是可以在规则表达式中使用的特殊字符:
字符 描述
与行首匹配
与行末尾匹配
与任一个字符匹配
将与 前一个字符的零或多个出现匹配
[ ] 与 [ ] 之内的所有字符匹配
感受规则表达式的最好方法可能是看几个示例。所有这些示例都将被 sed 作为合法地址接受,这些地址出现在命令的左边。下面是几个示例:
规则 表达式 描述
/./ 将与包含至少一个字符的任何行匹配
/../ 将与包含至少两个字符的任何行匹配
/^#/ 将与以 '#' 开始的任何行匹配
/^$/ 将与所有空行匹配
/}^/ 将与以 '}'(无空格)结束的任何行匹配
/} *^/ 将与以 '}' 后面跟有 零或多个空格结束的任何行匹配
/[abc]/ 将与包含小写 'a'、'b' 或 'c' 的任何行匹配
/^[abc]/ 将与以 'a'、'b' 或 'c' 开始的任何行匹配
在这些示例中,鼓励您尝试几个。花一些时间熟悉规则表达式,然后尝试几个自己创建的规则表达式。可以如下使用 regexp:
$ sed -e '/regexp/d' /path/to/my/test/file | more
这将导致 sed 删除任何匹配的行。然而,通过告诉 sed 打印regexp 匹配并删除不匹配的内容,而不是与之相反的方法,会更有利于熟悉规则表达式。可以用以下命令这样做:
$ sed -n -e '/regexp/p' /path/to/my/test/file | more
请注意新的 '-n' 选项,该选项告诉 sed 除非明确要求打印模式空间,否则不这样做。您还会注意到,我们用 'p' 命令替换了 'd' 命令,如您所猜想的那样,这明确要求 sed 打印模式空间。就这样,将只打印匹配部分。
有关地址的更多内容
目前为止,我们已经看到了行地址、行范围地址和 regexp 地址。但是,还有更多的可能。我们可以指定两个用逗号分开的规则表达式,sed 将与所有从匹配第一个规则表达式的第一行开始,到匹配第二个规则表达式的行结束(包括该行)的所有行匹配。例如,以下命令将打印从包含 "BEGIN" 的行开始,并且以包含 "END" 的行结束的文本块:
$ sed -n -e '/BEGIN/,/END/p' /my/test/file | more
如果没发现 "BEGIN",那么将不打印数据。如果发现了 "BEGIN",但是在这之后的所有行中都没发现 "END",那么将打印所有后续行。发生这种情况是因为 sed 面向流的特性 -- 它不知道是否会出现 "END"。
C 源代码示例
如果只要打印 C 源文件中的 main() 函数,可输入:
$ sed -n -e '/main[[:space:]]*(/,/^}/p' sourcefile.c | more
该命令有两个规则表达式 '/main[[:space:]]*(/' 和 '/^}/',以及一个命令 'p'。第一个规则表达式将与后面依次跟有任意数量的空格或制表键以及开始圆括号的字符串 "main" 匹配。这应该与一般 ANSI C main() 声明的开始匹配。
在这个特别的规则表达式中,出现了 '[[:space:]]' 字符类。这只是一个特殊的关键字,它告诉 sed 与 TAB 或空格匹配。如果愿意的话,可以不输入 '[[:space:]]',而输入 '[',然后是空格字母,然后是 -V,然后再输入制表键字母和 ']' -- Control-V 告诉 bash 要插入“真正”的制表键,而不是执行命令扩展。使用 '[[:space:]]' 命令类(特别是在脚本中)会更清楚。
好,现在看一下第二个 regexp。'/^}' 将与任何出现在新行行首的 '}' 字符匹配。如果代码的格式很好,那么这将与 main() 函数的结束花括号匹配。如果格式不好,则不会正确匹配 -- 这是执行模式匹配任务的一件棘手之事。
因为是处于 '-n' 安静方式,所以 'p' 命令还是完成其惯有任务,即明确告诉 sed 打印该行。试着对 C 源文件运行该命令 -- 它应该输出整个 main() { } 块,包括开始的 "main()" 和结束的 '}'。
既然已经触及了基本知识,我们将在后两篇文章中加快步伐。如果想看一些更丰富的 sed 资料,请耐心一些 -- 马上就有!同时,您可能想查看下列 sed 和规则表达式资源。
sed 是十分强大和小巧的文本流编辑器。在本文章系列的第二篇中,Daniel Robbins 为您演示如何使用 sed 来执行字符串替换、创建更大的 sed 脚本以及如何使用 sed 的附加、插入和更改行命令。
sed 是很有用(但常被遗忘)的 UNIX 流编辑器。在以批处理方式编辑文件或以有效方式创建 shell 脚本来修改现有文件方面,它是十分理想的工具。本文是 前一篇介绍 sed 文章的续篇。
替换!
让我们看一下 sed 最有用的命令之一,替换命令。使用该命令,可以将特定字符串或匹配的规则表达式用另一个字符串替换。下面是该命令最基本用法的示例:
$ sed -e 's/foo/bar/' myfile.txt
上面的命令将 myfile.txt 中每行第一次出现的 'foo'(如果有的话)用字符串 'bar' 替换,然后将该文件内容输出到标准输出。请注意,我说的是每行第一次出现,尽管这通常不是您想要的。在进行字符串替换时,通常想执行全局替换。也就是说,要替换每行中的 所有出现,如下所示:
$ sed -e 's/foo/bar/g' myfile.txt
在最后一个斜杠之后附加的 'g' 选项告诉 sed 执行全局替换。
关于 's///' 替换命令,还有其它几件要了解的事。首先,它是一个命令,并且只是一个命令,在所有上例中都没有指定地址。这意味着,'s///' 还可以与地址一起使用来控制要将命令应用到哪些行,如下所示:
$ sed -e '1,10s/enchantment/entrapment/g' myfile2.txt
上例将导致用短语 'entrapment' 替换所有出现的短语 'enchantment',但是只在第一到第十行(包括这两行)上这样做。
$ sed -e '/^$/,/^END/s/hills/mountains/g' myfile3.txt
该例将用 'mountains' 替换 'hills',但是,只从空行开始,到以三个字符 'END' 开始的行结束(包括这两行)的文本块上这样做。
关于 's///' 命令的另一个妙处是 '/' 分隔符有许多替换选项。如果正在执行字符串替换,并且规则表达式或替换字符串中有许多斜杠,则可以通过在 's' 之后指定一个不同的字符来更改分隔符。例如,下例将把所有出现的 /usr/local 替换成 /usr:
$ sed -e 's:/usr/local:/usr:g' mylist.txt
在该例中,使用冒号作为分隔符。如果需要在规则表达式中指定分隔符字符,可以在它前面加入反斜杠。
规则表达式混乱
目前为止,我们只执行了简单的字符串替换。虽然这很方便,但是我们还可以匹配规则表达式。例如,以下 sed 命令将匹配从 '<' 开始、到 '>' 结束、并且在其中包含任意数量字符的短语。下例将删除该短语(用空字符串替换):
$ sed -e 's/<.*>//g' myfile.html
这是要从文件除去 HTML 标记的第一个很好的 sed 脚本尝试,但是由于规则表达式的特有规则,它不会很好地工作。原因何在?当 sed 试图在行中匹配规则表达式时,它要在行中查找 最长的匹配。在我的 前一篇 sed 文章中,这不成问题,因为我们使用的是 'd' 和 'p' 命令,这些命令总要删除或打印整行。但是,在使用 's///' 命令时,确实有很大不同,因为规则表达式匹配的整个部分将被目标字符串替换,或者,在本例中,被删除。这意味着,上例将把下行:
<b>This</b> is what <b>I</b> meant.
变成:
meant.
我们要的不是这个,而是:
This is what I meant.
幸运的是,有一种简便方法来纠正该问题。我们不输入“'<' 字符后面跟有一些字符并以 '>' 字符结束”的规则表达式,而只需输入一个“'<' 字符后面跟有任意数量非 '>' 字符并以 '>' 字符结束”的规则表达式。这将与最短、而不是最长的可能性匹配。新命令如下:
$ sed -e 's/<[^>]*>//g' myfile.html
在上例中,'[^>]' 指定“非 '>'”字符,其后的 '*' 完成该表达式以表示“零或多个非 '>' 字符”。对几个 html 文件测试该命令,将它们管道输出到 "more",然后仔细查看其结果。
更多字符匹配
'[ ]' 规则表达式语法还有一些附加选项。要指定字符范围,只要字符不在第一个或最后一个位置,就可以使用 '-',如下所示:
'[a-x]*'
这将匹配零或多个全部为 'a'、'b'、'c'...'v'、'w'、'x' 的字符。另外,可以使用 '[:space:]' 字符类来匹配空格。以下是可用字符类的相当完整的列表:
字符类 描述
[:alnum:] 字母数字 [a-z A-Z 0-9]
[:alpha:] 字母 [a-z A-Z]
[:blank:] 空格或制表键
[:cntrl:] 任何控制字符
[:digit:] 数字 [0-9]
[:graph:] 任何可视字符(无空格)
[:lower:] 小写 [a-z]
[:print:] 非控制字符
[:punct:] 标点字符
[:space:] 空格
[:upper:] 大写 [A-Z]
[:xdigit:] 十六进制数字 [0-9 a-f A-F]
尽可能使用字符类是很有利的,因为它们可以更好地适应非英语 locale(包括某些必需的重音字符等等).
高级替换功能
我们已经看到如何执行简单甚至有些复杂的直接替换,但是 sed 还可以做更多的事。实际上可以引用匹配规则表达式的部分或全部,并使用这些部分来构造替换字符串。作为示例,假设您正在回复一条消息。下例将在每一行前面加上短语 "ralph said: ":
$ sed -e 's/.*/ralph said: &/' origmsg.txt
输出如下:
ralph said: Hiya Jim, ralph said: ralph said:
I sure like this sed stuff! ralph said:
该例的替换字符串中使用了 '&' 字符,该字符告诉 sed 插入整个匹配的规则表达式。因此,可以将与 '.*' 匹配的任何内容(行中的零或多个字符的最大组或整行)插入到替换字符串中的任何位置,甚至多次插入。这非常好,但 sed 甚至更强大。
那些极好的带反斜杠的圆括号
's///' 命令甚至比 '&' 更好,它允许我们在规则表达式中定义 区域,然后可以在替换字符串中引用这些特定区域。作为示例,假设有一个包含以下文本的文件:
foo bar oni eeny meeny miny larry curly moe jimmy the weasel
现在假设要编写一个 sed 脚本,该脚本将把 "eeny meeny miny" 替换成 "Victor eeny-meeny Von miny" 等等。要这样做,首先要编写一个由空格分隔并与三个字符串匹配的规则表达式。
'.* .* .*'
现在,将在其中每个感兴趣的区域两边插入带反斜杠的圆括号来定义区域:
'\(.*\) \(.*\) \(.*\)'
除了要定义三个可在替换字符串中引用的逻辑区域以外,该规则表达式的工作原理将与第一个规则表达式相同。下面是最终脚本:
$ sed -e 's/\(.*\) \(.*\) \(.*\)/Victor \1-\2 Von \3/' myfile.txt
如您所见,通过输入 '\x'(其中,x 是从 1 开始的区域号)来引用每个由圆括号定界的区域。输入如下:
Victor foo-bar Von oni Victor eeny-meeny Von miny Victor larry-curly Von moe Victor jimmy-the Von weasel
随着对 sed 越来越熟悉,您可以花最小力气来进行相当强大的文本处理。您可能想如何使用熟悉的脚本语言来处理这种问题 -- 能用一行代码轻易实现这样的解决方案吗?
组合使用
在开始创建更复杂的 sed 脚本时,需要有输入多个命令的能力。有几种方法这样做。首先,可以在命令之间使用分号。例如,以下命令系列使用 '=' 命令和 'p' 命令,'=' 命令告诉 sed 打印行号,'p' 命令明确告诉 sed 打印该行(因为处于 '-n' 模式)。
$ sed -n -e '=;p' myfile.txt
无论什么时候指定了两个或更多命令,都按顺序将每个命令应用到文件的每一行。在上例中,首先将 '=' 命令应用到第 1 行,然后应用 'p' 命令。接着,sed 继续处理第 2 行,并重复该过程。虽然分号很方便,但是在某些场合下,它不能正常工作。另一种替换方法是使用两个 -e 选项来指定两个不同的命令:
$ sed -n -e '=' -e 'p' myfile.txt
然而,在使用更为复杂的附加和插入命令时,甚至多个 '-e' 选项也不能帮我们的忙。对于复杂的多行脚本,最好的方法是将命令放入一个单独的文件中。然后,用 -f 选项引用该脚本文件:
$ sed -n -f mycommands.sed myfile.txt
这种方法虽然可能不太方便,但总是管用。
一个地址的多个命令
有时,可能要指定应用到一个地址的多个命令。这在执行许多 's///' 以变换源文件中的字和语法时特别方便。要对一个地址执行多个命令,可在文件中输入 sed 命令,然后使用 '{ }' 字符将这些命令分组,如下所示:
1,20{ s/[Ll]inux/GNU\/Linux/g s/samba/Samba/g s/posix/POSIX/g }
上例将把三个替换命令应用到第 1 行到第 20 行(包括这两行)。还可以使用规则表达式地址或者二者的组合: