考虑一个包含以下条目的文本文件:
aaa
bbb
ccc
ddd
eee
fff
ggg
hhh
iii
给定一个模式(例如fff
),我想 grep 上面的文件以获取输出:
all_lines except (pattern_matching_lines U (B lines_before) U (A lines_after))
例如,如果B = 2
和A = 1
,带有模式 = 的输出fff
应该是:
aaa
bbb
ccc
hhh
iii
如何使用 grep 或其他命令行工具执行此操作?
请注意,当我尝试时:
grep -v 'fff' -A1 -B2 file.txt
我没有得到我想要的。我反而得到:
aaa
bbb
ccc
ddd
eee
fff
--
--
fff
ggg
hhh
iii
答案1
您可以使用gnu grep
和-A
来-B
准确打印要排除的文件部分,但添加开关-n
以打印行号,然后格式化输出并将其作为命令脚本传递以sed
删除这些行:
grep -n -A1 -B2 PATTERN infile | \
sed -n 's/^\([0-9]\{1,\}\).*/\1d/p' | \
sed -f - infile
grep
这也应该适用于传递给via的模式文件,-f
例如:
grep -n -A1 -B2 -f patterns infile | \
sed -n 's/^\([0-9]\{1,\}\).*/\1d/p' | \
sed -f - infile
我认为,如果它将任何三个或更多连续行号折叠到范围中,以便使用 例如2,6d
而不是2d;3d;4d;5d;6d
...,则可以稍微优化,但如果输入只有几个匹配项,则不值得这样做。
其他不保留行顺序并且很可能更慢的方法:
使用comm
:
comm -13 <(grep PATTERN -A1 -B2 <(nl -ba -nrz -s: infile) | sort) \
<(nl -ba -nrz -s: infile | sort) | cut -d: -f2-
comm
需要排序的输入,这意味着行顺序不会保留在最终输出中(除非您的文件已经排序),因此nl
用于在排序之前对行进行编号,comm -13
仅打印唯一的行第二个文件然后cut
删除添加的部分(nl
即第一个字段和分隔符:
)
:join
join -t: -j1 -v1 <(nl -ba -nrz -s: infile | sort) \
<(grep PATTERN -A1 -B2 <(nl -ba -nrz -s: infile) | sort) | cut -d: -f2-
答案2
在大多数情况下,不这样做可能会更好,但以防万一文件是真的大,你无法sed
处理那么大的脚本文件(这可能发生在大约 5000 多行脚本时),这里是普通的sed
:
sed -ne:t -e"/\n.*$match/D" \
-e'$!N;//D;/'"$match/{" \
-e"s/\n/&/$A;t" \
-e'$q;bt' -e\} \
-e's/\n/&/'"$B;tP" \
-e'$!bt' -e:P -e'P;D'
这是所谓的一个例子滑动窗口在输入上。它的工作原理是建立一个展望$B
在尝试打印任何内容之前,先缓冲-count 行。
实际上,也许我应该澄清我之前的观点:此解决方案和其他解决方案的主要性能限制因素将与间隔直接相关。此解决方案会因间隔较大而变慢尺寸,而 don's 会随着时间间隔的增大而减慢频率。换句话说,即使输入文件非常大,如果实际的间隔出现仍然非常罕见,那么他的解决方案可能是正确的选择。但是,如果间隔大小相对易于管理,并且可能经常发生,那么这就是您应该选择的解决方案。
所以这是工作流程:
- 如果
$match
在模式空间中发现前面有\n
ewline,sed
则将递归D
删除\n
它前面的每个 ewline。- 我
$match
之前完全清除了 的图案空间 - 但为了轻松处理重叠,留下地标似乎效果更好。 - 我还尝试
s/.*\n.*\($match\)/\1/
尝试一次性获得它并避开循环,但是当$A/$B
很大时,D
elete 循环被证明要快得多。
- 我
- 然后我们拉入前面
N
有\n
ewline 分隔符的 ext 输入行,并再次尝试通过引用我们最近使用的正则表达式 w/ 来D
删除 a 。/\n.*$match/
//
- 如果模式空间匹配,
$match
那么它只能在行$match
首执行此操作 - 所有$B
前面的行都已被清除。- 所以我们开始循环
$A
。 - 每次运行此循环时,我们都会尝试用模式空间中的行字符
s///
来替换&
自身,如果成功,est 会将我们 - 以及我们的整个后缓冲区 - 完全从脚本中分支出来,以从顶部重新开始脚本与下一个输入行(如果有)。$A
\n
t
$A
- 如果
t
est 不成功,我们将b
返回到op 标签并递归另一行输入 - 如果在收集 fter 时发生,:t
则可能会重新开始循环。$match
$A
- 所以我们开始循环
- 如果我们通过了一个
$match
函数循环,那么我们将尝试p
打印$
最后一行(如果是最后一行),如果!
不是,则尝试用模式空间中的行字符 替换s///
它&
自己。$B
\n
- 我们也会
t
对此进行测试,如果成功,我们将转向:P
rint 标签。 - 如果没有,我们将分支回
:t
op 并将另一个输入行附加到缓冲区。
- 我们也会
- 如果我们让它进行
:P
rint,我们将P
rint 然后D
删除\n
模式空间中的第一个 ewline,并使用剩余的内容从顶部重新运行脚本。
所以这一次,如果我们这样做A=2 B=2 match=5; seq 5 | sed...
rint第一次迭代的模式空间:P
如下所示:
^1\n2\n3$
这就是sed
收集其$B
前缓冲区的方式。所以sed
打印到输出$B
-count 行在后面它收集的输入。这意味着,根据我们之前的示例,sed
将P
print1
输出,然后D
删除它并将模式空间发送回脚本顶部,如下所示:
^2\n3$
...并且在脚本的顶部N
检索 ext 输入行,因此下一次迭代如下所示:
^2\n3\n4$
因此,当我们5
在输入中找到第一次出现时,模式空间实际上如下所示:
^3\n4\n5$
然后D
elete 循环开始,当它完成时,它看起来像:
^5$
当N
拉动 ext 输入线时sed
,会遇到 EOF 并退出。到那时它只P
打印了第 1 行和第 2 行。
这是一个运行示例:
A=8 B=7 match='[24689]0'
seq 100 |
sed -ne:t -e"/\n.*$match/D" \
-e'$!N;//D;/'"$match/{" \
-e"s/\n/&/$A;t" \
-e'$q;bt' -e\} \
-e's/\n/&/'"$B;tP" \
-e'$!bt' -e:P -e'P;D'
打印:
1
2
3
4
5
6
7
8
9
10
11
12
29
30
31
32
49
50
51
52
69
70
71
72
99
100
答案3
如果您不介意使用vim
:
$ export PAT=fff A=1 B=2
$ vim -Nes "+g/${PAT}/.-${B},.+${A}d" '+w !tee' '+q!' foo
aaa
bbb
ccc
hhh
iii
-Nes
打开不兼容的静音 ex 模式。对于编写脚本很有用。+{command}
{command}
告诉 vim在该文件上运行。g/${PAT}/
- 在所有匹配的线路上/fff/
。如果模式包含您不打算以这种方式处理的正则表达式特殊字符,这会变得很棘手。.-${B}
- 从这一行上方的 1 行开始.+${A}
- 到该行下面的 2 行(参见:he cmdline-ranges
对于这两个)d
- 删除行。+w !tee
然后写入标准输出。+q!
退出而不保存更改。
您可以跳过变量并直接使用模式和数字。我使用它们只是为了明确目的。
答案4
您可以通过使用临时文件获得足够好的结果:
my_file=file.txt #or =$1 if in a script
#create a file with all the lines to discard, numbered
grep -n -B1 -A5 TBD "$my_file" |cut -d\ -f1|tr -d ':-'|sort > /tmp/___"$my_file"_unpair
#number all the lines
nl -nln "$my_file"|cut -d\ -f1|tr -d ':-'|sort > /tmp/___"$my_file"_all
#join the two, creating a file with the numbers of all the lines to keep
#i.e. of those _not_ found in the "unpair" file
join -v2 /tmp/___"$my_file"_unpair /tmp/___"$my_file"_all|sort -n > /tmp/___"$my_file"_lines_to_keep
#eventually use these line numbers to extract lines from the original file
nl -nln $my_file|join - /tmp/___"$my_file"_lines_to_keep |cut -d\ -f2- > "$my_file"_clean
结果是够好了因为您可能会在此过程中丢失一些缩进,但如果它是 xml 或缩进不敏感的文件,那么它应该不是问题。由于此脚本使用 RAM 驱动器,因此写入和读取这些临时文件与在内存中工作一样快。