sed修炼系列(四):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解析命令行的过程以及eval命令

显然,单引号内所有字符都成为了字面符号,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交互的问题可以得出一套结论:

  1. 遇到需要被shell解析的都不加引号,或者加双引号;
  2. 遇到shell和所执行命令共有的特殊字符时,要想被sed解析,必须加单引号,或者在双引号在加反斜线转义;
  3. 那些无关紧要的字符,无论加什么引号。

因此,使用命令替换的方式让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

由于old_passold_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/{amatched successful
;N}‘

aaa
bbb
matched successful
ccc
ddd

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

无论是sed自动读取下一行,还是"n"或"N"命令读取下一行,只要有读取动作,在其前面必然会输出模式空间的内容。当"N"读取下一行时,首先它会判断是否还有下一行可供读取,如果有,则先锁住模式空间,然后自动输出并清空模式空间,再解锁模式空间并向其尾部追加一个换行符"\n",最后读取下一行追加到换行符尾部。由于模式空间被锁住,使得自动输出时输出流是空流,也同样无法清空模式空间。注意,它不是禁止输出,虽然输出空流的结果和禁止输出是一样的,但输出空流它有输出动作,有输出流,会写入标准输出,而禁止输出则没有输出动作。如果没有下一行可供读取,则自动输出模式空间、清空模式空间并退出sed程序。过程大致如下所描述:

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

回到"a"命令和"N"命令结合的问题上。之所以"a"命令的队列化文本会插入在匹配行的前面,问题就出在输出空流上。"N"在准备读取下一行时,它有输出动作,即使输出结果为空。而"a"命令是时刻等待sed输出流的,只要一有输出流,立马就会追上去追加在输出流的屁股后面。因此,"matched successful"会追加在空流的尾部,追加之后"N"才会读入下一行,最后输出模式空间中的内容"ccc\nddd",也就得到前面"有悖期待"的结果。

sed系列文章:

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

回到系列文章大纲:http://www.cnblogs.com/f-ck-need-u/p/7048359.html

转载请注明出处:http://www.cnblogs.com/f-ck-need-u/p/7499309.html

注:若您觉得这篇文章还不错请点击下右下角的推荐,有了您的支持才能激发作者更大的写作热情,非常感谢!

