sed修炼系列(一):花拳绣腿之入门篇

sed系列文章:html

sed修炼系列(一):花拳绣腿之入门篇
sed修炼系列(二):武功心法(info sed翻译+注解)
sed修炼系列(三):sed高级应用之实现窗口滑动技术
sed修炼系列(四):sed中的疑难杂症正则表达式


学习sed的过程当中,推荐使用"sedsed"调试工具,这对于分析sed处理过程以及pattern space、hold space有很大帮助。shell

本文是sed系列的第一篇文章,倒是最重要的一篇,特别是第一节和第四节是整个sed的重中之重,是sed的"神",而那些简单的sed用法、示例等等都是sed的"形"。若是是sed初学者,这些"神"的内容可略看一遍,在有了使用sed操做的基础以后,再仔细回头琢磨这些"神"中的每一句话,必能深刻sed。此后,再去阅读第二篇文章(sed武功心法)掌握更细节的"形"和"神",最后阅读第三篇文章(sed高级应用)掌握更高级的操做手段,在实现复杂逻辑时每每能事半功倍,最后还可看看第四篇文章(sed中的疑难杂症),在sed出错殊不知和解时,这篇文章每每能找到答案。编程


1.基本概念

sed是一个流式编辑器程序,它读取输入流(能够是文件、标准输入)的每一行放进模式空间(pattern space),同时将此行行号经过sed行号计数器记录在内存中,而后对模式空间中的行进行模式匹配,若是能匹配上则使用sed程序内部的命令进行处理,处理结束后,从模式空间中输出(默认)出去,并清空模式空间,随后再从输入流中读取下一行到模式空间中进行相同的操做,直到输入流中的全部行都处理完成。因而可知,sed是一个循环一个循环处理内容的。缓存

这是sed的一个循环的过程: bash

  1. 读取输入流的一行到模式空间。
  2. 对模式空间中的内容进行匹配和处理。
  3. 自动输出模式空间内容。
  4. 清空模式空间内容。
  5. 读取输入流的下一行到模式空间。

(注:(如看不懂,请跳过)若是是读取文件数据,则会每次须要的时候一次性加载必定量(好比多行)的数据到os buffer,而后sed从os buffer中一行一行读取,并非要读一行就从磁盘文件中加载一行。另外,若是是管道或其它输入流,则直接从对应的缓存中一行一行读取。验证命令:sed ‘p;s/.*/:>filename/e;d’ filename)网络

上述整个循环过程当中,第2步是咱们写sed命令所修改的地方,其他的几个步骤,经过命令行没法改变。可是,sed有几个命令和选项能改变第三、4步的行为,使其输出老是输出空内容或没法清空模式空间。app

sed程序的语法格式为:编辑器

sed OPTIONS SCRIPT INPUT_STREAM

其中SCRIPT部分就是所谓的sed脚本,它是sed内部命令的集合,sed中的命令有些奇特,它包含行匹配以及要执行的命令。格式为ADDR1[,ADDR2]cmd_list。例如,要对第2行执行删除命令,其命令为sed 2d filename,只输出第4行到6行,其命令为sed -n 4,6p工具

sed的内部命令很是多,但既然"花拳绣腿篇",固然只介绍些入门的东西。具体的行匹配方法、有哪些命令以及哪些选项稍后解释。如今的重点是sed中的循环过程。既然SCRIPT是命令的集合,因而上面的循环过程能够修改成以下:

  1. 读取输入流的一行到模式空间。
  2. 对模式空间中内容执行SCRIPT。(包括上面示例中的"2d"和"4,6p")
  3. 读取输入流的下一行到模式空间。
  4. 对模式空间中内容执行SCRIPT。

其中SCRIPT部分包含了sed命令行中的内部命令,还包括两个特殊动做:自动输出和清空模式空间内容。这两个动做是必定会执行的,只不过有些时候经过某些命令可使其输出空内容、使其清空不了模式空间。

若是使用编程结构来描述,则大体过程以下:

for ((line=1;line<=last_line_num;++line))
do
    read $line to pattern_space;
    while pattern_space is not null
    do
        execute cmd1 in SCRIPT;
        execute cmd2 in SCRIPT;
        execute cmd3 in SCRIPT;
        ……
        auto_print;
        remove_pattern_space;
    done
done

