有一文件passwd2,在用sort+sed命令去重的过程中,用到N P D,这是一个十分有趣的命令,值得分析讨论。
本文重点描述了执行的循环过程。也纠正了一些网上不正确的表述:多行模式空间不是保持空间。
cat passwd2
root:x:0:0:root:/root:/bin/bash
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
bin:x:1:1:bin:/bin:/sbin/nologin
sync:x:5:0:sync:/sbin:/bin/sync
shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
halt:x:7:0:halt:/sbin:/sbin/halt
mail:x:8:12:mail:/var/spool/mail:/sbin/nologin
operator:x:11:0:operator:/root:/sbin/nologin
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
文件中有多行重复,如何去重呢?主要方法有:sort+uniq方法,awk方法和sort+sed方法。其中,sort+sed方法主要代码:
sort passwd2 |sed '$!N;/^\(.*\)\n\1$/!P;D'
一、sed工作原理
sed在正常情况下,将处理的行读入“pattern space”(模式空间),脚本中的“sed-command(sed命令)”就一条接着一条进行处理,直到脚本执行完毕。然后该行被输出,(pattern space)模式空间被清空;接着,再重复执行刚才的动作,文件中的新的一行被读入,直到文件处理完毕。
由于种种原因,如果用户希望在某种条件下脚本中的某行在(pattern space)模式空间保留,以便下一次使用,这都有可能使sed在处理文件的时候,不按照正常的流程,这时候就需要用sed高级命令来满足需求,这就用到命令n和命令N。
命令n:读取下一行到pattern space。
由于pattern space中有按照正常流程读取的内容,使用n命令后,pattern space中又有了一行,此时,pattern space中有2行内容,但是先读取的那一行不会被取代、覆盖或删除;当n命令后,还有其他命令的时候,如p,此时打印出的结果是n命令读取的那一行的内容。
命令N:读取下一行到pattern space中,追加到原来行内容的后面。如原来的行内容是line1,下一行内容是line2。执行N命令后,pattern space中内容是:line1\nline2。
为了理解上述概念,我们可以参考如下知识:
单行模式空间
是指模式空间中仅有一行的情况。sed命令使用中,模式空间中仅读入文件的一行、处理一行、输出一行处理结果。
多行模式空间
是指模式空间中具有二行或多行的情况。sed命令使用中,模式空间中只有n和N命令在读入一行数据没有处理的情况下,又读取了另一行的数据。模式空间的多行是逻辑一行,其中包含多个换行符(\n)、实际是物理多行。
二、N P D
sed的众多命令中,n,d,p和N,D,P应用广泛。二者有相同之处,也有重大差别。主要表现在:n,d,p用于单行模式空间,而N,D,P应用于多行模式空间。
n&N
n,next的简写。n命令不创建多行模式空间;N(Next)通过读取新的输入行,并将它添加到模式空间的现有内容之后来创建多行模式空间。简单说,N加一个\n, 然后追加下一行到当前的pattern space, 如果没有下一行,退出。 这也隐含了一个事实: N会改变input的行号。
有且只有N才能创建多行模式空间,才使P、D有了用武之地。
注:在模式空间中嵌入的换行符可以利用转移序列"\n"来匹配,在多行模式空间中"^"匹配多行模式空间中的第一个字条(前一行内容),而不匹配换行符后面的字符(后一行内容),同样"$"只匹配多行模式空间最后的换行符。
d&D
d删除单行模式空间的内容并导致读入新的输入行,并且在脚本顶端重新使用编辑方法。
D删除多行模式空间中第一个字条(前一行内容)。如前面多行模式空间中(line1\nline2)的line1\n,D不会导致读入新的输入行并且它返回到脚本的顶端,将后续指令应用于多行模式空间的剩余内容。
p&P
p 打印单行模式空间的行内容
P打印多行模式空间中(line1\nline2)的line1\n(包括\n)内容,\n换行符是打印不出来的。
N&P&D
P命令经常在N之后和D之前。N&D&P能建立一个输入/输出循环,用来维护多行模式空间,但是一次只输出一行。这个循环的目的是只(P)输出多行模式空间的第一行,然后删除(D)多行模式空间第一行,然后返回到脚本的顶端,将所有的命令应用于模式空间的第二行。
三、语句释义
sort passwd2 |sed '$!N;/^\(.*\)\n\1$/!P;D'
对文件先排序,再去除重复行。
1.sort passwd2
对文件进行排序,没有什么难度,十分简单的命令。
2.sed '$!N;/^\(.*\)\n\1$/!P;D'
去除重复行
$! 这个表示当前读入行不是最后一行的意思。$表示是最后一行的意思. !则是取反。也可以理解为,只有最后一行不执行后面的命令
N (当前读入行不是最后一行时)往模式空间中读入一行。使模式空间中有两行数据(创建多行模式空间)
/^\(.*\)\n\1$/!
先看:/^\(.*\)\n\1$/ 这个表示匹配一个pattern。这个pattern是说多行模式空间中的两行(被\n分隔的两行),内容是否相等。实现方法是\n后面的内容用\1来反向引用前面的分组。
综合理解这段代码: 第一行用一个分组(.*)(实际就是任意内容的意思)来表示,然后中间一个\n(就是换行符),最后第二行的内容直接引用第1分组,即两行内容比较,用此判断后面的动作。
最后加了一个叹号’!’,说明要取反,意思是两行内容不同的情况下执行后面的command,即P
P 是打印首行内容的意思 ^\(.*\)\n
最后一个命令是D
D 也是删除首行的意思 ^\(.*\)\n
注意这两个命令都是对多行模式空间的内容进行处理
四、循环剖析
结合前面的sed工作原理,及各项命令的了解,前面的梳理并不能让我们全面了解去重的过程。下面从循环过程,分析一下。
对原文件排序后,前面几行如下:
从上图可知,通过排序,原文件各行顺序都打乱了;重复的行都在一起。
当执行sed命令后,sed首先把标准输入的第一行读入模式空间,因为没有单行模式空间命令,第一行读入后没有任何变化。
'$!N;/^\(.*\)\n\1$/!P;D'中没有对第一行执行的命令。
第一次读入过程的结果:第一行没有什么输出;第一行内容存放在单行模式空间。
sed开始读取标准输入的第二行到模式空间,构成多行模式空间。
N命令:在第一行后面追加第二行
adm:x:3:4:adm:/var/adm:/sbin/nologin\nadm:x:3:4:adm:/var/adm:/sbin/nologin
/^\(.*\)\n\1$/匹配成功,后面的!P,要求不打印\n及前面的内容^\(.*\)(即第一行内容);最后命令D,删除^\(.*\)(即第一行内容),结束本次读入行过程。
第二次读入过程的结果:第二行没有什么输出;第二行内容存放在多行模式空间中。
接下来,继续读入第三行内容。
因为与第二行内容相同,第三行结束后效果一样。
第三次读入过程的结果:第三行没有什么输出;第三行内容存放在多行模式空间中。
接下来,继续读入第四行内容。
第四行内容追加到第三行后面,如下
adm:x:3:4:adm:/var/adm:/sbin/nologin\n bin:x:1:1:bin:/bin:/sbin/nologin
/^\(.*\)\n\1$/匹配不成功,后面的!P,要求打印\n及前面的内容^\(.*\)(即第三行内容);最后命令D,删除^\(.*\)(即第三行内容),结束本次读入行过程。
第四次读入过程的结果:第四行输出第三行内容(我们知道前三行都一样,最终只输出了一次);第四行内容存放在多行模式空间中。
接下来,继续读入第五行内容。
第五行内容追加到第四行后面,如下
bin:x:1:1:bin:/bin:/sbin/nologin \n daemon:x:2:2:daemon:/sbin:/sbin/nologin
/^\(.*\)\n\1$/匹配不成功,后面的!P,要求打印\n及前面的内容^\(.*\)(即第四行内容);最后命令D,删除^\(.*\)(即第四行内容),结束本次读入行过程。
第五次读入过程的结果:第五行输出第四行内容;第五行内容存放在多行模式空间中。
后面的过程,基本相同,不再一一描述。
主要结论:前后两行相同,没有输出;前后两行不相同,输出前一行的内容。
最后一行读入标准输入:
根据N命令的特点,最后一行是前一行(倒数第二行)的N对象。当时就读入了最后一行且比较了最后两行是否相等,由比较结果处理了倒数第二行到底打印?删除?
当没有下一行时,sed执行到最后一行,由于没有两行,因此“两行不相等”的条件仍然满足,所以最后一行也会原样输出!!
最后的D命令,删除模式空间内容,最终退出sed进程。
最后一次读入过程的结果:输出模式空间内存放的内容(最后一行内容)。