【问题标题】:SED: delete lines after pattern match while skipping a few lines, then delete the pattern lineSED:模式匹配后删除行,同时跳过几行,然后删除模式行
【发布时间】:2015-09-23 20:37:28
【问题描述】:

假设我想删除模式之后的一些行,同时跳过中间的一些行,然后删除模式本身的行。我可以使用以下两个命令来做到这一点:

sed -i '/PATTERN/{n;n;n;n;n;n;N;N;N;N;d}' file  # match pattern, skip 5 lines, then delete 5 consecutive lines
sed -i /PATTERN/d file  # delete line with pattern

但我想在一个命令中执行此操作。这里的问题很明显,因为模式更改会影响两个表达式,因此必须在单个表达式中指定更改。有没有办法做到这一点?

更新:

示例输入:

...
ifeq ($(ose),)
    dh_installdocs \
        $(archdir)/UserManual*.pdf $(archdir)/VirtualBox*.chm \
        $(addprefix $(archdir)/, LICENSE)
    rm $(addprefix $(archdir)/,UserManual*.pdf VirtualBox*.chm \
        LICENSE)
else
    dh_installdocs \
        $(archdir)/UserManual*.pdf
    rm $(addprefix $(archdir)/,UserManual*.pdf)
endif
...

示例输出:

...
    dh_installdocs \
        $(archdir)/UserManual*.pdf $(archdir)/VirtualBox*.chm \
        $(addprefix $(archdir)/, LICENSE)
    rm $(addprefix $(archdir)/,UserManual*.pdf VirtualBox*.chm \
        LICENSE)
...

更新 2:

示例文件可以在这里获取:http://download.virtualbox.org/virtualbox/5.0.4/VirtualBox-5.0.4.tar.bz2(debian/rules)

【问题讨论】:

  • 必须是sed 和一个命令吗?你能给出一个测试输入和输出的例子吗?但首先想到的是 - 如果您正在使用相同的模式进行键控,这是否意味着您正在删除然后跳过?
  • 是的,它必须是单行命令,最好是 sed。如果 sed 不是一个选项,那么也许其他人会这样做(我没有在 sed 之外寻找答案)。我将用一个例子来更新最初的帖子。
  • 为什么我的问题被否决了?
  • {之后建议使用x;之前有一个答案。这部分起作用,因为它从具有匹配模式的行中删除所有内容,同时留下一个空白行。有没有办法去掉整行?
  • 请告诉我我的问题出了什么问题,以便我解决。在发布之前,我总是尝试正确地提出我的问题。

标签: regex bash sed


【解决方案1】:

求救!

awk 's&&s--{print;next} d&&d--{next} /pattern/{d=5;s=5;next} 1'

使用 Ed Morton 的 smart counters 设置 s 跳过和 d 删除

对于您的模式,您需要转义特殊字符

awk 's&&s--{print;next} d&&d--{next} /\(\$\(ose\),\)/{d=5;s=5;next} 1'

【讨论】:

  • 如何使用($(ose),) 作为模式?
  • 是字面意思“($(ose),)”吗?使用引用的值作为变量并将/patttern/更改为$0~var
  • 是的,正如您在示例中看到的那样,它是字面意思。使用变量需要额外的一行,因此它无法达到目的。无论如何,我无法让它工作。
  • 设置 var 是为了方便起见,可以使用 awk -v 选项来完成,如果您愿意,可以通过转义 $ ( ) 来嵌入模式,就像在 /\(\$\(ose\),\)/ 中一样
【解决方案2】:

另一种方法:

sed '/($(ose),)/,/^endif/{/($(ose),)/d;/^else/,/^endif/d}' file

输出:

... dh_installdocs \ $(archdir)/UserManual*.pdf $(archdir)/VirtualBox*.chm \ $(添加前缀 $(archdir)/, 许可证) rm $(addprefix $(archdir)/,UserManual*.pdf VirtualBox*.chm \ 执照) ...

添加选项-i 以“就地”编辑。

【讨论】:

  • 现在向我开枪。这样一个简单而聪明的解决方法。为什么我以前没有想到这一点?无数次感谢你!
【解决方案3】:

这是一种使用 awk 的方法:

awk '/ifeq/ { n = 6; next } n && !--n { c = 1 } c && c++ < 6 { next }1' file

看起来有点棘手,但逻辑如下:

  • 当模式/ifeq/匹配时,将变量n设置为6并跳过该行
  • n &amp;&amp; !--n 仅在 n 为 1 时为真(这样写,n 只会递减直到达到 0)
  • 设置c 后,跳过每一行直到c 达到6
  • 末尾的1 表示打印任何未跳过的行(默认操作为
    { print }

【讨论】:

  • 这似乎可行,但如何将结果保存到文件中?添加-i 不起作用。
  • 执行此操作的标准方法是重定向到临时文件,然后覆盖原始文件:awk '...' file &gt; tmp &amp;&amp; mv tmp file
  • 我已经尝试将输出重定向到另一个文件,但是当我这样做时 awk 挂起(即等待输入)。
  • 对我来说听起来像是语法错误。使用我的答案中的代码并假设文件名为file,它将是:awk '/ifeq/ { n = 6; next } n &amp;&amp; !--n { c = 1 } c &amp;&amp; c++ &lt; 6 { next }1' file &gt; tmp &amp;&amp; mv tmp file
  • 这行得通,但是,生成的文件不正确。文件中缺少几行。
【解决方案4】:

如果它不必是sed,我会提供perl 可以在“sed 模式”下工作,并使用正则表达式以及一些更复杂的脚本逻辑。

例如一个for循环:

#!/usr/bin/env perl
use strict;
use warnings;

while ( <DATA> ) {
   if ( m/ifeq\ \(\$\(ose\)\,\)/ ) {

       print "". <DATA> for 1..5;
       <DATA> for ( 1..5 );
   }
   else { print };
}

__DATA__
...
ifeq ($(ose),)
    dh_installdocs \
        $(archdir)/UserManual*.pdf $(archdir)/VirtualBox*.chm \
        $(addprefix $(archdir)/, LICENSE)
    rm $(addprefix $(archdir)/,UserManual*.pdf VirtualBox*.chm \
        LICENSE)
else
    dh_installdocs \
        $(archdir)/UserManual*.pdf
    rm $(addprefix $(archdir)/,UserManual*.pdf)
endif
...

你可以像 sed 那样做“内联”:

perl -i -ne 'if ( m/ifeq\ \(\$\(ose\)\,\)/ ) { print "". <> for 1..5; <> for ( 1..5 );} else {print}'

或者,您可以使用 perl 的“范围运算符”来检测您是否介于两种模式之间。不过,这更多地取决于文件的其余部分(我假设“else”...“endif”并不罕见)。

【讨论】:

  • 是不是说sed不能用于这个?
  • 我不知道。 sed 可以做很多事情,但我在 15 年前开始使用 perl,当时我开始达到“shell 工具”的极限,并没有真正回头。
  • 尝试您的命令时出现以下错误:syntax error at -e line 1, near "else print " Execution of -e aborted due to compilation errors.
  • 那么可能需要大括号 - 抱歉,无法测试那个位。已编辑
  • 不幸的是,由于某种原因,您所做的更改现在使文件中的每一行都翻了一番。
最近更新 更多