【问题标题】:Using sed/awk, I need to remove all lines in a file from the first occurrence of pattern1 up-to (but not including) the last occurrence of pattern2使用 sed/awk,我需要删除文件中从第一次出现 pattern1 到(但不包括)最后一次出现 pattern2 的所有行
【发布时间】:2016-02-02 21:38:26
【问题描述】:

使用 sed/awk,我需要删除文件中从第一次出现 pattern1 到(但不包括)最后一次出现 pattern2 的所有行。

考虑以下文本:

    <entity name="good">
    </entity>
    <entity name="bad">
    stuff to delete
    </entity>
    <entity name="bad">
    stuff to remove
    </entity>
    <entity name="bad2">
    </entity>
    <entity name="deleteMe2">
    </entity>
    <entity name="bad2">
    </entity>
    <entity name="good">
    </entity>

我想要以下结果

<entity name="good">
</entity>
<entity name="bad2">
</entity>
<entity name="good">
</entity>

我知道如何在 sed 中设置范围,但不知道如何匹配最后一次出现的 'bad2' 并且不将其包含在删除中。下面当然不会起作用,因为它将匹配第一个 bad2 并且不会删除 'deleteme2' 或 'bad2' 的第二个出现。

sed -i '/<entity name="bad"/,/<entity name="bad2"/d' file.xml

我正在处理的文件中可能有数百行“坏”/“deleteMe2”/“坏2”行,因此简单的行数不起作用。如果这是多个命令(它不必只是一个),我很好,但是效率越高越好,因为被修改的文件可能非常大。同样, -i 是因为我想就地删除它们之间的线。

注意:我对 SED 比对 AWK 更熟悉,但我愿意接受所有可以得到的帮助:)

【问题讨论】:

  • 看起来很像 XML。是 XML 吗?因为如果是这样,使用解析器几乎肯定会更好。
  • 是的,它是 XML,我完全不使用 sed/awk 来修改 XML,但是在这种情况下 XML 定义非常简单。从字面上看,你在上面看到的还有一些额外的文字。我没有真正提到的一个限制是我很可能必须在 Windows 机器上执行此操作,最有可能使用 gnused 或 gawk。如果在 sed/awk 中无法执行我所要求的操作,我会考虑将 perl 作为一种选择。
  • 当您在问题中重复标题时,问题会更加清晰。我先跳过标题,然后一头雾水。
  • 您需要删除和保留哪些部分?从您的“结果”中不清楚,它删除了 2 个坏部分、1 个 bad2 部分和 1 个 deleteMe2 部分。
  • @Brian 删除带有 bad 的第一行和所有后续行,直到最后一个 bad2 部分完成。中间的一切都很糟糕。

标签: regex bash awk sed


【解决方案1】:
$ cat tst.awk
NR==FNR {
    if (/"bad"/ && !begFnr) {
        begFnr = FNR
    }
    if (/"bad2"/) {
        endFnr = FNR
    }
    next
}
(FNR < begFnr) || (FNR >= endFnr)

$ awk -f tst.awk file file
<entity name="good">
</entity>
<entity name="bad2">
</entity>
<entity name="good">
</entity>

【讨论】:

  • 这看起来是最容易理解的解决方案,但我无法用它来替换文件中的任何内容。也许我做错了......我试图在上面的情况下用“转义”,因为我相信它们是特殊字符并且仍然没有让它执行“删除”/“替换”。上面还有什么我可能会丢失/错误的吗?最后一个问题,'awk -f tst.awk file file' 第二个'file'是干什么用的?
  • "s 不是正则表达式中的特殊字符,它们不需要转义。该文件必须列出两次,因为它需要 1 遍来确定 last 出现bad2 的位置,而第二遍则需要基于此实际进行行选择/删除。如果您按原样使用脚本,如图所示调用它,在您发布的输入文件上,它将产生显示的输出,因此如果它没有产生所需的输出,那么您做错了,我猜不出是什么。
  • 好吧,废话,也许我应该按照我被告知的去做,而不是尝试“修复”一些没有损坏的东西。这在使用两个“文件”值并删除转义后确实有效。感谢您的帮助!
  • 不客气。我想你会发现在 awk 中做这样的事情比在 sed 中做这些事情要简单和容易理解几个数量级!
【解决方案2】:

这对我来说看起来像 XML,所以我会强烈建议 regex 不是这项工作的工具。改用解析器:

#!/usr/bin/env perl
use strict;
use warnings;
use XML::Twig;

my $twig = XML::Twig -> new -> parsefile ( 'your_file.xml' ) ;
$_ -> delete for $twig -> findnodes ( '//entity[@name="bad"]');
$twig -> set_pretty_print('indented_a');
$twig -> print;

或者更全面地说:

for my $entity ( $twig -> findnodes ( '//entity') ) {
   if ( $entity -> att('name') eq "bad"
   or   $entity -> att('name') eq "deleteMe2" ) {
           $entity -> delete; 
   }
}

要仅删除“bad2”的第一个实例,您只需调用一次findnodes,然后删除第一个“命中”。

【讨论】:

  • 完全理解正则表达式不喜欢 XML。我正在使用的要求是 awk/sed,因为它很容易移植到 Windows。我将其传递给的人对 perl 的熟悉程度不如对 sed/awk 的熟悉,这就是为什么我在问题中专门提出这个问题。
【解决方案3】:

awk 来救援!

$ awk 'NR==FNR&&/\"bad\"/&&!s{s=NR;next} 
          NR==FNR&&/\"bad2\"/{e=NR;next} 
          NR!=FNR && (FNR<s || FNR>=e)' xml{,}

    <entity name="good">
    </entity>
    <entity name="bad2">
    </entity>
    <entity name="good">
    </entity>

我想可以进一步简化。两遍脚本,先标记行号,再打印第二次。

【讨论】:

    【解决方案4】:

    这可能对你有用(GNU sed):

     sed '/bad/,$!b;/bad2/h;//!H;$!d;g;/bad2/!d' file
    

    不在bad 和文件末尾之间的行,正常打印。否则,当匹配bad2 时,将这些行存储在保留空间中覆盖这些存储的行。删除除最后一行之外的所有行,将其替换为保留空间的内容。删除该行,除非它与 bad2 匹配。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-07-05
      • 2015-06-08
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多