Ame*_*ina 36 grep sed awk text-processing
考虑一个包含以下条目的文本文件:
aaa
bbb
ccc
ddd
eee
fff
ggg
hhh
iii
Run Code Online (Sandbox Code Playgroud)
给定一个模式(例如fff
),我想对上面的文件进行 grep 以获取输出:
all_lines except (pattern_matching_lines U (B lines_before) U (A lines_after))
Run Code Online (Sandbox Code Playgroud)
例如,如果B = 2
和A = 1
,带有模式 = 的输出fff
应该是:
aaa
bbb
ccc
hhh
iii
Run Code Online (Sandbox Code Playgroud)
如何使用 grep 或其他命令行工具执行此操作?
请注意,当我尝试时:
grep -v 'fff' -A1 -B2 file.txt
Run Code Online (Sandbox Code Playgroud)
我没有得到我想要的。我反而得到:
aaa
bbb
ccc
ddd
eee
fff
--
--
fff
ggg
hhh
iii
Run Code Online (Sandbox Code Playgroud)
don*_*sti 13
您可以使用gnu grep
with-A
和-B
精确打印要排除的文件部分,但添加-n
开关以打印行号,然后格式化输出并将其作为命令脚本传递sed
以删除这些行:
grep -n -A1 -B2 PATTERN infile | \
sed -n 's/^\([0-9]\{1,\}\).*/\1d/p' | \
sed -f - infile
Run Code Online (Sandbox Code Playgroud)
这也适用于grep
通过以下方式传递的模式文件-f
:
grep -n -A1 -B2 -f patterns infile | \
sed -n 's/^\([0-9]\{1,\}\).*/\1d/p' | \
sed -f - infile
Run Code Online (Sandbox Code Playgroud)
我认为这可以稍微优化,如果它将任何三个或更多连续行号折叠到范围内,以便使用 eg2,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-
Run Code Online (Sandbox Code Playgroud)
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-
Run Code Online (Sandbox Code Playgroud)
mik*_*erv 11
在大多数情况下,don's 可能会更好,但以防万一文件真的很大,并且您无法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'
Run Code Online (Sandbox Code Playgroud)
这是所谓的输入滑动窗口的示例。它通过建立一个前瞻缓冲区$B
曾经试图打印任何东西之前-count线。
实际上,也许我应该澄清我之前的观点:此解决方案和不解决方案的主要性能限制因素将与间隔直接相关。这个解决方案会随着更大的间隔大小而变慢,而不要随着更大的间隔频率而变慢。换句话说,即使输入文件非常大,如果实际间隔出现的频率仍然非常低,那么他的解决方案可能是要走的路。但是,如果间隔大小相对易于管理,并且可能经常发生,那么这就是您应该选择的解决方案。
所以这是工作流程:
$match
在模式空间中以\n
ewline 开头,sed
则将递归D
删除\n
它之前的每个ewline。
$match
之前完全清除了的模式空间 - 但为了轻松处理重叠,留下一个里程碑似乎效果更好。s/.*\n.*\($match\)/\1/
一次性完成并避开循环,但是当$A/$B
很大时,D
elete 循环证明要快得多。N
输入的ext 行拉入以\n
ewline 分隔符开头,并再次尝试通过引用我们最近使用的正则表达式 w/再次D
删除 a 。/\n.*$match/
//
$match
那么它只能$match
在行的开头这样做- 所有前$B
行都已被清除。
$A
。s///
为&
自己替换模式空间中的$A
th \n
ewline 字符,如果成功,t
est 会将我们 - 以及我们的整个后$A
缓冲区 - 完全从脚本中分出,以从顶部开始脚本与下一个输入行(如果有)。t
est 不成功,我们将b
返回到:t
op 标签并递归输入另一行 - 如果$match
在收集$A
fter时发生,可能会重新开始循环。$match
函数循环,那么我们将尝试p
打印$
最后一行(如果是),如果!
不是,则尝试s///
为&
自己替换模式空间中的$B
th \n
ewline 字符。
t
对此进行测试,如果成功,我们将分支到:P
rint 标签。:t
op 并在缓冲区中添加另一个输入行。:P
rint,我们将P
rint 然后D
elete 到\n
模式空间中的第一个ewline 并从顶部重新运行脚本并保留剩余的部分。所以这一次,如果我们在做 A=2 B=2 match=5; seq 5 | sed...
:P
rint第一次迭代的模式空间如下所示:
^1\n2\n3$
Run Code Online (Sandbox Code Playgroud)
这就是如何sed
收集它的前$B
缓冲区。因此sed
打印到它收集的输入后面的输出$B
-count 行。这意味着,鉴于我们之前的示例,将rint输出,然后删除它并将其发送回脚本顶部的模式空间,如下所示:sed
P
1
D
^2\n3$
Run Code Online (Sandbox Code Playgroud)
...并在脚本的顶部N
检索 ext 输入行,因此下一次迭代如下所示:
^2\n3\n4$
Run Code Online (Sandbox Code Playgroud)
因此,当我们5
在输入中找到第一次出现时,模式空间实际上是这样的:
^3\n4\n5$
Run Code Online (Sandbox Code Playgroud)
然后D
elete 循环开始,当它通过时,它看起来像:
^5$
Run Code Online (Sandbox Code Playgroud)
当N
ext 输入线被拉动时,会sed
碰到 EOF 并退出。到那时,它只P
打印了第 1 行和第 2 行。
这是一个示例运行:
^1\n2\n3$
Run Code Online (Sandbox Code Playgroud)
那打印:
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
Run Code Online (Sandbox Code Playgroud)
如果您不介意使用vim
:
$ export PAT=fff A=1 B=2
$ vim -Nes "+g/${PAT}/.-${B},.+${A}d" '+w !tee' '+q!' foo
aaa
bbb
ccc
hhh
iii
Run Code Online (Sandbox Code Playgroud)
-Nes
打开不兼容的静音 ex 模式。对脚本很有用。+{command}
告诉 vim{command}
在文件上运行。g/${PAT}/
- 在所有匹配的行上/fff/
。如果模式包含您不打算以这种方式处理的正则表达式特殊字符,这将变得棘手。.-${B}
- 从这一行上方的 1 行开始.+${A}
- 到这一行下方的 2 行(参见:he cmdline-ranges
这两行)d
- 删除行。+w !tee
然后写入标准输出。+q!
退出而不保存更改。您可以跳过变量并直接使用模式和数字。我使用它们只是为了明确目的。
怎么样(使用 GNUgrep
和bash
):
$ grep -vFf - file.txt < <(grep -B2 -A1 'fff' file.txt)
aaa
bbb
ccc
hhh
iii
Run Code Online (Sandbox Code Playgroud)
在这里,我们找到要被 丢弃的行grep -B2 -A1 'fff' file.txt
,然后使用它作为输入文件来查找丢弃这些行的所需行。