html { font-family: sans-serif }
body { margin: 0 }
article,aside,details,figcaption,figure,footer,header,hgroup,main,nav,section,summary { display: block }
audio,canvas,progress,video { display: inline-block; vertical-align: baseline }
audio:not([controls]) { display: none; height: 0 }
[hidden],template { display: none }
a { background: transparent }
a:active,a:hover { outline: 0 }
abbr[title] { border-bottom: 1px dotted }
b,strong { font-weight: bold }
dfn { font-style: italic }
h1 { font-size: 2em; margin: 0.67em 0 }
mark { background: #ff0; color: #000 }
small { font-size: 80% }
sub,sup { font-size: 75%; line-height: 0; position: relative; vertical-align: baseline }
sup { top: -0.5em }
sub { bottom: -0.25em }
img { border: 0 }
svg:not(:root) { overflow: hidden }
figure { margin: 1em 40px }
hr { height: 0 }
pre { overflow: auto }
code,kbd,pre,samp { font-family: monospace, monospace; font-size: 1em }
button,input,optgroup,select,textarea { color: inherit; font: inherit; margin: 0 }
button { overflow: visible }
button,select { text-transform: none }
button,html input[type="button"],input[type="reset"],input[type="submit"] { cursor: pointer }
button[disabled],html input[disabled] { cursor: default }
button::-moz-focus-inner,
input::-moz-focus-inner { border: 0; padding: 0 }
input { line-height: normal }
input[type="checkbox"],input[type="radio"] { padding: 0 }
input[type="number"]::-webkit-inner-spin-button,
input[type="number"]::-webkit-outer-spin-button { height: auto }
input[type="search"] { }
input[type="search"]::-webkit-search-cancel-button,
input[type="search"]::-webkit-search-decoration { }
fieldset { border: 1px solid #c0c0c0; margin: 0 2px; padding: 0.35em 0.625em 0.75em }
legend { border: 0; padding: 0 }
textarea { overflow: auto }
optgroup { font-weight: bold }
table { border-collapse: collapse; border-spacing: 0 }
td,th { padding: 0 }
* { }
*::before,*::after { }
html { font-size: 62.5% }
body { font-family: "Helvetica Neue", Helvetica, Arial, "Microsoft Yahei", sans-serif; font-size: 14px; line-height: 1.42857143; color: #333333 }
input,button,select,textarea { font-family: inherit; font-size: inherit; line-height: inherit }
a { color: #428bca; text-decoration: none }
a:hover,a:focus { color: #2a6496; text-decoration: underline }
a:focus { outline: 5px auto -webkit-focus-ring-color }
figure { margin: 0 }
img { vertical-align: middle }
.hljs { display: block; padding: 0.5em; color: #333; background: #f8f8f8 }
.hljs-comment,.hljs-template_comment,.diff .hljs-header,.hljs-javadoc { color: #998; font-style: italic }
.hljs-keyword,.css .rule .hljs-keyword,.hljs-winutils,.javascript .hljs-title,.nginx .hljs-title,.hljs-subst,.hljs-request,.hljs-status { color: #333; font-weight: bold }
.hljs-number,.hljs-hexcolor,.ruby .hljs-constant { color: #099 }
.hljs-string,.hljs-tag .hljs-value,.hljs-phpdoc,.tex .hljs-formula { color: #d14 }
.hljs-title,.hljs-id,.coffeescript .hljs-params,.scss .hljs-preprocessor { color: #900; font-weight: bold }
.javascript .hljs-title,.lisp .hljs-title,.clojure .hljs-title,.hljs-subst { font-weight: normal }
.hljs-class .hljs-title,.haskell .hljs-type,.vhdl .hljs-literal,.tex .hljs-command { color: #458; font-weight: bold }
.hljs-tag,.hljs-tag .hljs-title,.hljs-rules .hljs-property,.django .hljs-tag .hljs-keyword { color: #000080; font-weight: normal }
.hljs-attribute,.hljs-variable,.lisp .hljs-body { color: #008080 }
.hljs-regexp { color: #009926 }
.hljs-symbol,.ruby .hljs-symbol .hljs-string,.lisp .hljs-keyword,.tex .hljs-special,.hljs-prompt { color: #990073 }
.hljs-built_in,.lisp .hljs-title,.clojure .hljs-built_in { color: #0086b3 }
.hljs-preprocessor,.hljs-pragma,.hljs-pi,.hljs-doctype,.hljs-shebang,.hljs-cdata { color: #999; font-weight: bold }
.hljs-deletion { background: #fdd }
.hljs-addition { background: #dfd }
.diff .hljs-change { background: #0086b3 }
.hljs-chunk { color: #aaa }
#container { padding: 15px }
pre { border: 1px solid #ccc; display: block }
pre code { white-space: pre-wrap }
.hljs,code { font-family: Monaco, Menlo, Consolas, "Courier New", monospace }
:not(pre)>code { padding: 2px 4px; font-size: 90%; color: #c7254e; background-color: #f9f2f4; white-space: nowrap }

时间: 2024-12-23 22:45:50

sed修炼系列(四):sed中的疑难杂症的相关文章

sed修炼系列(三):sed高级应用之实现窗口滑动技术

html { font-family: sans-serif } body { margin: 0 } article,aside,details,figcaption,figure,footer,header,hgroup,main,nav,section,summary { display: block } audio,canvas,progress,video { display: inline-block; vertical-align: baseline } audio:not([co

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

本文为花拳绣腿招式入门篇,主要目的是入门,为看懂sed修炼系列(二):武功心法做准备.虽然是入门篇,只介绍了基本工作机制以及一些选项和命令,但其中仍然包括了很多sed的工作机制细节.对比网上各sed相关文章以及介绍sed的书籍,基本上都只介绍了sed是如何使用的,却没有"How sed Works"这种工作机制的原理性内容,最多给出一段稍微解释下.即使是非常流行的<sed & awk>也只是零零散散地介绍了一些sed工作机制细节.我想本文必能刷新你对sed的认知.

RxJava入门系列四,Android中的响应式编程

RxJava入门系列四,Android中的响应式编程 在入门系列1,2,3中,我基本介绍了RxJava是如何使用的.但是作为一名Android开发人员,你怎么让RxJava能为你所用呢?这篇博客我将针对Android开发来介绍一下RxJava的使用场景. RxAndroid RxAndroid是为Android打造的RxJava扩展.通过RxAndroid可以让你的Android开发变得更轻松. 首先,RxAndroid中提供了AndroidSchedulers,你可以用它来切换Android线

解读Unity中的CG编写Shader系列四——unity中的圆角矩形shader

上篇文章中我们掌握了表面剔除和剪裁模式 这篇文章将利用这些知识实现一个简单的,但是又很常用的例子:把一张图片做成圆角矩形 例3:圆角矩形Shader 好吧我承认在做这个例子的时候走了不少弯路,由于本人对矩阵的知识掌握已经悉数还给老师,所以一开始用了一些笨办法计算圆角矩形区域. 我们知道TEXTCOORD0是一个以对象为坐标系的坐标,并且范围在该坐标的第一象限,取值为(0,0)到(1,1) 那么我们把每一张图片都看做一张1X1大小的矩形 我们要在1X1大小的矩形中擦除4个角,应该是这样: 以左上角

解读Unity中的CG编写Shader系列四??unity中的圆角矩形shader

转自 http://www.itnose.net/detail/6097625.html 上篇文章中我们掌握了表面剔除和剪裁模式 这篇文章将利用这些知识实现一个简单的,但是又很常用的例子:把一张图片做成圆角矩形 例3:圆角矩形Shader 好吧我承认在做这个例子的时候走了不少弯路,由于本人对矩阵的知识掌握已经悉数还给老师,所以一开始用了一些笨办法计算圆角矩形区域. 我们知道TEXTCOORD0是一个以对象为坐标系的坐标,并且范围在该坐标的第一象限,取值为(0,0)到(1,1) 那么我们把每一张图

RDLC报表系列(四) 简单的图表

继续接上一篇的内容,本文主要是讲图标的内容,本文就是简单的图标,复杂的柱状图和折线图在下一文章中介绍. 数据源还是上文RDLC报表系列(四) 矩阵中的相同 1.还是继续使用demo2的文件

[转]C# 互操作性入门系列(四):在C# 中调用COM组件

传送门 C#互操作系列文章: C#互操作性入门系列(一):C#中互操作性介绍 C#互操作性入门系列(二):使用平台调用调用Win32 函数 C# 互操作性入门系列(三):平台调用中的数据封送处理 C#互操作性入门系列(四):在C# 中调用COM组件 本专题概要: 引言 如何在C#中调用COM组件--访问Office 互操作对象 在C# 中调用COM组件的实现原理剖析 错误处理 小结 一.引言 COM(Component Object Modele,组件对象模型)是微软以前推崇的一个开发技术,所以

Exchange 2013SP1和O365混合部署系列四

前面的三篇算是准备工作,今天我们看下如何在Exchange 2013 SP1中配置启用混合部署.老规矩,先看图,特别注意的我会,指出. 在EAC面板有个混合选项.点击启用.然后会登录到0365. 继续下一步. 继续下一步. 继续下一步. 远程迁移需要一张公网的证书. 继续下一步,需要输入凭据. 下面会自动开始配置. 混合部署到这里,算是配置完成. 组织里面多了本地到O365的通道. O365里面则相反的. 下篇我们将介绍本地新建O365账号和本地到O365的迁移. 先到这里. Exchange

ICMP拒绝服务攻击(原始套接字系列四)

拒绝服务攻击(DoS)企图通过使被攻击的计算机资源消耗殆尽从而不能再提供服务,拒绝服务攻击是最容易实施的攻击行为.中美黑客大战中的中国黑客一般对美进行的就是拒绝服务攻击,其技术手段大多不够高明. ICMP实现拒绝服务攻击的途径有二:一者"单刀直入",一者"借刀杀人".具体过程分析如下:   ICMPFLOOD攻击 大量的 ICMP消息发送给目标系统,使得它不能够对合法的服务请求做出响应.中美黑客大战中的多数中国黑客采用的正是此项技术.ICMP FLOOD攻击实际上是