网上看到这样一条命令,运用sed命令交换文件中任意两行数据,其代码应用知识众多,思路巧妙,符合项目学习训练的特点,很有启发性,特分析研究讨论。
一、案例
1.素材
先准备一个素材,文件passwd.demo中有6行数据
2.代码
交换^bin和^lp两行数据(^bin匹配行在前,这一点很重要)
分行编写代码,方便理解:
sed -r '/^bin/{
:a
N
s/(^bin[^\n]*)\n(([^\n]*\n)*)(^lp[^\n]*)$/\4\n\2\1/
t
ba
}' passwd.demo
为了提高可读性,每个sed命令都放在单独的行上。但是,可以选择将所有命令放在一行中,如下所示:
sed -r '/^bin/{:a;N;s/(^bin[^\n]*)\n(([^\n]*\n)*)(^lp[^\n]*)$/\4\n\2\1/;t;ba;}' passwd.demo
在把分行编写的代码转为一行时,仅需把换行改为分号";"即可。
3.效果
对照前面素材,已知达到目的,交换了两行数据。
二、知识点
行与行之间交换数据是常用训练项目。
两行交换实现起来并不难。学过编程的人都学过或遇到过变量交换:先把变量1交换给临时变量tmp,再把变量2交换给变量1,最后把临时变量交换给变量2。交换的实质:有一个舞台或中间环节!
sed两行交换正好有保持空间可用:sed读取行内容,在"模式空间"处理一下,然后保存在"保持空间"中,多次有效处理,最终达到交换的目的。
下面简单梳理一下本案例应用的知识。
1.循环
循环,其实就是一种重复,在满足指定的条件下,重复的做某些事情。可以说,循环通常都表现为条件转换的形式。
与其他编程语言一样,sed也提供了循环和分支语句来控制执行流程。虽没有专门的循环命令,但提供了跳转的命令,因而我们仍然可以实现循环,达到循环目的。
sed的循环方式类似于goto语句。sed可以跳到标签所标记的行,然后继续执行其余命令。在sed中,可以如下定义label:
:label
:start
:end
:up
在上面的示例中,冒号(:)之后的名称表示标语法称,一般称为标签名。
要跳转到标签,可以使用t/b命令,后跟标签名。如果省略标签名,则sed跳至文件的末尾。
2.条件判断
判断语句要求程序员指定一个或多个要测试的条件,以及条件为真时执行的语句(必需的)和条件为假时执行的语句(可选的)。
sed的条件判断没有if语句,没有switch语句,而是一个简单的b命令。
字符b是break的缩写。b/t都是sed中的分支(跳转)命令。
b的格式是'b label',也可以去掉中间的空格,写作'blabel'。其作用是从:label处继续执行脚本。label必须在脚本中定义,且对标签的长度有限制,具体的限制可以参考sed faq。如果b后没有带标签,则默认转到脚本结束处。
当然了,我们也可以对条件取反,也就是条件测试不通过时才跳转,这时候就要用到逻辑非运算符"! ",具体的语法格式如下:
:label
some_code_here
/<pattern/!t label
3.t b参数
定义了标签之后,如果要跳转到某个标签名,可以使用t命令。
t命令的语法格式为
t [label_name]
其中[label_name]为要跳转到的标签名。这是一个可选项,如果忽略,那么sed会跳转到sed命令文件的末尾,也就是结束所有sed命令。
条件为假时跳转到指定标签处,这时候就需要使用逻辑非运算符"! ",语法格式如下:
/pattern/!t label
t命令与b相似。不同点在于t是以前s命令的成功与否来决定是否跳转,如成功则跳转。如果在一个s命令后使用了多个条件跳转则第二个及其后的t都会失败。gnu sed还提供了与t相反的T命令。
4.N多行处理
N是把下一行加入到当前的模式空间里,使之进行后续处理,最后sed会默认打印模式空间里的内容。也就是说,sed及N是可以处理多行数据的。
sed是按行处理文本数据的,每次处理一行数据后,都会在行尾自动添加trailing newline,其实就是行的分隔符即换行符。连续两行执行一次sed命令,就可以把前一行的\n替换完成。
PS:sed中如何替换全部换行符
使用如下解决方案:
sed ':a;N;$!ba;s/\n/ /g' 文件名
这将在一个循环里读取整个文件,然后将换行符替换成一个空格。主要原理与过程:通过:a创建一个标记,通过N追加当前行和下一行到模式空间。如果处于最后一行前,跳转到之前的标记处。$!ba($!意思是不在最后一行做这操作)。
5.扩展正则
正则表达式REGEXP:
Regular Expressions,由一类特殊字符及文本字符所编写的模式,其中有些字符(元字符)不表示字符字面意义,而表示控制或通配的功能,类似于增强版的通配符功能,但与通配符不同。一般来说,通配符是用来处理文件名,而正则表达式是处理文本内容中字符。
正则表达式被很多程序和开发语言所广泛支持:vim,less,grep,sed,awk,nginx,mysql等。
正则表达式分两类:
基本正则表达式:BRE Basic Regular Expressions
扩展正则表达式:ERE Extended Regular Expressions
模式:由正则表达式的元字符及文本字符所编写的过滤条件
元字符分类:字符匹配、匹配次数、位置锚定、分组
扩展正则表达式的元字符:字符匹配、匹配次数、位置锚定、分组(与基本正则表达式基本相同;用法相似,除了词首、词尾锚定一样,其他只是在基本正则表达式中去掉转义字符)。
与基本正则表达式的比较:写法上比较简单,去掉了大量的转义字符;但需要匹配特殊字符时,扩展正则表达式需将特殊字符用[]括起来使用,这时用基本正则表达式比较方便。
6.分组与反向替换
(x) 匹配x并且捕获匹配项,又称捕获括号(capturing parentheses)
\n (此处\n指\后加一数字,不是换行符)n是一个反向引用(back reference),指向正则表达式中第n个括号(从左开始数)中匹配的子字符串
通俗地讲,括号在正则中可以用于分组,被括号括起来的部分可以称为子表达式,会被保存成一个子组。
所谓分组就是为了实现多个字符绑定在一起而加括号把这一组字符限定为一个整体。比如我们要表达0个或者多个a,那么可以直接a*,而如果表达零个或者多个ab,那么就用括号把ab括起来(ab)*,这个时候ab就是一个分组。
分组常用的的语法有:
(exp):匹配exp,并且捕获文本到自动命名的组里。
自动命名:就是一个正则表达式里如果有多个组,如果你没有给组添加自己的名字,那么正则表达式会自动给这几个组从左到右自动命名,从左到右依次是1到9。
手动命名:给组添加自己的名字。正则表达式会从左到右扫描2遍,先扫描自动命名再扫描手动命名。
反向引用主要用于重复搜索前面某个分组匹配的文本,表达简单,灵活快捷。
三、过程梳理
1.原理
循环合并两行;
行合并后同时进行数据替换;
判断跳转:替换不成功,返回循环起点;成功,结束循环。
2.代码解释
/^bin/ {
:a
N
s/(^bin[^\n]*)\n(([^\n]*\n)*)(^lp[^\n]*)$/\4\n\2\1/
t
ba
}
-r sed的选项,表示sed代码中可以使用扩展正则
/^bin/ '{ }' 仅处理模式匹配^bin的行
:a ...ba 设置名称为a的label(标签,标记) 设置返回标记点。这两项结合在一起,构成完整的代码段
ba 前面的代码为返回条件
N 合并两行且只能两行。只能循环执行N才可能合并多行
s/// 替换,可以采用正则或扩展正则表达式
第一个/ / 之间是要替换的字符或表达式
第二个/ / 之间是替换后的字符或反向引用
s/(^bin[^\n]*)\n(([^\n]*\n)*)(^lp[^\n]*)$/中
(^bin[^\n]*)匹配以bin开头的行,且作为1号分组,其中^放在括号内与外,功能相同;[^\n]表示除\和n以外的任意字符;*表示零次或多次;扩展正则时允许对()不加转义字符\;此项内容与下面几项雷同
\n 分组之间用换行符隔开
(([^\n]*\n)*) 匹配任意的行,且作为2号分组,其中含3号子分组
(^lp[^\n]*) 匹配以lp开头的行,且作为4号分组
/\4\n\2\1/表示反向引用,把1号和4号分组交换,4号分组后面有一个换行符\n
t 循环跳转参数。
如果t后有标签名,则跳转到标记点;没有标签名,则跳转到其代码后面(执行其他代码)。
此处没有标签名,又分两种情况:条件为假,执行后面的代码;条件为真,直接跳转到代码结束处。
b 循环跳转参数,后有标记名称a。执行这一步时跳转到标记点a。
具体循环过程:
执行第一次循环时,bin行与daemon行合并,替换时没有什么事发生,条件为假。在执行t时会根据上一步的false,跳转到下一行代码。而下一行ba是跳转到标记点,又开启第二次循环。第二次、第三次循环中替换条件都为假。第四次循环时,是lp开头行,替换条件为真,执行t,直接结束代码。
替换条件为真,就是我们需要的把两行内容交换。
小结
两行交换是常用训练项目。
两行交换有很多状况,也有许多相应方法,值得研究商榷。
本文是针对模式匹配行,然后交换数据。本人写过一篇小文《Linux centos7 sed命令学深一点》,介绍指定文件行号时如何交换两行内容,欢迎指导。
可以用vim处理。
可以进行shell编程实现目的。
学好linux三剑客十分重要,学习正则表达式也非常有必要。但没有具体项目,仅学习循环代码、依条件跳转,学习效果并不好,也不利于知识贯通。
循环变化不好理解,且很少有t b两参数同时使用的案例,本案例做到了,这称得上一份优秀的案例。