其中while循环执行的正是SCRIPT中的全部命令,只不过通常状况下,while循环只执行一轮就退出并进入外层的for循环。因而,外层的for循环称之为"sed循环",内层的while循环称之为"SCRIPT"循环。因此,for循环只包含了两个动做:读取下一行和执行SCRIPT循环。

其实while循环中是有continue、break甚至是exit的,分别表示回到SCRIPT的顶端(即进入下一个SCRIPT循环)、退出当前SCRIPT循环回到外层sed循环以及退出整个sed循环。显然,这不是"花拳绣腿"的内容。

最后,说明下sed命令行如何书写,其实就是写SCRIPT部分,这部分的写法比较灵活,大体有如下几种:

# 一行式。多个命令使用分号分隔
sed Address{cmd1;cmd2;cmd3...}

# 多个表达式时,可使用"-e"选项,也能够不用,但使用分号分隔
sed Address1{cmd1;cmd2;cmd3};Address2{cmd1;cmd2;cmd3}...
sed  -e 'Address1{cmd1;cmd2;cmd3}' -e 'Address2{cmd1;cmd2;cmd3}' ...

# 分行写时
sed Address1{
    cmd1
    cmd2
    cmd3
}
Address2{
    cmd1
    cmd2
    cmd3
}

若是是写在文件中,即sed脚本,以文件名为a.sed为例。

#!/usr/bin/sed -f
#注释行
Address1{cmd1;cmd2...}
Address2{cmd1;cmd2...}
......

其中cmd部分还能够进行模式匹配,也即相似于"Address{{pattern1}cmd1;{pattern2}cmd2}"的写法。例如, /^abc/{2d;p} 。

有了以上基本的大纲性知识,理解和深刻sed机制就简单多了。


2.sed选项

sed选项不算多,能用到的更没几个。

sed OPTIONS SCRIPT INPUT_STREAM

可能用到的几个选项:

'-n'
默认状况下,sed将在每轮script循环结束时自动输出模式空间中的内容。使用该选项后可使得此次自动输出动做输出空内容,而不是当前模式空间中的内容。注意,"-n"是输出空内容而不是禁用输出动做,虽然二者的结果都是不输出任何内容,但在有些依赖于输出动做和输出流的地方,它们的区别是很大的,前者有输出流,只是输出空流,后者则没有输出流。

'-e SCRIPT'
前文说了,SCRIPT中包含的是命令的集合,"-e"选项就是向SCRIPT中添加命令的。能够省略"-e"选项,但若是命令行容易产生歧义,则使用"-e"选项可明确说明这部分是SCRIPT中的命令。另外,若是一个"-e"选项不方便描述所需命令集合时,能够指定多个"-e"选项。

'-f SCRIPT-FILE'
指定包含命令集合的SCRIPT文件,让sed根据SCRIPT文件中的命令集处理输入流。

'-i[SUFFIX]'
该选项指定要将sed的输出结果保存(覆盖的方式)到当前编辑的文件中。GNU sed是经过建立一个临时文件并将输入写入到该临时文件,而后重命名为源文件来实现的。

当当前输入流处理结束后,临时文件被重命名为源文件的名称。若是还提供了SUFFIX,则在重命名临时文件以前,先使用该SUFFIX修改源文件名,从而生成一个源文件的备份文件。

临时文件老是会被重命名为源文件名称,也就是说输入流处理结束后,仍使用源文件名的文件是sed修改后的文件。文件名中包含了SUFFIX的文件则是最原始文件的备份。例如源文件为a.txt,sed -i'.log' SCRIPT a.txt将生成两个文件:a.txt和a.txt.log,前者是sed修改后的文件,a.txt.log是源a.txt的备份文件。

重命名的规则以下:若是扩展名不包含符号"*",将SUFFIX添加到原文件名的后面看成文件后缀;若是SUFFIX中包含了一个或多个字符"*",则每一个"*"都替换为原文件名。这使得你能够为备份文件添加一个前缀,而不是后缀。若是没有提供SUFFIX,源文件被覆盖,且不会生成备份文件。

该选项隐含了"-s"选项。

'-r'
使用扩展正则表达式,而不是使用默认的基础正则表达式。sed所支持的扩展正则表达式和egrep同样。使用扩展正则表达式显得更简洁,由于有些元字符不用再使用反斜线"\"。正则表达式见grep命令中文手册

