文章目录
1. 初识sed
1.1. 模式空间
sed维护一种模式空间,即一个工作区或临时缓冲区,当应用编辑命令时,将在那里存储单个输入行。sed首先将整个编辑脚本应用于第一个输入行,然后再读取第二个输入行并对其应用整个脚本。sed不会保留最初的行,这意味着与原始输入行匹配的模式可能不再与经过编辑操作之后的行匹配。
一次一行的设计的一个优点是sed在读取非常庞大的文件时不会出现问题。屏幕编辑程序必须将整个文件(或者他的一些庞大的部分)读入内存,这将会产生内存溢出或者处理庞大的文件时速度非常慢。
1.2. 寻址上的全局透视
sed命令可以指定零个、一个或两个地址。每个地址都是一个描述模式、行号或者行寻址符号的正则表达式。
- 如果没有指定地址,那么命令将应用于每一行。
- 如果只有一个地址,那么命令应用于与这个地址匹配的任意行。
- 如果指定了由逗号分隔的两个地址,那么命令应用于匹配第一个地址的第一行和它后面的行,直到匹配第二个地址的行(包括此行)。
- 如果地址后面跟有感叹号(!),那么命令就应该用于不匹配该地址的所有行。
1.3. 分组命令
sed使用大括号({})将一个地址嵌套在另一个地址中,或者在相同的地址上应用多个命令。如果想指定行的范围,然后在这个范围内指定另一个地址,则可以嵌套地址。
/xxx/,/yyy/{
/zz/d
}
注意:左大括号必须在行末,而且右大括号本身单独占一行。要确保在大括号之后没有空格。
2. 正则表达式
元字符:
. 匹配除换行符以外的任一单个字符。在awk中,句点也能匹配换行符。
* 匹配任意一个(包括零个)在它面前的字符(包括由正则表达式指定的字符)。
[...] 匹配方括号中的字符类中的任意一个。
如果方括号中第一个字符为脱字符号(^),则表示否定匹配,
即匹配除了换行符和类中列出的那些字符以外的所有字符。
在awk中,也匹配换行符。连字符(-)用于表示字符的范围。
如果类中的第一个字符为右方括号(])则表示它是类的成员。
所有其它的元字符在被指定为类中的成员时,都会失去它们原来的含义。
^ 如果作为正则表达式的第一个字符,则表示匹配行的开始。
在awk中匹配字符串的开始,即使字符串包含嵌入的换行符
$ 如果作为正则表达式的最后一个字符,则表示匹配行的结尾。
在awk中匹配字符串的结尾,即使字符串包含嵌入的换行符。
\{n,m\} 匹配它前面某个范围内单个字符出现的次数(包括由正则表达式指定的字符)。
\{n\}将匹配n次出现,\{n,\}至少匹配n次出现,
而且\{n,m\}匹配n和m之间的任意次出现。
\ 转义随后的特殊字符。
扩展的元字符(egrep和awk)
+ 匹配前面的正则表达式的一次或多次出现
? 匹配前面的正则表达式的零次或一次出现
| 指定可以匹配其前面的或后面的正则表达式(替代方案)
() 对正则表达式分组
{n,m} 匹配它前面某个范围内单个字符出现的次数(包括由正则表达式指定的字符)。
{n}表示匹配n次出现,{n,}至少匹配n次出现,
{n,m}匹配n和m之间的任意次出现。
POSIX字符类
[:alnum:] 可打印的字符(包括空白字符)
[:alpha:] 字母字符
[:blank:] 空格和制表符
[:cntrl:] 控制字符
[:digit:] 数字字符
[:graph:] 可打印的和可见的(非空格)字符
[:lower:] 小写字符
[:print:] 可打印的字符(包括空白字符)
[:punct:] 标点符号字符
[:space:] 空白字符
[:upper:] 大写字符
[:xdigit:] 十六进制数字
3. 基本sed命令
sed命令集合由25个命令组成。
3.1. sed命令的语法
行地址对于任何命令都是可选的。它可以使一个模式,被描述为由斜杠、行号或行寻址符号括住的正则表达式。大多数sed命令能接受由逗号分隔的两个地址,这两个地址用来标识行的范围。这些命令的语法格式为:
[address]command
有一些命令只接受单个行地址。它们不能应用于某个范围的行。它们的语法格式为:
[line-address]command
另外,命令还可以用大括号进行分组以使其作用于同一个地址:
address {
command1
command2
command3
}
第一个命令可以和大括号放置在同一行,但是右大括号必须自己单独处于一行。每个命令都可以有自己的地址并允许有多层分组。而且,就像命令在大括号内的缩进方式一样,允许在行的开始处插入空格和制表符。
当sed不理解一个命令时,它打印出消息“Command garbled(命令不清)"。在命令后添加空格会产生一个小的语法错误,命令的结束必须在行的结尾处。
如果命令之间用一个分号分隔,那么可以将多个sed命令放在同一行:
n;d
然而,在n命令后面放置一个空格会导致语法错误,而在d命令前放置一个空格是可以的。
备注:用分号分隔命令的用法不在POSIX标准中。
3.2. 替换
语法:
[address]s/pattern/replacement/flags
这里修饰替换的标志flags是:
- n 1到512之间的一个数字,表示对文本模式中指定模式第n次出现的情况进行替换
- g 对模式空间的所有出现的情况进行全局更改。而没有g时通常只有第一次出现的情况被取代
- p 打印模式空间的内容
- W file
将模式空间的内容写到文件file中
替换命令应用于address匹配的行。如果没有指定地址,那么应用于pattern匹配的所有行。如果正则表达式作为地址来提供,并且没有指定模式,那么替换命令匹配由地址匹配的内容。当替换命令是应用于同一个地址上的多个命令之一时,这可能会非常有用。格式如:
[address]s/address/replacement/flags
和地址不同的是,地址需要一个作为定界符的斜杠(/),而正则表达式可以用任一字符来分隔,只有换行符除外。因此,如果模式包含斜杠,那么可以选择另一个字符作为定界符,例如逗号:
s,/usr/mail/,/usr2/mail,
不管使用哪种定界符,如果它出现在正则表达式中,或者在替换文本中,那么就用反斜杠来转义它。
replacement是一个字符串,用来替换与正则表达式匹配的内容。在replacement部分,只用下列字符有特殊含义:
& 用正则表达式匹配的内容进行替换。
\n 匹配第n个子串(n是一个数字),这个子串以前在pattern中用"\("和"\)"指定。
\ 当在替换部分包含“与”字符(&),反斜杠(\)和替换命令的定界符时,可用\转义它们。
另外,它用于转义换行符并创建多行replacement字符串。
3.2.1. 替换元字符
替换元字符是:反斜杠(\)、“与”符号(&)和\n。
1)反斜杠一般用于转义其它的元字符,但是它在替换字符串中也用于包含换行符,如
s/\./\
/
注意:在反斜杠后面不允许有空格,上面命令等价于:s/./\n/
2)“与”符号(&)表示模式匹配的范围,不是被匹配的行。
echo "hello world" | sed 's/hello/& my/g'
=> hello my world
当正则表达式匹配单词的变化时,“与”符号特别有用。它允许指定一个可变的替换字符串,该字符串相当于匹配的内容与实际内容匹配的字符串。
3)一种用于选择被匹配的字符串的任意独立部分,并且在替换字符串中回调它的元字符。
在sed中转义的圆括号括住正则表达式的任一部分并且保存它以备回调。一行最多允许“保存”9次。“\n”用于回调被保存的匹配部分,n是从1到9的数字,用于引用特殊“保存的”备用字符串。
3.3. 删除
删除命令(d),它采用一个地址,如果行匹配这个地址就删除模式空间的内容。
删除命令还是一个可以改变脚本中的控制流的命令。这是因为一旦执行这个命令,那么在“空的”模式空间中就不会再有命令执行。删除命令会导致读取新输入行,而编辑脚本则从头开始新的一轮。
重要的是:如果某行匹配这个地址,那么就删除整个行,而不只是删除行中匹配的部分。
3.4. 追加、插入和更改
追加(a),插入(i)和更改(c)命令提供了通常在交互式编辑器中所选的编辑功能。
1)追加:
[line-address]a text
[line-address]a\
text
2)插入
[line-address]i text
[line-address]i\
text
3)更改
[address]c text
[address]c\
text
插入命令将所提供的文本放置在模式空间的当前行之前。追加命令将文本放置在当前行之后。更改命令用所提供的文本取代模式空间的内容。
追加命令和插入命令只应用于单个行地址,而不是一个范围内的行。然而,更改命令可以处理一个范围内的行。
3.5. 列表
列表命令(l,小写的L)用于显示模式空间的内容,将非打印的字符显示为两个数字的ASCII码。其功能类似于vi中的列表命令(:l)。可以使用该命令来检测输入中的“不可见”字符。
sed -n -e "l" FILE
3.6. 转换
转换命令是特有的,不仅因为它在所有的sed命令中拥有最小的助记符。这个命令按位置将字符串abc中的每个字符,都转换成字符串xyz中的等价字符。语法:
[address]y/abc/xyz
替换根据字符的位置来进行。因此,它没有“词”的概念。这样,在该行上的任何地方的“a"都被换成了“x”,而不管它后面是否跟有“b"。这个命令的一个可能的用处是用大写字母替换对应的小写字母。
y/abcdefghijklmnopqrstuvwxyz/ABCDEFGHIJKLMNOPQRSTUVWXYZ/
这个命令影响整个模式空间的所有内容。如果想在输入行上转换单个单词,那么通过使用保持空间可以完成。
3.7. 打印
打印命令(p)输出模式空间的内容。它既不清除模式空间也不改变脚本中的控制流。然而,它频繁地用在改变流控制的命令(d,N,b)之前。除非抑制(-n)默认的输出,否则打印命令将输出行的重复复制。当抑制默认的输出或者当通过程序的流控制来避免到达脚本的底部时,可能会使用它。
3.8. 打印行号
跟在地址后面的等号(=)打印被匹配的行的行号。除非抑制行的自动输出,行号和行本身将被打印。语法:
[line-address]=
这个命令不能对一个范围内的行进行操作。
3.9. 下一步
下一步(next)命令(n)输出模式空间的内容,然后读取输入的下一行,而不用返回到脚本的顶端。语法:
[address]n
next命令改变了正常的流控制,直到到达脚本的底部才会输出模式空间的内容,它总是在读入新行之后从脚本的顶端开始。实际上,next命令导致输入的下一行取代模式空间中的当前行。脚本中的后续命令应用于替换后的行,而不是当前行。如果没有抑制默认输出,那么在替换发生之前会打印当前行。示例:
/^\.H1/{
n
/^$/d
}
匹配任何以字符串“.H1”开始的行,然后打印那一行并读入下一行。如果那一行为空,则删除它。大括号用于在同一个地址应用多个命令。
出现在next命令之前的命令不会应用于新的输入行,而且出现在后面的命令不应用于旧的输入行。
3.10. 读和写文件
读(r)和写(w)命令用于直接处理文件。这两个命令都只有一个参数,即文件名。语法:
[line-address]r file
[address]w file
读命令将由file指定的文件确定的行之后的内容读入模式空间。它不能对一个范围内的行进行操作。写命令将模式空间的内容写到file中。
在命令和文件名之前必须有一个空格(空格后到换行符前的每个字符都被当做文件名,因此,前导的和嵌入的空格也是文件名的一部分)。如果文件不存在,读命令也不会报错。如果写命令中指定的文件不存在,将创建一个文件;如果文件已经存在,那么写命令将在每次调用脚本时改写它。如果一个脚本中有多个指令写到同一个文件中,那么每个写命令都将内容追加到这个文件中。而且,每个脚本最多只能打开10个文件。
读命令对于将一个文件的内容插入到另一个文件的特定位置是很用的。例如:假设有一组文件并且每个文件都应以相同的一个或两个段落的语句结束。使用sed脚本可以在必要时,分别单独对结束部分进行维护,例如:当将文件发送到打印机时:
sed '$r closing' $* | pr | lp
$是指定文件最后一行的寻址符号。文件closing的内容放置在模式空间的内容之后并且和它一起输出。这个示例没指定路径名,假设文件和命令在同一个目录下,一个更通用的命令应该使用完整的路径名。
3.11. 退出
退出命令(q)会使sed停止读取新的输入行(并停止将它们发送到输出)。语法:
[line-address]q
它只适用于单行的地址。一旦找到和address匹配的行,那么脚本就结束。
quit的另一个可能的用法是在从文件中提取了想要的内容后退出脚本。
4. 高级sed命令
高级命令分成了3个组:
- 处理了多行模式空间(N、D、P)
- 采用保持空间来保存模式空间的内容并使它可用于后续的命令(H、h、G、g、x)
- 编写使用分支和条件指令的脚本来更改控制流(:、b、t)
高级脚本都做一件共同的事,那就是它们改变了执行或控制的流程顺序。通常,一行被读入模式空间并且脚本中的每个命令(一个接一个地)应用于那一行。当达到脚本的底部时,输出这一行并且清空模式空间。然后新行被读入模式空间,并且控制被转移会脚本的顶端。这是sed脚本中正常的控制流。
4.1. 多行模式空间
sed能查看模式空间的多个行,这就是允许匹配模式扩展到多行上。3个多行命令(N、D、P)对应于小写字母的基本命令(n、d、p),例如:删除命令(D)是删除命令(d)的多行形式。区别是:d删除模式空间的内容,D只删除多行模式空间的第一行。
4.1.1. 追加下一行
多行Next(N)命令通过读取新的输入行,并将它添加到模式空间的现有内容之后来创建多行模式空间。模式空间最初的内容和新的输入行之间用换行符分隔。在模式空间中嵌入的换行符可以利用转义序列“\n”来匹配。在多行模式空间中,元字符“^”匹配空间中的第一个字条,而不匹配换行符后面的字符。同样,“$”只匹配模式空间中最后的换行符,而不匹配任何嵌入的换行符。在执行next命令之后,控制将被传递给脚本中的后续命令。
Next命令与next命令不同,next输出模式空间的内容,然后读取新的输入行。next命令不创建多行模式空间。
4.1.2. 多行删除
删除命令(d)删除模式空间的内容并导致读入新的输入行,从而在脚本的顶端重新使用编辑方法。删除命令(D)稍微有些不同,它删除模式空间中直到第一个嵌入的换行符的这部分内容。它不会导致读入新的输入行,相反,它返回到脚本的顶端,将这些指令应用于模式空间剩余的内容。
举例:实现一个查找一系列空行并输出单个空行的脚本
# 将多个空行减少到一行,利用d命令的版本
/^$/{
N
/^\n$/d
}
当遇到一个空行时,下一行就追加到模式空间中。然后试着匹配嵌入的换行符。
结果:当有偶数个空行时,所有的空行都会被删除。仅当有奇数个空行时,有一行被保留下来。这是因为删除命令清除的是整个模式空间。一旦遇到第一个空行,就读入下一行,并且两行都被删除。如果遇到第三个空行,并且下一行不为空,那么删除命令就不会被执行,因此空行被输出。如果使用多行Delete命令(是D不是d),就能得到想要的结果。
多行Delete命令完成工作的原因是,当遇到两个空行时,Delete命令只删除两个空行中的第一个。下一次遍历该脚本时,这个空行将导致另一行被读入模式空间。如果那行不为空,那么两行都输出,因此确保了输出一个空行。换句话说,当模式空间中有两个空行时,只有第一个空行被删除。当一个空行后面跟有文本时,模式空间可以正常输出。
4.1.3. 多行打印
多行打印(Print)命令与小写字母的print命令稍有不同。该命令输出多行模式空间的第一部分,直到第一个嵌入的换行符位置。在执行完脚本的最后一个命令后,模式空间的内容自动输出(-n选项或#n抑制这个默认的动作)。因此,当默认的输出被抑制或者脚本中的控制流更改,以致不能到达脚本的底部时,需要使用打印命令(P或p)。
Print命令经常出现在Next命令之后和Delete命令之前。
这3个命令能建立一个输入/输出循环,用来维护两行的模式空间,但是一次只输出一行。这个循环的目的是只输出模式空间的第一行,然后返回到脚本的顶端将所有的命令应用于模式空间的第二行。没有这个循环,当执行脚本中的最后一个命令时,模式空间中的这两行都将被输出。
4.2. 包含那一行
模式空间是容纳当前输入行的缓冲区。还有一个称为保持空间(hold space)的顶留(sed-aside)缓冲区。
模式空间的内容可以复制到保持空间,而且保持空间的内容也可以复制到模式空间。
有一组命令用于在保持空间和模式空间之间移动数据。
保持空间用于临时存储。单独的命令不能寻址保持空间或者更改它的内容。
保持空间最常的用途是,当改变模式空间中的原始内容时,用于保留当前输入行的副本。影响模式空间的命令有:
——————————————————————————————————————————————————————
命令 缩写 功能
————————————————————————————
Hold h或H 将模式空间的内容复制或追加到保持空间
Get g或G 将保持空间的内容复制或追加到模式空间
Exchange x 交换保持空间和模式空间的内容
——————————————————————————————————————————————————————
这些命令中的每一条都可以利用一个地址来指定一行或行范围。
Hole(h,H)命令将数据移至保持空间,Get(g,G)命令将保持空间的数据移回到模式空间。
同一命令的小写字母和大小字母之间的差别是:小写字母命令改写目的缓存区的内容,大写字母命令追加缓存区的现有内容。
Hold命令用模式空间的内容取代保持空间的内容。get命令用保持空间的内容取代模式空间的内容。
Hold命令在保持空间的内容之后放置一个换行符,且后面跟随模式空间的内容(即使保持空间是空的,换行符也被追加到保持空间中)。Get命令在模式空间的内容之后放置一个换行符,且后面跟随保持空间的内容。
交换命令交换两个缓存区的内容,对两个缓存区没有副作用。
4.3. 高级的流控制命令
分支(b)和测试(t)命令将脚本中的控制转移到包含特殊标签的行。如果没有指定标签,则将控制转移到脚本的结尾处。
分支命令用于无条件转移,测试命令用于有条件转移,它们只有当替换命令改变当前行时才会执行。
标签是任意不多于7个字符的序列。标签本身占据一行并以冒号开始:
:mylabel
在冒号和标签之间不允许有空格。行结尾处的空格将被认为是标签的一部分。
当在分支命令或测试命令中指定标签时,在命令和标签之间允许有空格:
b mylabel
注意:不要在标签后面插入空格。
4.3.1. 分支
Branch命令用于在脚本中将控制转移到另一行。语法:
[address]b[label]
Label是可选的,如果没有给出label,控制就被转到脚本的结尾处。如果有label就继续执行标签后面的行。
分支命令可用于将一组命令作为一个过程来执行,这个过程可以从这个脚本的主体中重复调用。它也可用于避免执行某个基于一个模式匹配的过程。
通过使用!并组合一组命令可以实现类似的效果。在应用中对分支命令使用!的优点是,可以更容易地指定要避免的多个情况。!符号可以应用于单个命令,或者应用于紧随其后的包围在大括号中的一组命令。另一方面,分支命令赋予了你几乎不受限制的脚本跳转控制能力。
举例1:如何使用分支命令创建循环?
一旦读取一个输入行,command1和command2就会应用于那一行;然后,如果模式空间的内容与指定的模式匹配,那么控制就被转移到跟在标签“top”后面的行,这就意味着,command1和command2将再次执行。
:top
command1
command2
/pattern/b top
command3
只有当模式不匹配时,才执行command3。所以这3个命令都会执行,尽管前两个命令可以多次执行。
举例2:
首先执行command1。如果模式匹配,控制权就会转移到跟在标签“end”后面的行,这就意味着跳过了command2。
command1
/pattern/b end
command2
:end
command3
在所有的情况下,都会执行command1和command3
举例3:
如果指定执行command2或command3中的一个,但不是两个都执行。
command1
/pattern/b dothree
command2
b
:dothree
command3
4.3.2. 测试
如果在当前匹配地址的行上进行了成功的替换,那么test命令就会转到标签(或者脚本的结尾)处。因此,它隐含了一个条件分支。语法:
[address]t[label]
如果没有给出标签label,控制被转移到脚本的结尾处。如果提供了标签label,那么就会继续执行标签后面的行。
/pattern/{
command1
t
command2
t
command3
}
command1/2/3为替换命令,一旦一个替换被执行,其中的test命令就使控制到达脚本的末尾。
test命令提供的功能类似于C程序设计语言,或shell程序设计语言中的case语句的功能。即测试每种情况并且当一种情况为真时,退出结构。
如果上面的脚本时一个更大的脚本中的一部分,可以使用标签(可以形象地命名为“break”)直接转移到分组命令的末尾,然后继续执行其它一些命令:
/pattern/{
command1
t break
.
.
}
:break
more commands
5. 参考
- sed与awk(Dale Dougherty & Arnold Robbins 著,张旭东 杨作梅 田丽华 等译)