实验与例子

实验与例子

在评论中这个问题出现了一种情况,各种 sed 实现在一个相当简单的程序上存在分歧,并且我们(或至少我)无法确定规范对其的实际要求。

问题是从已删除行开始的范围的行为:

1d;1,2d

第2行是否应该删除即使在到达该命令之前范围的起点已被删除?我最初的期望是“不”,与 BSD sed 一致,而 GNU sed 说“是”,并且检查规范文本并不能完全解决问题。

(至少)符合我的期望的是 macOS 和 Solarissed以及 BSD sed。 (至少)GNU 和 Busyboxsed以及这里的许多人持不同意见。前两个是经过 SUS 认证的,而其他的可能更广泛。哪种行为是正确的?


规范文本对于两个地址范围说:

sed然后实用程序应按顺序应用其地址选择该模式空间的所有命令,直到命令开始下一个周期或退出。

具有两个地址的编辑命令应选择从与第一个地址匹配的第一个模式空间到与第二个地址匹配的下一个模式空间的包含范围。 [...] 从选定范围后的第一行开始,sed 将再次查找第一个地址。此后,应重复该过程。

可以说,2号线 之内“从与第一个地址匹配的第一个模式空间到与第二个地址匹配的下一个模式空间的包含范围”,无论起始点是否已被删除。另一方面,我预计第一个d会进入下一个周期,而不会给该范围提供开始的机会。 UNIX™ 认证的实现符合我的预期,但可能不符合规范的要求。

接下来是一些说明性实验,但关键问题是:什么应该 sed当范围从删除的行开始时做什么?


实验与例子

该问题的简化演示如下,它打印额外的行副本而不是删除它们:

printf 'a\nb\n' | sed -e '1d;1,2p'

这提供了sed两行输入,ab。该程序做了两件事:

  1. 删除第一行1d。命令d将要

    删除模式空间并开始下一个循环。和

  2. 除了自动打印每行收到的内容之外,还可以选择从 1 到 2 的行范围并明确打印它们。因此,包含在该范围内的行应该出现两次。

我的期望是这应该打印

b

只是,范围不适用,因为1,2在第 1 行期间从未达到(因为d已经跳到下一个周期/行),因此范围包含永远不会开始,同时a已被删除。 macOS 和 Solaris 10 的一致 Unix会产生此输出, Solaris 和 BSD 中的sed非 POSIX通常也会产生此输出。sedsed

另一方面,GNU sed 打印

b
b

表明它解释了范围。无论是在 POSIX 模式下还是非 POSIX 模式下都会发生这种情况。 Busybox 的 sed 具有相同的行为(但行为并不总是相同,因此它似乎不是共享代码的结果)。

进一步实验

printf 'a\nb\nc\nd\ne\n' | sed -e '2d;2,/c/p'
printf 'a\nb\nc\nd\ne\n' | sed -e '2d;2,/d/p'

发现它似乎将从已删除行开始的范围视为从下列的线。这是可见的,因为/c/与结束范围不匹配。使用/b/来启动范围确实不是行为与 相同2


我使用的最初的工作示例是

printf '%s\n' a b c d e | sed -e '1{/a/d;};1,//d'

作为删除第一个/a/匹配项之前的所有行的方法,即使它位于第一行(GNU sed 的用途0,/a/d- 这是尝试与 POSIX 兼容的再现)。

有人建议这应该删除最多第二如果第一行匹配/a/(如果没有第二个匹配则匹配整个文件),这似乎是合理的 - 但同样,只有 GNU sed 这样做。 macOS sed 和 Solaris 的 sed 都会生成

b
c
d
e

为此,正如我所期望的那样(GNU sed 通过删除未终止的范围产生空输出;Busybox sed 仅打印de,无论如何这显然是错误的)。一般来说,我认为他们通过了认证一致性测试意味着他们的行为是正确的,但足够多的人提出了其他建议,我不确定,规范文本并不完全令人信服,测试套件也不能非常全面。

显然,由于存在不一致,今天编写该代码实际上是不可移植的,但是理论上它应该在任何地方都具有一种含义或另一种含义。我认为这是一个错误,但我不知道要针对哪个实现来报告它。我目前的观点是 GNU 和 Busybox sed 的行为与规范不一致,但我可能会弄错。

这里 POSIX 需要什么?

答案1

这个问题是在 2012 年 3 月在 Austin 小组邮件列表中提出的。以下是关于此问题的最终消息(由 Austin 小组(维护 POSIX 的机构)的 Geoff Clare 提出,他也是首先提出这个问题的人)。这里是从 gmane NNTP 接口复制的:

