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

本文目录:
1 sed中使用变量和变量替换的问题
2 反向引用失效问题
3 "-i"选项的文件保存问题
4 贪婪匹配问题
5 sed命令"a"和"N"的纠葛

1.sed中使用变量和变量替换的问题

在脚本中使用sed的时候,很可能需要在sed中引用shell变量,甚至想在sed命令行中使用变量替换。也许很多人都遇到过这个问题,但引号却死活调试不出正确的位置。其实这不是sed的问题,而是shell的特性。搞懂sed如何解决引号的问题,对理解shell引号问题有很大帮助,触类旁通,以后在使用awk、MySQL等等自带语法解析的工具时就不会再疑惑。

例如下面想输出a.txt的倒数5行的语句。可能顺手就写出了下面的命令行:

total=`wc -l <a.txt`
sed -n '$((total-4)),$p' a.txt

但很不幸,这会报错。一方面,"$"在sed中是特殊符号,放在定址表达式中时,它表示的是输入流的最后一行的标记。而$(())中也出现了"$"符号,这会让sed去解析该符号。另一方面,$(())这部分是使用shell计算而不是使用sed计算的,因此必须要将其暴露给shell,以便能让shell能解析它。

再说说shell中单引号、双引号和不加引号的情况。

单引号:单引号内的所有字符变为字面符号。但注意:单引号内不能再使用单引号,即使使用了反斜线转义也不允许。

双引号:双引号内的所有字符变为字面符号,但"\"、"$"、"`"(反引号)除外,如果开启了"!"引用历史命令时,则感叹号也除外。

不使用引号:几乎等同于使用了双引号,但会进行大括号和波浪号扩展。

上面关于双引号的情况,描述的并不是真正的完整,但已足够。这些只是它们的字面意义,引号真正的意义在于:决定命令行中哪些"单词"需要被shell解析,也决定哪些是字面意义不用被shell解析

显然,单引号内所有字符都成为了字面符号,shell不会解析其内任何单词,例如单引号内变量不再被解析、命令替换和算术运算不再执行、不会进行路径扩展等等。总之,单引号内的字符全是普通字符,如果某些字符需要交给自带解析功能的命令解析,必须使用单引号。例如,"$"、"!"和"{}"在sed中均有特殊意义,要想让sed能解析它们,必须对它们使用单引号,否则必出错,或者产生歧义。例如下面3个sed语句中的符号都必须使用单引号才能得到正确结果。

sed '$d' filename
sed '1!d' filename
sed -n '2{p;q}' filename

而想要让特殊字符被shell解析,必须不能将其包围在单引号中,可以使用双引号,也可以不加任何引号,即使不加引号时可能看上去很怪异。例如,上面的算术运算$(())是想被shell解析的,因此必须使用单引号或者不加引号将其暴露给shell。所以正确的语句是:

sed -n $((total-4))',$p' a.txt
sed -n "$((total-4))"',$p' a.txt
sed -n "$((total-4)),\$p" a.txt

从肉眼看上去,这个语句的引号加的真的很怪异。但shell又不管丑美,它是死的,在划分命令行的时候它有自己的一套规则,规则怎样就怎样划分。

于是,关于sed如何和shell交互的问题可以得出一套结论:

遇到需要被shell解析的都不加引号,或者加双引号;

遇到shell和所执行命令共有的特殊字符时,要想被sed解析,必须加单引号,或者在双引号在加反斜线转义;

那些无关紧要的字符,无论加什么引号。

因此,使用命令替换的方式让sed输出倒数5行的语句如下:

sed -n `expr $(wc -l <a.txt) - 4`',$p' a.txt
上面的语句中,`expr $(wc -l <a.txt) - 4` 要被shell解析,因此必须不能使用单引号包围。而$p部分的"$"要被sed解析成最后一行,必须使用单引号以避免被shell解析。

更复杂一些,在sed的正则表达式中使用变量替换。例如,输出a.txt中以变量str字符串开头的行到最后一行。

str="abc"
sed -n /^$str/',$p' a.txt
因为没有使用任何引号,所以$str能如期被shell替换成"abc"。这个命令还有多种写法:

sed -n '/^'$str'/,$p' a.txt
sed -n "/^$str"'/,$p' a.txt
sed -n "/^$str/,\$p" a.txt
sed -n "/^$str/,"'$'p a.txt
给一个稍难一些的sed符号使用问题。将/etc/shadow中的最后一行的密码部分替换成"$1$123456$wOSEtcyiP2N/IfIl15W6Z0"。

[root@xuexi ~]# tail -n 1 /etc/shadow
userX:$6$hS4yqJu7WQfGlk0M$Xj/SCS5z4BWSZKN0raNncu6VMuWdUVbDScMYxOgB7mXUj./dXJN0zADAXQUMg0CuWVRyZUu6npPLWoyv8eXPA.::0:99999:7:::
替换语句如下:

old_pass="$(tail -n 1 /etc/shadow | cut -d':' -f2)"
new_pass='$1$123456$wOSEtcyiP2N/IfIl15W6Z0'
sed -n '$'s%$old_pass%$new_pass% /etc/shadow

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

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