Linux sed 命令详解系列教程之各种问题解决(2)

由于old_pass和old_pass中包含了"/"和"$"符号,因此"s"命令的分隔符使用了"%"替代。再仔细观察new_pass,其内有"."符号,这是正则表达式的元字符,因此它还可以匹配其他情况。

2.反向引用失效问题

当正则表达式中使用二者选一的选项"|"时,如果分组括号()中的内容没有参与匹配,后向引用将不起作用。例如(a)\1u|b\1将只匹配"aau"的行,不匹配"ba"的行,因为在二者选一的第二个正则中\1代表的分组没有参与匹配,所以第二个正则中的\1失效,但是第一个正则中的\1有效。

这是正则匹配的问题,不只是sed,其它使用基础正则和扩展正则引擎的工具也一样会有这样的问题。

另外,在s命令中使用反向引用时,将不会引用"s"命令外面的分组。例如:

echo "ab3456cd" | sed -r "/(ab)/s/([0-9]+)/\1/"

得到的结果将是ab3456cd,而不是ababcd,而且如果此时使用\2引用,则会报错"invalid reference \2 on 's' command's RHS"。

3."-i"选项的文件保存问题

sed是通过创建一个临时文件,并将输出写入到该临时文件,然后重命名该临时文件为源文件来实现文件保存的。因此,sed会无视文件的只读性。

是否允许重命名或移入或删除文件,是由文件所在目录的权限控制的。如果目录为只读权限,则sed无法使用"-i"选项保存结果,即使该文件具有可读权限。

4.贪婪匹配问题

所谓的贪婪匹配,是指当正则表达式能匹配多个内容时,取最长的那个。最简单的例子,给定数据"abcdsbaz",正则表达式"a.*b"可以匹配该数据中"ab"和"abcdsb",由于贪婪匹配,它会取最长的"abcdsb"。

echo "abcdbaz" | grep -o "a.*b"
abcdb

基础正则表达式和扩展正则表达式一直以来的一个不足之处在于无法原生态克服贪婪匹配,像Perl正则或其他编程语言的正则实现的比较完整,在"*"或"+"这种多次重复的匹配后加上一个"?"就可以明确表示采取懒惰匹配的模式,例如"a.*?b"。

echo "abcdbaz" | grep -P -o "a.*?b"
ab

想要克服基础正则或扩展正则的贪婪匹配,只能"投机取巧"地采用不包含符号"[^]"来实现。例如上面的:

echo "abcdbaz" | grep -o "a[^b]*b"
ab

这种投机取巧的方式,性能比较差,因为基础或扩展正则表达式的引擎总是会先匹配出最长的内容,然后往回匹配,这称为"回溯"。例如"abcdsbaz"在被"a[^b]*b"匹配时,先匹配出"abcdsb",再一个字符一个字符地回退匹配,直到回退到第一个"b"才是最短的结果。

再例如,/etc/passwd文件中每行数据的格式如下:

rootx:0:0:root:/root:/bin/bash

如何使用sed向/etc/passwd中的每个用户问声好,输出格式大致为:"hello root"、"hello nobody"。

首先,得取出文件中的第一列,即用户名。但由于该文件中所有行都采用冒号分隔各字段,想要使用正则表达式匹配得到第一段,必须克服贪婪匹配。语句如下:

sed -r 's/^([^:]*):.*/hello \1/' /etc/passwd

注意,sed采用的是基础正则和扩展正则引擎,在克服贪婪匹配时,它必须先匹配出最长的,再回溯出最短的。

如果想取/etc/passwd中的前两个字段呢?只需将克服贪婪的正则当作整体重复一次即可。

sed -r 's/^([^:]*):([^:]*):.*/hello \1 \2/' /etc/passwd

取第三个字段?

sed -r 's/^([^:]*:){2}([^:]*):.*/hello \2/' /etc/passwd

取第三和第五个字段?没办法,只能将第四个字段显式标注出来。

sed -r 's/^([^:]*:){2}([^:]*):([^:]*):([^:]*):/hello \2 \4/' /etc/passwd

取第三道第5字段?更简单,重复3次就可以了。

sed -r 's/^([^:]*:){2}(([^:]*:){3}).*/hello \2/' /etc/passwd

但这样的结果中,第3到第5字段中必然会包含":"分隔符,想要去除它?洗洗睡吧!sed本就不擅长处理字段,克服贪婪匹配本就让表达式变得很复杂不易读,而且效率还不高。用它处理字段,绝对是吃撑了。

5.sed命令"a"和"N"的纠葛

sed的"a"命令作用是将提供的文本数据队列化在内存中,然后在模式空间内容输出时追加在输出流的尾部一并输出。

例如,在匹配行"ccc"后插入一行数据"matched successful"。

echo -e "aaa\nbbb\nccc\nddd" | sed '/ccc/a matched successful'
aaa
bbb
ccc
matched successful
ddd

咋一使用"a"命令,很顺利,没毛病。但是结合"N"试试看?

echo -e "aaa\nbbb\nccc\nddd" | sed '/ccc/{a\
matched successful
;N}'

aaa
bbb
matched successful
ccc
ddd

不是追加在尾部吗,怎么跑匹配行的前面去了?即使"N"读取了下一行,也应该是追加在"ddd"的下一行吧?想要真正弄明白这个问题,对sed模式空间的输出机制必须了如指掌,可以参考Linux sed 命令详解系列教程之入门篇。此处简单描述下"N"命令的输出机制。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/12892.html