Date: Fri, 16 Mar 2012 17:09:42 +0000
From: Geoff Clare <gwc-7882/[email protected]>
To: austin-group-l-7882/[email protected]
Newsgroups: gmane.comp.standards.posix.austin.general
Subject: Re: Strange addressing issue in sed

Stephane Chazelas <[email protected]> wrote, on 16 Mar 2012:
>
> 2012-03-16 15:44:35 +0000, Geoff Clare:
> > I've been alerted to an odd behaviour of sed on certified UNIX
> > systems that doesn't seem to match the requirements of the
> > standard.  It concerns an interaction between the 'n' command
> > and address matching.
> > 
> > According to the standard, this command:
> > 
> > printf 'A\nB\nC\nD\n' | sed '1,3s/A/B/;1,3n;1,3s/B/C/'
> > 
> > should produce the output:
> > 
> > B
> > C
> > C
> > D
> > 
> > GNU sed does produce this, but certified UNIX systems produce this:
> > 
> > B
> > B
> > C
> > D
> > 
> > However, if I change the 1,3s/B/C/ to 2,3s/B/C/ then they produce
> > the expected output (tested on Solaris and HP-UX).
> > 
> > Is this just an obscure bug from common ancestor code, or is there
> > some legitimate reason why this address change alters the behaviour?
> [...]
> 
> I suppose the idea is that for the second 1,3cmd, line "1" has
> not been seen, so the 1,3 range is not entered.

Ah yes, now it makes sense, and it looks like the standard does
require this slightly strange behaviour, given how the processing
of the "two addresses" case is specified:

    An editing command with two addresses shall select the inclusive
    range from the first pattern space that matches the first address
    through the next pattern space that matches the second.  (If the
    second address is a number less than or equal to the line number
    first selected, only one line shall be selected.) Starting at the
    first line following the selected range, sed shall look again for
    the first address. Thereafter, the process shall be repeated.

It's specified this way because the addresses can be BREs, but if
the same matching process is applied to the line numbers (even though
they can only match at most once), then the 1,3 range on that last
command is never entered.

-- 
Geoff Clare <g.clare-7882/[email protected]>
The Open Group, Apex Plaza, Forbury Road, Reading, RG1 1AX, England

以下是杰夫引用的(由我)其余消息的相关部分:

I suppose the idea is that for the second 1,3cmd, line "1" has
not been seen, so the 1,3 range is not entered.

Same idea as in

printf '%s\n' A B C | sed -n '1d;1,2p'

whose behavior differ in traditional (heirloom toolchest at
least) and GNU.

It's unclear to me whether POSIX wants one behavior or the
other.

所以,(根据 Geoff 的说法)POSIX 是清除GNU 行为不合规。

确实,它不太一致(seq 10 | sed -n '1d;1,2p'与相比seq 10 | sed -n '1d;/^1$/,2p'),即使对于那些不了解范围如何处理的人来说可能不那么令人惊讶(甚至杰夫最初发现了一致的行为“奇怪的”)。

没有人愿意将其作为错误报告给 GNU 人员。我不确定我是否会将其视为错误。也许最好的选择是更新 POSIX 规范,以允许这两种行为明确表明人们不能依赖其中任何一种。

编辑。我现在已经了解了sed70 年代末 Unix V7 中的原始实现,看起来很像数字地址的行为不是有意的,或者至少没有完全考虑到。

相反,随着 Geoff 对规范的阅读(以及我对它发生原因的原始解释),在:

seq 5 | sed -n '3d;1,3p'

应输出第 1、2、4 和 5 行,因为这一次,它是1,3pranged 命令从未遇到过的结束地址,如seq 5 | sed -n '3d;/1/,/3/p'

然而,在原始实现中并没有发生这种情况,我尝试过的任何其他实现也没有发生这种情况(busyboxsed返回第 1、2 和 4 行,看起来更像是一个错误)。

如果你看UNIX v7 代码,它确实检查当前行号是否为更大比(数字)结束地址,然后超出范围。事实是它不会对起始地址执行此操作看起来更像是一种疏忽,而不是有意的设计。

这意味着目前没有任何实现实际上符合 POSIX 规范在这方面的解释。

GNU 实现的另一个令人困惑的行为是:

$ seq 5 | sed -n '2d;2,/3/p'
3
4
5

由于跳过了第 2 行,因此2,/3/在第 3 行(编号 >= 2 的第一行)上输入 。但正是这条线造就了我们进入范围,不检查结尾地址。情况变得更糟busybox sed

$ seq 10 | busybox sed -n '2,7d; 2,3p'
8

由于删除了第 2 至 7 行,因此第 8 行是第一个 >= 2 的行,因此 2,3 范围为进入然后!

相关内容