'-s'
默认状况下,若是为sed指定了多个输入文件,如sed OPTIONS SCRIPT file1 file2 file3,则多个文件会被sed看成一个长的输入流,也就是说全部文件被当成一个大文件。指定该选项后,sed将认为命令行中给定的每一个文件都是独立的输入流。

既然是独立的输入流,范围定址(如/abc/,/def/)就没法跨越多个文件进行匹配,行号也会在处理每一个文件时重置,"$"表明的也将是每一个文件的最后一行。这也意味着,若是不使用该选项,则这几个行为都是能够完成的。


示例:以sed命令"p"和"="为例,其中"p"命令用于强制输出当前模式空间中的内容,"="命令用于输出sed行号计数器当前的值,即刚被读入到模式空间中的行是输入流中的第几行。

(1).只输出a.txt中的第5行。

sed -n 5p a.txt

这里使用了"-n"选项,使得读取到模式空间的每一行都没法被输出,只有明确使用了"p"选项才能被"p"动做输出。因为只有读入的第5行内容能匹配"5",才能被"p"输出。

其实上面的命令和sed -n -e '5p' a.txt是彻底同样的,由于"5p"在sed解析命令行时不会产生歧义,因此能够省略"-e"选项。

(2).输出a.txt,并输出每行的行号。

sed '=' a.txt

因为要输出a.txt的内容,因此不使用"-n"选项,同时"="命令会输出每行行号。

(3).分别输出a.txt和b.txt的第5行,并分别保存到".bak"后缀的文件中。

sed -i'*.bak' -n '5p' a.txt b.txt

此处必须使用"-s"选项,不然将只会输出"a.txt+b.txt"结合后的第5行。但"-i"隐含了"-s"选项。这会生成4个文件:a.txt、b.txt和a.txt.bak、b.txt.bak。前两个是第5行内容,后两个是源文件的备份文件。

(4).使用扩展正则表达式,输出a.txt和b.txt中能包含3个以上字母"a"的行。

sed -r -n '/aaa+/p' a.txt b.txt

3.定址表达式

当sed将输入流中的行读取到模式空间后,就须要对模式空间中的内容进行匹配,若是能匹配就能执行对应的命令,若是不能匹配就直接输出、清空模式空间并进入下一个sed循环读取下一行。

匹配的过程称为定址。定址表达式有多种,但总的来讲,其格式为[ADDR1][,ADDR2]。这能够分为3种方式:

  1. ADDR1和ADDR2都省略时,表示全部行都能被匹配上。
  2. 省略ADDR2时,表示只有被ADDR1表达式匹配上的行才符合条件。
  3. 不省略ADDR2时,是范围地址。表示从ADDR1匹配成功的行开始,到ADDR2匹配成功的行结束。

不管是ADDR1仍是ADDR2,均可以使用两种方式进行匹配:行号和正则表达式。以下:

'N'
指定一个行号,sed将只匹配该行。(须要注意,除非使用了"-s"或"-i"选项,sed将对全部输入文件的行连续计数。)

'FIRST~STEP'
表示从第FIRST行开始,每隔STEP行就再取一次。也就是取行号知足FIRST+(N*STEP) (其中N>=0)的行。所以,要选择全部奇数行,使用"1~2";要从第2行开始每隔3行取一次,使用"2~3";要从第10行开始每隔5行取一次,使用"10~5";而"50~0"则表示只取第50行。

'$'
默认该符号匹配的是最后一个文件的最后一行,若是指定了"-i"或"-s",则匹配的是每一个文件的最后一行。总之,"$"匹配的是每一个输入流的最后一行。

须要注意的是,sed采用行号计数器来临时记录当前行的行号,所以sed在读取到最后一行前即便是倒数第二行的时候,彻底不知道最后一行是第几行,因此表明最后一行的"$"没法进行任何数学运算,例如倒数第二行使用"$-1"表示是错误的。并且,"$"只是一个额外的标记符号,当sed读取到输入流的最后一行时,发现这就是最后一行,因而为此行打上"$"记号,并读取到模式空间中。

'/REGEXP/'
将选择能被正则表达式REGEXP匹配的全部行。若是REGEXP中自身包含了字符"/",则必须使用反斜线转义,即"\/"

'/REGEXP/I'
和"/REGEXP/"是同样的,只不过匹配的时候不区分大小写。

