如果 2 个或更多连续行包含特定模式,则删除所有匹配行并仅保留第一行。
在下面的示例中,当 2 个或更多连续行包含“逻辑 IO”时,我们需要删除所有匹配的行但保留第一行。
输入文件:
select * from test1 where 1=1
testing logical IO 24
select * from test2 where condition=4
parsing logical IO 45
testing logical IO 500
handling logical IO 49
select * from test5 where 1=1
testing logical IO 24
select * from test5 where condition=78
parsing logical IO 346
testing logical IO 12
输出文件:
select * from test1 where 1=1
testing logical IO 24
select * from test2 where condition=4
parsing logical IO 45
select * from test5 where 1=1
testing logical IO 24
select * from test5 where condition=78
parsing logical IO 346
答案1
使用awk
:
awk '/logical IO/ {if (!seen) {print; seen=1}; next}; {print; seen=0}' file.txt
/logical IO/ {if (!seen) {print; seen=1}; next}
检查该行是否包含logical IO
,如果找到并且变量seen
为 false 即前一行不包含logical IO
,则打印该行,设置seen=1
并转到下一行,否则转到下一行,因为上一行已包含logical IO
对于任何其他行,
{print; seen=0}
打印该行和集合seen=0
例子:
$ cat file.txt
select * from test1 where 1=1
testing logical IO 24
select * from test2 where condition=4
parsing logical IO 45
testing logical IO 500
select * from test5 where 1=1
testing logical IO 24
select * from test5 where condition=78
parsing logical IO 346
parsing logical IO 346
testing logical IO 12
$ awk '/logical IO/ {if (!seen) {print; seen=1}; next}; {print; seen=0}' file.txt
select * from test1 where 1=1
testing logical IO 24
select * from test2 where condition=4
parsing logical IO 45
select * from test5 where 1=1
testing logical IO 24
select * from test5 where condition=78
parsing logical IO 346
答案2
和sed
:
sed '/logical IO/{x;//!{g;p;};d;};//!h' infile
怎么运行的:
sed '/logical IO/{ # if line matches
x # exchange hold space w. pattern space
//!{ # if whatever was in the hold buffer doesn't match
g # overwrite pattern space with hold space content
p # print current pattern space
}
d # delete
}
//!h # if line doesn't match, copy over the hold space
' infile
答案3
在里面TXR我们可以在没有任何变异状态变量的情况下表达这一点。在文件中的任何给定位置,我们可以使用两个分支替代方案执行多行模式匹配:我们要么匹配包含搜索字符串的一个或多个连续行,然后打印第一行,要么匹配一行并打印它。一种可能的方法是这样的:
@(repeat)
@ (cases)
@ (collect :gap 0 :mintimes 1)
@line
@ (require (search-str line "logical IO"))
@ (end)
@ (do (put-line (first line)))
@ (or)
@line
@ (do (put-line line))
@ (end)
@(end)
跑步:
$ txr 第一个日志 IO.txr 数据 从 test1 选择 *,其中 1=1 测试逻辑IO 24 从 test2 中选择 *,其中条件=4 解析逻辑IO 45 从 test5 中选择 *,其中 1=1 测试逻辑IO 24 从 test5 中选择 *,其中条件=78 解析逻辑IO 346
@(repeat)
建立遍历数据的过程,而不收集变量绑定;当看到这个构造时,通常表明迭代中发生了一些副作用。在这种情况下,它是输出。
在内部,@(repeat)
我们有一个@(cases)
构造:由 分隔的情况组成的多路匹配@(or)
。第二个分支,后备情况,只是@line
匹配一行。@(do (put-line (first line)))
接下来的指令打印该行。
该组织的主要分支@(cases)
通过 收集材料@(collect)
。根据 的要求,匹配项必须是连续的,:gap 0
并且根据 的要求,必须至少有一场:mintimes 1
。收集主体匹配绑定到变量的单行line
。然后有一个@(require ...)
断言,除非该行包含子字符串,否则该断言将失败"logical IO"
。因此,当遇到不匹配的行时,集合将停止,因为这:gap 0
会阻止它跳过它。匹配的行被隐式收集到一个名为的列表中line
,该列表会弹出collect
(在收集内绑定的变量自动成为收集外的列表,其中包含在多次迭代中绑定的所有值)。我们只根据需要打印第一个,抑制其余的。
请注意,这两场@line
比赛彼此无关;他们将line
变量绑定在不同的范围内。
另一种方法是在 TXR Lisp 中对惰性列表进行一些函数式编程:
[(opip (partition-by (do cond
((search-str @1 "logical IO") t)
(t @1)))
(mapcar* first)
put-lines)
(get-lines)]
$ txr 第一个日志 IO.tl
运算opip
符是用于构建函数管道的语法糖。它的参数都被视为op
语法:用于生成带有隐式编号参数的匿名函数的宏。
整体形式是 [(opip ...) (get-lines)] ,它的意思是“调用 产生的函数opip
,并将结果(get-lines)
作为参数”。这(get-lines)
会将标准输入流转换为惰性字符串列表。 (它的“相反”是put-lines
,它出现了)。
现在在管道中,我们用来partition-by
(懒惰地!)将行列表转换为列表列表,这是其分区。分区条件是包含的每一行都logical IO
映射到符号t
,而所有其他行仅映射到其自身。这意味着包含的连续行logical IO
显示为一个分区,而所有其他行则显示为长度为 1 的分区。现在我们需要对这些数据做的就是将每个分区映射到它的第一个项目,通过(mapcar* first)
并将其传递给put-lines
转储结果。
我们使用mapcar*
它是因为这是mapcar
.我们希望一切都是惰性的,以便动作实际上是由 触发的put-lines
。当put-lines
遍历输出列表时,它从lazy中取出项目mapcar*
,这会强制由产生的元素partition-by
,这会强制由产生的列表(get-lines)
导致I/O发生以读取这些行。
如果我们mapcar
错误地使用了正则,我们会导致整个输出在转储之前在内存中构建的问题,这对于大文件来说是一个糟糕的兆头。