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

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

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

sed '/banana/I,+2 d' file

到目前为止,一切都很好!但我需要它删除 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: `-'

答案1

Sed 不会回溯:一旦处理完一行,就完成了。因此,“查找一行并打印前 N 行”不会按原样工作,这与“查找一行并打印接下来的 N 行”不同,后者很容易移植。

如果文件不太长,那么您似乎可以使用 GNU 扩展,因此可以使用tac反转文件的行。

tac | sed '/banana/I,+2 d' | tac

另一个攻击角度是在 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]} }

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

答案2

这很容易前任或者vim-e

    vim -e - $file <<@@@
g/banana/.-2,.d
wq
@@@

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

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

    vim -e - $file <<@@@
g/banana/?apple?,/orange/d
wq
@@@

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

vim -e -c 'g/banana/.-2,.d' -c 'wq' $yourfilename

ex -c 'g/banana/?apple?,/orange/d' -c 'wq' $yourfilename 

答案3

您可以使用以下命令相当简单地完成此操作sed

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

我不知道为什么有人会这么说,但查找一行并打印前几行 sed包含内置的Print 原语,该原语仅写入\n模式空间中的第一个 ewline 字符。互补的Delete 原语会删除相同的模式空间片段,然后递归地回收剩余的脚本。最后,有一个原语用于将Next 输入行附加到插入的 ewline 字符后面的模式空间中\n

所以这一行sed应该就是你所需要的。你只需更换match无论你的正则表达式是什么,你都是黄金。那应该是一个非常快速地解决方案也是如此。

另请注意,它会正确计算match紧接在另一个之前match作为前两行安静输出的触发器也使其打印安静:


1
7match
8
11match

为了让它工作随意的行数,您所需要做的就是获得领先。

所以:

    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'

1
11match
12
13
14
20match

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

答案4

使用man 1 ed

str='
1
2
3
banana
4
5
6
banana
8
9
10
'

# using Bash
cat <<-'EOF' | ed -s <(echo "$str")  | sed -e '1{/^$/d;}' -e '2{/^$/d;}'
H
0i


.
,g/banana/km\
'm-2,'md
,p
q
EOF

相关内容