'\%REGEXP%'
('%'可使用其余任意单个字符替换。) 这和上一个定址表达式的做用是同样的,只不过是使用符号"%"替换了符号"/"。当REGEXP中包含"/"符号时,使用该定址表达式就无需对"/"使用反斜线"\"转义。但若是此时REGEXP中包含了"%"符号时,该符号须要使用"\"转义。
总之,定址表达式中使用的分隔符在REGEXP中出现时,都须要使用反斜线转义。

'ADDR1,+N'
匹配ADDR1和其后的N行。

'ADDR1,~N'
匹配ADDR1和其后的行直到出现N的倍数行。倍数可为随意整数倍,只要N的倍数是最接近且大于ADDR1的便可。 如ADDR1=1,N=3匹配1-3行,ADDR1=5,N=4匹配5-8行。而"1,+3"匹配的是第一行和其后的3行即1-4行。

另外,在定址表达式的后面加"!"符号表示反转匹配的含义。也就是说那些匹配的行将不被选择,而是不匹配的行被选择。

例如,如下几个定址的示例:

sed -n '3p' INPUTFILE
sed -n '3,5!p' INPUTFILE
sed -n '3,/^# .*/! p' INPUTFILE
sed -n '/abc/,/xyz/p' INPUTFILE
sed -n '!p' INPUTFILE   # 这个有悖常理,但确实是容许的

4.sed经常使用命令

sed命令不少,本文的只简单介绍几个最多见的。

此处不以命令的用法为重,而是经过这几个命令,引出sed最重要的原理和执行机制(还包括本文的第一节内容),并为阅读下一篇文章sed武功心法:info sed打下基础。并且理解了这些原理,再使用sed作任何操做都有理可循,遇到疑难之处也知道如何进行分析。而这,是任何书籍(包括广为推崇的sed && awk)、教学视频和网络文章中都没有的内容(至少我从未见过,这些内容也是我花费大量精力通过大量实验推演出来)。

(1).强制输出命令"p"。

该命令能强制输出当前模式空间的内容。即便使用了"-n"选项。

事实上,它们本就不冲突,由于循环过程以下:

for ((line=1;line<=last_line_num;++line))
do
    read $line to pattern_space;
    while pattern_space is not null
    do
        execute cmd1 in SCRIPT;
        execute cmd2 in SCRIPT;
 ADDR1,ADDR2{print}; # "p" command
        ……
        auto_print;
        remove_pattern_space;
    done
done

在sed处理的过程当中,"p"和"auto_print"是两个输出动做,都是输出当前模式空间的内容,只不过auto_print是隐含动做。使用了"-n"选项,其所影响的动做仅是"auto_print",使其输出空内容。也所以,当没有使用"-n"选项时,模式空间的内容会被输出两次。

例如,仅输出标准输入的第2行内容。

[root@xuexi ~]# echo -e 'abc\nxyz' | sed -n 2p
xyz

不加"-n"选项,在"p"输出以后,SCRIPT循环的结尾处还会被auto_print输出一次。

[root@xuexi ~]# echo -e 'abc\nxyz' | sed 2p 
abc
xyz    # 这是p命令输出的结果
xyz    # 这是自动输出的结果

(2).删除命令"d"。

命令"d"用于删除整个模式空间中的内容,并当即退出当前SCRIPT循环,进入下一个sed循环,即读取下一行

循环大体格式以下:

for ((line=1;line<=last_line_num;++line))
do
    read $line to pattern_space;
    while pattern_space is not null
    do
        execute cmd1 in SCRIPT;
        execute cmd2 in SCRIPT;
 ADDR1,ADDR2{delete;break}; # "d" command
        ……
        auto_print;
        remove_pattern_space;
    done
done

惟一须要注意的一点是当即退出当前SCRIPT循环,这意味着若是"d"命令后面还有其余的命令,则这些命令都不会执行。

例如:删除a.txt中的第5行,并保存到原文件中。

sed -i '5d' a.txt

这里不能使用重定向的方式保存,由于重定向是在sed命令执行前被shell执行的,因此会截断a.txt,使得sed读取的输入流为空,或者结果出乎意料以外。而"-i"选项则不会操做原文件,而是生成临时文件并在结束时重命名为原文件名。

删除a.sh中包含"#"开头的注释行,但第一行的#!/bin/bash不删除。

sed '/^#/{1!d}' a.sh

若是"d"后面还有命令,在删除模式空间后,这些命令不会执行,由于会当即退出当前SCRIPT循环。例如:

