使用 sed(或 awk)删除模式上方的行范围

Ter*_*ior 32 sed awk shell-script text-processing

我有以下代码将删除带有该模式的banana行和其后的 2 行:

sed '/banana/I,+2 d' file
Run Code Online (Sandbox Code Playgroud)

到现在为止还挺好!但是我需要它在之前 删除 2 行banana,但是我无法使用“减号”或其他任何东西(类似于grep -v -B2 banana file应该做什么但不做什么):

teresaejunior@localhost ~ > LC_ALL=C sed '-2,/banana/I d' file
sed: invalid option -- '2'
teresaejunior@localhost ~ > LC_ALL=C sed '/banana/I,-2 d' file
sed: -e expression #1, char 16: unexpected `,'
teresaejunior@localhost ~ > LC_ALL=C sed '/banana/I,2- d' file
sed: -e expression #1, char 17: unknown command: `-'
Run Code Online (Sandbox Code Playgroud)

Gil*_*il' 26

Sed 不会回溯:一旦处理了一行,就完成了。所以“找一行并打印前N行”不会按原样工作,不像“找一行并打印下N行”容易嫁接。

如果文件不是太长,因为您似乎对 GNU 扩展没问题,您可以使用tac来反转文件的行。

tac | sed '/banana/I,+2 d' | tac
Run Code Online (Sandbox Code Playgroud)

另一个攻角是在像awk这样的工具中维护一个滑动窗口。从适应没有任何替代的grep的-A -B -C开关(打印前,后几行)?(警告:最低限度测试):

#!/bin/sh
{ "exec" "awk" "-f" "$0" "$@"; } # -*-awk-*-
# The array h contains the history of lines that are eligible for being "before" lines.
# The variable skip contains the number of lines to skip.
skip { --skip }
match($0, pattern) { skip = before + after }
NR > before && !skip { print NR h[NR-before] }
{ delete h[NR-before]; h[NR] = $0 }
END { if (!skip) {for (i=NR-before+1; i<=NR; i++) print h[i]} }
Run Code Online (Sandbox Code Playgroud)

用法: /path/to/script -v pattern='banana' -v before=2

  • `sed` 也可以执行滑动窗口,但生成的脚本通常难以阅读,因此使用 `awk` 会更容易。 (2认同)

小智 19

这很容易使用exvim -e

    vim -e - $file <<@@@
g/banana/.-2,.d
wq
@@@
Run Code Online (Sandbox Code Playgroud)

该表达式为:对于从当前行 -2 到当前行的范围内包含香蕉的每一行,删除。

很酷的是,该范围还可以包含向后和向前搜索,例如,这将删除文件的所有部分,以包含苹果的行开始,以包含橙色的行和包含香蕉的行结束:

    vim -e - $file <<@@@
g/banana/?apple?,/orange/d
wq
@@@
Run Code Online (Sandbox Code Playgroud)

另请注意,使用内联命令选项“-c”最多可以提交十个 vim/ex 命令。请参阅手册页。

vim -e -c 'g/banana/.-2,.d' -c 'wq' $yourfilename
Run Code Online (Sandbox Code Playgroud)

ex -c 'g/banana/?apple?,/orange/d' -c 'wq' $yourfilename 
Run Code Online (Sandbox Code Playgroud)


mik*_*erv 10

你可以很简单地做到这一点sed

printf %s\\n    1 2 3 4match 5match 6 \
                7match 8 9 10 11match |
sed -e'1N;$!N;/\n.*match/!P;D'
Run Code Online (Sandbox Code Playgroud)

我不知道为什么会有人不这么说,但是要找到一行并打印前几行 sed包含了内置的Print 原语,它只写入\n模式空间中的第一个ewline 字符。互补的Delete 原语在用剩余的递归回收脚本之前删除相同的模式空间段。为了圆它,有一个原语用于在N插入的后面将ext 输入行附加到模式空间\newline 字符。

所以一行sed应该就是你所需要的。你只需match用你的正则表达式替换,你就是金子。这应该是一个非常快的解决方案。

还要注意,它会正确地将match紧接在另一个之前的一个match作为触发前两行的安静输出安静它的打印:


printf %s\\n    1 2 3 4match 5match 6 \
                7match 8 9 10 11match |
sed -e'1N;$!N;/\n.*match/!P;D'
Run Code Online (Sandbox Code Playgroud)

为了使其适用于任意数量的行,您需要做的就是获得线索。

所以:

    printf %s\\n     1 2 3 4 5 6 7match     \
                     8match 9match 10match  \
                     11match 12 13 14 15 16 \
                     17 18 19 20match       |
    sed -e:b -e'$!{N;2,5bb' -e\} -e'/\n.*match/!P;D'
Run Code Online (Sandbox Code Playgroud)
1
11match
12
13
14
20match
Run Code Online (Sandbox Code Playgroud)

...删除任何匹配项之前的 5 行。


cho*_*oba 7

在 中使用“滑动窗口” perl

perl -ne 'push @lines, $_;
          splice @lines, 0, 3 if /banana/;
          print shift @lines if @lines > 2
          }{ print @lines;'
Run Code Online (Sandbox Code Playgroud)