这个问题来源于ChinaUnix的一篇帖子“sed地址和模式匹配的问题”。

man sed手册说明

Sed默认的命令执行范围是全局的,如果想仅对其中部分行执行命令,可以使用地址限制。在Manual手册中有一节关于地址的描述,摘取部分如下:

Sed commands can be given with no addresses, in which case the command will be executed for all input lines;

Sed默认是全局编辑的,因此如果不明确指定行的话,命令会在所有输入行上执行。

with one address, in which case the command will only be executed for input lines which match that address;

如果指定一个行地址,那么sed命令就限制在那一行执行。

or with two addresses, in which case the command will be executed for all input lines which match the inclusive range of lines starting from the first address and continuing to the second address.

如果给了两个地址,即地址对(或者地址范围),则命令在匹配的这个地址范围内执行。但是需要注意的几点是:

The syntax is addr1,addr2 (i.e., the addresses are separated by a comma); the line which addr1 matched will always be accepted, even if addr2 selects an earlier line; If addr2 is a regexp, it will not be tested against the line that addr1 matched.

上面的内容,大致意思是说:(a)

  • 对于像“addr1,addr2”这种形式的地址匹配,如果addr1匹配,则匹配成功,“开关”打开,在该行上执行命令,此时不管addr2是否匹配,即使addr2在addr1这一行之前;
  • 接下来读入下一行,看addr2是否匹配,如果addr2在addr1之前,则不匹配,不执行命令,关闭“开关”;如果addr2匹配,则执行命令,同样开关“关闭”;如果addr2在addr1之后,则一直处理到匹配为止,换句话说,如果addr2一直不匹配,则开关一直不关闭,因此会持续执行命令到最后一行。

After the address (or address-range), and before the command, a ! may be inserted, which specifies that the command shall only be executed if the address (or address-range) does not match.

如果地址或者地址对之后有一个"!",表明对匹配的行不执行后面的命令,刚好相反。

地址形式

Sed一般至少支持以下形式的地址表示:

  1. number: 这一种情况不多说。
  2. first~step: 从first行开始,每隔step行执行一次命令。详细的内容也可以参照手册:

    Match every step’th line starting with line first. For example,‘sed -n 1~2p’ will print all the odd-numbered lines in theinput stream, and the address 2~5 will match every fifth line, starting with the second. (This is an extension.)

  3. /regexp/: 同第一种方法类似,不过是匹配该正则的那一行。

在匹配行的时候,$是特殊的标记,匹配最后一行。1匹配第一行,而不是^,这点和正则不大一样。

另外,GNU Sed还支持以下几种特殊的地址对形式:(平常使用的基本是GNU Sed,即gsed)

  1. 0,addr2

    这种形式的地址对,默认第一个地址是匹配的,也就是匹配开关打开,直到找到匹配addr2的那行为止,匹配开关关闭。大多数情况下和1, addr2是一样的,除非addr2匹配文件的第一行,在这种时候,0, addr2就在第一行就关闭了,而1, addr2会继续往下找匹配的行。

    Start out in "matched first address" state, until addr2 is found. This is similar to 1,addr2, except that if addr2 matches the very first line of input, the 0,addr2 form will be at the end of its range, whereas the 1,addr2 form will still be at the beginning of its range.

    可以看一个例子:

    $ seq 6 | sed -n '0, /1/p'  # 打印第一行(/1/是正则表达式,因为第一行的内容是1)

    为什么不相同呢? 解释一下:

    • 0, addr2 这种形式默认第一个地址是匹配的,然后直到add2匹配为止。因此上述情况,只要看每一行是否匹配第二个地址就可以了addr2,因为第一行是匹配的,所以打印到第一行为止。
    • 1, addr2 这种形式就是普通形式,参考(a)红字说明部分,不管addr2匹配与否,第一行是匹配的,然后读入第二行,发现不匹配addr2,继续读,直到最后也没找到匹配,因此打印从第1行到最后一行之间的所有内容。

    但是这种地址对表示有一个限制,即addr2只能使用/regex/形式,如果使用行号,就会出错,不信可以试试。

  2. addr1,+N: 从匹配的addr1行开始,连续N行,当然包括addr1这一行。
  3. addr1,~N: 从匹配的addr1行开始,直到某一行,该行的行号是N的倍数为止。

    总结

    可以总结出几个关键点:

    • 地址对addr1, addr2的匹配方式 ,从匹配addr1的那行开始,打开匹配开关,直到匹配addr2的那行结束,关闭匹配开关,之后的行会忽略这个地址对,不再做匹配。(b)
    • 地址对addr1, addr2的匹配方式 ,假设addr1是number,即行号,如果新读入行的行号大于addr1,则匹配;小于addr1,则不匹配。(c)

      注意:如果addr2是行号,如果新读入行的行号小于addr2,则匹配,继续往下读;大于addr2,则不匹配,关闭匹配开关。刚好与上面的情况相反(这个比较好理解,可以辅助记住(c)这种情况)。

    最后引用chinaunix上的那个帖子的问题,以下两种情况结果为什么会不一样:

    $ seq 6 | sed '1,2d' | sed '1,2d'  # 结果返回5 6
    $ seq 6 | sed -e '1,2d' -e '1,2d' # 结果返回4 5 6

    显然这两种情况使用sed的命令形式是不一样的,第一种利用管道使用了sed两次,结果返回5 6,没什么问题;第二种情况在同一个sed命令中使用了两次1,2d,按常理应该是返回 3 4 5 6,结果返回 4 5 6,第3行竟然也被意外地删除了,为什么呢?

    解释:

  1. 首先第一行被读入,遇到第一组expression -> 1, 2d,第一行匹配成功(打开匹配开关),执行d命令,d命令清空模式空间的内容,因此不会再执行接下来的命令。
  2. 继续从标准输入读入第二行,同1
  3. 读入第三行,第一组expression匹配失败(因为3>2),因此试着执行第二组expersson->1,2d,因为3>1,打开匹配开关,执行d。(这里是关键)
  4. 读入第四行,执行第二组expersson->1,2d,因为4>2,匹配失败,关闭匹配开关,同时也不执行d。
  5. 因此,最后第1 2 3行被删除。