echo -e 'abc\nxyz' | sed '{/abc/d;=}'
2
xyz

其中"="这个命令用于输出行号,可是结果并无输出被"abc"匹配的行的行号。

(3).退出sed程序命令"q"和"Q"。

使用"q"和"Q"命令的做用是当即退出当前sed程序,使其再也不执行后面的命令,也再也不读取后面的行。所以,在处理大文件或大量文件时,使用"q"或"Q"命令能提升很大效率。它们之间的不一样之处在于"q"命令被执行后还会使用自动输出动做输出模式空间的内容,除非使用了"-n"选项。而"Q"命令则会当即退出,不会输出模式空间内容。另外,能够为它们指定退出状态码,例如"q 1"。

使用了"q"和"Q"的sed循环结构大体以下:

# "q"命令
for ((line=1;line<=last_line_num;++line))
do
    read $line to pattern_space;
    while pattern_space is not null
    do
        execute cmd1 in SCRIPT;
        execute cmd2 in SCRIPT;
 ADDR1,ADDR2{auto_print;exit}; # "q" command
        ……
        auto_print;
        remove_pattern_space;
    done
done

# "Q"命令
for ((line=1;line<=last_line_num;++line))
do
    read $line to pattern_space;
    while pattern_space is not null
    do
        execute cmd1 in SCRIPT;
        execute cmd2 in SCRIPT;
 ADDR1,ADDR2{exit}; # "Q" command
        ……
        auto_print;
        remove_pattern_space;
    done
done

例如,搜索脚本a.sh,当搜索到使用了"."或"source"命令加载环境配置脚本时就输出并当即退出。

sed -n -r '/^[ \t]*(\.|source) /{p;q}' a.sh

(4).输出行号命令"="。

"="命令用于输出最近被读取行的行号。在sed内部,使用行号计数器进行行号计数,每读取一行,行号计数器加1。计数器的值存储在内存中,在要求输出行号时,直接插入在输出流中的指定位置。因为值是存在于内存中,而非模式空间中,所以不受"-n"选项的影响。

这是一个依赖于输出流的命令,只要有输出动做就会追加在该输出流的尾部

例如,搜索出httpd.conf中"DocumentRoot"开头的行的行号,容许有前导空白字符。

sed -n '/^[ \t]*DocumentRoot/{p;=}' httpd.conf        
DocumentRoot "/var/www/html"
119

若是"="命令前没有"p"输出命令,且没有使用"-n"选项,则是输出在Document所在行的前一行,由于SCRIPT最后的自动输出动做也有输出流。

(5).字符一一对应替换命令"y"。

该命令和"tr"命令的映射功能同样,都是将字符进行一一替换。

例如,将a.txt中包含大写字母的YES、Yes等替换成小写的yes。

sed 'y/YES/yes/' a.txt

(6).手动读取下一行命令"n"。

在sed的循环过程当中,每一个sed循环的第一步都是读取输入流的下一行到模式空间中,这是咱们没法控制的动做。但sed有读取下一行的命令"n"。

因为是读取下一行,因此它会触发自动输出的动做,因而就有了输出流。不只如此,还应该记住的是:只要有读取下一行的行为,在其真正开始读取以前必定有隐式自动输出的行为

但需注意,当没有下一行可供"n"读取时(例如文件的最后一行已经被读取过了),将输出模式空间内容后直接退出sed程序,使得"n"命令后的全部命令都不会执行,即便是那两个隐含动做。

相应的循环结构以下:

