【问题标题】:sed remove a line matching a pattern if not after another matching pattern如果不在另一个匹配模式之后,sed 删除匹配模式的行
【发布时间】:2023-09-20 20:21:01
【问题描述】:

文件内容应该是这样的:

foo
foobar
bar

foo 或 foobar 可以单独存在。但是 bar 必须在 foo 之后并且没有 foo 就不能发生。所以任何没有 foo 的 bar,我想删除。

对于我的用例(问题),bar 总是出现在 foo 之前和 foo 之后。

bar
...
foo
foobar
bar

所以我使用grep 查找出现次数,并使用sed 删除第一次出现的bar(如果有多个bar)。

但我想知道,是否可以使用 sed 或其他工具来实际找到上一次出现的 foo 并保持计数。如果它后面没有自己的 foo,则删除该栏。就像下面的所有案例一样。

1.(删除第一个小节)

bar
...
foo
foobar
bar

2.(删除第 2 条,因为即使它前面有一个 foo,它已经计入第 1 条)

foo
foobar
bar
...
bar

3.(删除第 2 和第 4 小节)

foo
foobar
bar
...
bar
...
foo
bar
...
bar

【问题讨论】:

  • 这里:foo \n text \n bar \n bar 你想同时删除还是保留第一个?
  • 没有。将保留第一个。 foo 和 bar 之间是否有任何文本都没有关系。但是如果 bar 存在而没有 foo,我想删除。

标签: linux awk sed


【解决方案1】:
awk '/^bar/{if (k) k=0; else next} /^foo/{k=1} 1' file

k 表示保留线路,在bar 之后发现foo

【讨论】:

    【解决方案2】:

    是否有可能使用 sed 或其他一些工具来实际找到上一次出现的 foo 并保持计数。

    sed 原则上可以对foo 的出现次数进行计数。例如,为每个 foo 行执行 H 命令会执行此操作,但结果计数的形式会有点难以使用。

    但听起来您实际上并不需要一个 count,而是需要一个 flag 来报告是否有任何 foos 已被看到。为此,我将只使用h 命令。一个整体的 sed 程序删除第一行之前的所有 bar 行,保持所有其他行不变,如下所示:

    # When a 'foo' line is encountered, copy it to the hold space
    /^foo$/ h
    # If is a 'bar' then print or delete it, as appropriate
    /^bar$/ {
    # Append a newline and the contents of the hold space to the pattern space
    G
    # If the pattern space (now) ends in `foo`, then print up to the first newline of it
    /foo$/ P
    # delete the contents of the pattern space and start the next cycle
    d
    }
    

    您可以将它放在一个文件中并使用sed-f 选项从那里读取命令,或者您可以通过删除 cmets 并用分号分隔符连接行来直接将其放在命令行上:

    sed '/^foo$/ h; /^bar$/ {G; /foo$/ P; d; }' input > output
    

    更新

    不过,更简单、更清晰的是通过地址范围表达您的意图:

    # For all lines from before the first through one containing foo
    0,/^foo$/ {
    # delete bar lines
    /^bar$/ d
    }
    

    或者在单个命令中:

    sed '0,/^foo$/ { /^bar$/ d; }' input > output
    

    但是,使用行号 0 作为地址或在地址范围内可能需要 GNU sed。它绝对适用于 GNU sed,但 sed 的 POSIX 规范并未明确表明它受支持。

    【讨论】:

    • 我喜欢使用 sed 的想法,因为我的 awk 不支持 -i。并且还喜欢您解释的方式,而不仅仅是给出答案。 sed 命令删除 foo 和 bar 的所有出现,而不管它们的出现模式(即使是好的模式)。让我四处寻找合适的。 :)
    • 我的错,@RatDon,我应该在发布之前更仔细地测试。我已经用类似的工作代码更新了答案。原来的问题是n 命令并没有像我声称的那样做——它读取下一行,但不会自动开始下一个循环。
    • 我以前不知道保持和模式空间。感谢您指出这一点,正在学习它们。关于您的回答,它仍然会删除 bar 的好条目以及坏条目。例如酒吧 \n... \n 富 \n...\n 酒吧。在这种情况下,两个栏都被删除了。
    • @RatDon,您提供的测试用例有点不清楚:您的行中是否有尾随空格字符?或者您是否有 DOS 行终止符?显示的 sed 代码匹配包含 exactly foobar 的行。如果您想更包容,可以调整正则表达式。如果线条具有设计的形式,它对我来说可以正常工作。
    • 我指的是第 3 个问题中的第一个测试用例。就我而言, foobar 包含尾随文本。我试图将其与通配符合并到您的答案中。但它仍然删除了所有栏。
    最近更新 更多