for ((line=1;line<=last_line_num;++line))
do
    read $line to pattern_space;
    while pattern_space is not null
    do
        execute cmd1 in SCRIPT;
        execute cmd2 in SCRIPT;
 ADDR1,ADDR2{ # "n" command if [ "$line" -ne "$last_line_num" ];then auto_print; remove_pattern_space; read next_line to pattern_space; else auto_print; remove_pattern_space; exit; fi }; 
        ……
        auto_print;
        remove_pattern_space;
    done
done

注意,是先判断是否有下一行可读取,再输出和清空pattern space中的内容,因此then和else语句中都有这两个动做。 也许感受上彷佛更应该像下面这样的优化形式:

ADDR1,ADDR2{              # "n" command
         auto_print;
         remove_pattern_space;
         [ "$line" -ne "$last_line_num" ] && read next_line to pattern_space || exit;
 };

但事实证实并不是如此,证实过程在本文结尾。此处暂不讨论这些复杂的东西,先看看"n"命令的示例。

例如,搜索a.txt中包含"redirect"字符串的行以及其下一行,并输出。

sed -n '/redirect/{p;n;p}' a.txt

再例以下面的命令。

echo -e "abc\ndef\nxyz" | sed '/abc/{n;=;p}' 
abc
2
def
def
xyz

从结果中能够分析出,"n"读取下一行前输出了"abc",而后当即读入了下一行,因此输出的行号是2而不是1,由于这时候行号计数器已经读取了下一行,随后命令"p"输出了该模式空间的内容,输出后还有一次自动输出的隐含动做,因此"def"被输出了两次。

(7).替换命令"s"。

这是sed用的最多的命令。两个字就能归纳其功能:替换。将匹配到的内容替换成指定的内容。

"s"命令的语法格式为:其中"/"能够替换成任意其余单个字符。

s/REGEXP/REPLACEMENT/FLAGS

它使用REGEXP去匹配行,将匹配到的那部分字符替换成REPLACEMENT。FLAGS是"s"命令的修饰符,常见的有"g"、"p"和"i"或"I"。

  • "g":表示替换行中全部能被REGEXP匹配的部分。不使用g时,默认只替换行中的第一个匹配内容。此外,"g"还能够替换成一个数值N,表示只替换行中第N个被匹配的内容。

  • "p":输出替换后模式空间中的内容。

  • "i"或"I":REGEXP匹配时不区分大小写。

REPLACEMENT中可使用"\N"(N是从1到9的整数)进行后向引用,所表明的是REGEXP第N个括号(...)中匹配的内容。另外,REPLACEMENT中能够包含未转义的"&"符号,这表示引用pattern space中被匹配的整个内容。须要注意,"&"是引用pattern space中的全部匹配,不只仅只是括号的分组匹配。

例如,删除a.sh中全部"#"开头(能够包括前导空白)的注释符号"#",但第一行"#!/bin/bash"不处理。

sed -i '2,$s/^[ \t]*#//' a.sh

为a.sh文件中的第5行到最后一行的行首加上注释符号"#"。

sed '5,$s/^/#/' a.sh

将a.sh中全部的"int"单词替换成"SIGINT"。

sed 's/\bint\b/SIGINT/g' a.sh

将a.sh中"cmd1 && cmd2 || cmd3"的cmd2和cmd3命令对调个位置。

sed 's%&&\(.*\) ||\(.*\)%\&\&\2 ||\1%' a.sh

这里使用了"%"代替"/",且在REPLACEMENT部分对"&"进行了转义,由于该符号在REPLACEMENT中时表示的是引用REGEXP所匹配的全部内容。

(8).追加、插入和修改命令"a"、"i"、"c"。

这3个命令的格式是"[a|i|c] TEXT",表示将TEXT内容队列化到内存中,当有输出流或者说有输出动做的时候,半路追上输出流,分别追加、插入和替换到该输出流而后输出。追加是指追加在输出流的尾部,插入是指插入在输出流的首部,替换是指将整个输出流替换掉。"c"命令和"a"、"i"命令有一丝不一样,它替换结束后当即退出当前SCRIPT循环,并进入下一个sed循环,所以"c"命令后的命令都不会被执行。

例如:

echo -e "abc\ndef" | sed '/abc/a xyz'
abc
xyz
def

其实"a"、"i"和"c"命令的TEXT部分写法是比较复杂的,若是TEXT只是几个简单字符,如上便可。但若是要TEXT是分行文本,或者包含了引号,或者这几个命令是写在"{}"中的,则上面的写法就没法实现。须要使用符号"\"来转义行尾符号,这表示开启一个新行,此后输入的内容都是TEXT,直到遇到引号或者";"开头的行时。

例如,在a.sh的#!/bin/bash行后添加一个注释行"# Script filename: a.sh"以及一个空行。因为是追加在尾部,因此使用"a"命令。

sed '\%#!/bin/bash%a\# Script filename: a.sh\n' a.sh

"a"命令后的第一个反斜线用于标记TEXT的开始,"\n"用于添加空白行。若是分行写,或者"a"命令写在大括号"{}"中,则格式以下:

sed '\%#!/bin/bash%a\
# Script filename: a.sh\n
' a.sh

sed '\%#!/bin/bash%{p;a\
# Script filename: a.sh\n
;p}' a.sh

最后须要说的是,这3个命令的TEXT是存放在内存中的,不会进入模式空间,所以不受"-n"选项或某些命令的影响。此外,这3个命令依赖于输出流,只要有输出动做,无论是空输出流仍是非空的输出流,只要有输出,这几个命令就会半路"劫杀"。若是不理解这两句话,这3个命令的结果有时可能会比较疑惑。

例如,"a"命令是追加在当前匹配行行尾的,但为何下面的"haha"却插入到匹配行"def"的前面去了呢?

echo -e "abc\ndef\nxyz" | sed '/def/{a\ haha ;N}'

abc
haha
def
xyz

阅读了下面的"N"命令以后,再回头看这个示例,应该能知道为何。在sed修炼系列(四):sed中的疑难杂症中给出了解释。

(9).多行模式命令"N"、"D"、"P"简单说明。

在前面已经解释了"n"、"d"和"p"命令,sed还支持它们的大写命令"N"、"D"和"P"。

  • "N"命令:读取下一行内容追加到模式空间的尾部。其和"n"命令不一样之处在于:"n"命令会输出模式空间的内容(除非使用了"-n"选项)并清空模式空间,而后才读取下一行到模式空间,也就是说"n"命令虽然读取了下一行到模式空间,但模式空间仍然是单行数据。而"N"命令在读取下一行前,虽然也有自动输出和清空模式空间的动做,但该命令会把当前模式空间的内容锁住,使得自动输出的内容为空,也没法清空模式空间,而后读取下一行追加到当前模式空间中的尾部。追加时,原有内容和新读取内容使用换行符"\n"分隔,这样在模式空间中就实现了多行数据。即所谓的"多行模式"。 另外,当没法读取到下一行时(到了文件尾部),将直接退出sed程序,使得"N"命令后的命令不会再执行,这和"n"命令是同样的。

  • "D"命令:删除模式空间中第一个换行符"\n"以前的内容,而后当即回到SCRIPT循环的顶端,即进入下一个SCRIPT循环。若是"D"删除后,模式空间中已经没有内容了,则SCRIPT循环自动退出进入下一个sed循环;若是模式空间还有剩余内容,则继续从头执行SCRIPT循环。也就是说,"D"命令后的命令不会被执行。

  • "P"命令:输出模式空间中第一个换行符"\n"以前的内容。

"N"、"D"和"P"命令做用很是大,它们是绝佳的组合命令,由于借助它们能实现"窗口滑动"技术,这对于复杂的文本行操做来讲大有裨益。但显然,这不是本文的内容,在sed修炼系列(三):sed高级应用之实现窗口滑动技术中详细说明了这3个命令的功能。

此处按照惯例,仍是给出它们的大体循环结构:其中"N"命令的if判断和前文的"n"同样,在本文结尾证实

# "N"命令的大体循环结构 
for ((line=1;line<=last_line_num;++line))
do
    read $line to pattern_space;
    while pattern_space is not null
    do
        execute cmd1 in SCRIPT;
        execute cmd2 in SCRIPT;
 ADDR1,ADDR2{ # "N" command if [ "$line" -ne "$last_line_num" ];then lock pattern_space; auto_print; remove_pattern_space; unlock pattern_space; append "\n" to pattern_space; read next_line to pattern_space; else auto_print; remove_pattern_space; exit; fi }; 
        ……
        auto_print;
        remove_pattern_space;
    done
done

# "D"命令的大体循环结构
for ((line=1;line<=last_line_num;++line))
do
    read $line to pattern_space;
    while pattern_space is not null
    do
        execute cmd1 in SCRIPT;
        execute cmd2 in SCRIPT;
 ADDR1,ADDR2{ # "D" command delete first line in pattern_space; continue; }; 
        ……
        auto_print;
        remove_pattern_space;
    done
done

# "P"命令的大体循环结构
for ((line=1;line<=last_line_num;++line))
do
    read $line to pattern_space;
    while pattern_space is not null
    do
        execute cmd1 in SCRIPT;
        execute cmd2 in SCRIPT;
 ADDR1,ADDR2{ # "P" command print first line in pattern_space; }; ……
        auto_print;
        remove_pattern_space;
    done
done

(10).buffer空间数据交换命令"h"、"H"、"g"、"G"、"x"简单说明。

sed除了维护模式空间(pattern space),还维护另外一个buffer空间:保持空间(hold space)。这两个空间初始状态都是空的。

绝大多数时候,sed仅依靠模式空间就能达到目的,但有些复杂的数据操做则只能借助保持空间来实现。之因此称之为保持空间,是由于它是暂存数据用的,除了仅有的这几个命令外,没有任何其余命令能够操做该空间,所以借助它能实现数据的持久性。

保持空间的做用很大,它和模式空间之间的数据交换能实现不少看上去不能实现的功能,是实现sed高级功能所必须的,例如"窗口滑动"。一样,这不是本文的内容。因此只简单解释这几个命令的做用:

  • "h"命令:将当前模式空间中的内容覆盖到保持空间。

  • "H"命令:在保持空间的尾部加上一个换行符"\n",并将当前模式空间的内容追加到保持空间的尾部。

  • "g"命令:将保持空间的内容覆盖到当前模式空间。

  • "G"命令:在模式空间的尾部加上一个换行符"\n",并将当前保持空间的内容追加到模式空间的尾部。

  • "x"命令:交换模式空间和保持空间的内容。

注意,不管是交换、追加仍是覆盖,原空间的内容都不会被删除。


5.总结

看到这里,对sed已经有了一些概念,也许已经发现了sed的重点在于各选项和各命令是如何影响sed循环以及SCRIPT循环的。确实如此,在info sed文档中,虽然没有将这些工做机制详细描述,但各选项各命令说明中,在须要的时候都提到了这些细节,而我所作的只不过是将其系统性地描述出来、作一些深刻,再给几个示例解释,并使用通俗易懂的循环结构来展现这些机制。

最后,验证前文"n"和"N"命令留下的疑问:"n"和"N"命令是先判断是否还有下一行,再自动输出的。也就是证实下面两个判断语句采用前者仍是后者的问题。

ADDR1,ADDR2{              # "n" command
     if [ "$line" -ne "$last_line_num" ];then
         auto_print;
         remove_pattern_space;
         read next_line to pattern_space;
     else
         auto_print;
         remove_pattern_space;
         exit;
     fi
 }; 

 ADDR1,ADDR2{              # "n" command
         auto_print;
         remove_pattern_space;
         [ "$line" -ne "$last_line_num" ] && read next_line to pattern_space || exit;
 };

虽而后者看上去代码更优化,但事实上采用的是前者。要证实这一点不太容易,好在我想出了下面的方法来证实。下面的示例中使用的是"N",它和"n"在判断逻辑上的行为是一致的。

[root@xuexi ~]# echo -e "abc\ndef\nxyz" | sed '/def/{a\ 
haha
;N}' abc haha def xyz [root@xuexi ~]# echo -e "abc\ndef" | sed '/def/{a\     
haha
;N}' abc def haha

在以上两个命令中,第一个命令"haha"是插入在匹配行"def"的前面,而第二个命令则是插入在"def"的后面。彷佛根据"a"命令的做用来讲,第二个命令才是意料之中的结果。

首先,解释第一个命令为什么"haha"会出如今匹配行"def"的前面。当sed读取的行能匹配"def"时,将队列化"haha"到内存中,并在有输出流的时候追加到输出流尾部。因为这里的输出流来自于"a"命令后的"N"命令,该命令将模式空间锁住,使得隐含动做自动输出的内容为空,但队列化的内容仍是发现了这个空输出流,因而追加在这个空流的尾部。再以后,"N"将下一行读取到模式空间中,到了SCRIPT循环的结尾,再次自动输出,此时模式空间有两行:"def" 和 "xyz",这两行同时被输出。显然,在"def"被输出以前,队列化的内容已经随着空输出流而输出了。

再解释为什么第二个命令的结果中"haha"在"def"以后,这也是待证实的疑问。第二个命令中,因为"def"已是输入流的最后一行,"N"已经没法再读取下一行,因而输出当前模式空间内容并退出sed程序。假设,"n"或"N"命令是先自动输出、清空模式空间内容,再判断是否有下一行可读取的,那么在判断以前自动输出时,"N"不知道是否还有下一行,因而队列化的内容应该同第一个命令同样,插入在"def"以前。但结果却并不是如此。若是先判断是否有下一行可供读取,再输出、清空模式空间,则队列化内容是跟随着"N"退出sed程序前输出的,这正符合第二个命令的结果。

相关文章
相关标签/搜索