从文件中读取索引“n”低于与给定正则表达式匹配的行的所有行

Posted

技术标签:

【中文标题】从文件中读取索引“n”低于与给定正则表达式匹配的行的所有行【英文标题】:Read from file all lines that have an index `n` lower than the lines that match a given regex 【发布时间】:2017-11-13 05:26:10 【问题描述】:

我想从文件file.txt 中读取索引n 低于匹配给定正则表达式regex 的行的所有行。例如文件

hello my friend
foo
_bar_
I love this bar
poof
kouki
splash in the water
bar

如果regex=barn=2,那么我们想读

hello my friend
foo
kouki

我找到了解决这个问题的方法,用的是一个笨重的班轮

sed -n `grep -n bar file.txt | awk -F ":" 'print ($1 - 2)' | tr '\n' 'X'
| sed 's+X+p;+g' | sed 's/.$//'` < file.txt

是否有更好(更快、更易阅读)的解决方案?

(我提出这个问题的目的纯粹是为了教育)

【问题讨论】:

【参考方案1】:

awk:

$ awk '/bar/ && FNR>2 print li[-2]
       li[-2]=li[-1]; li[-1]=$0' file
hello my friend
foo
kouki

在比赛前打印第 nth 行可以更通用(无需将整个文件保存在内存中):

$ awk -v n=3 '/bar/ && FNR>n print li[n]
              for (i=n;i>1;i--) 
                    li[i]=li[i-1]
               li[1]=$0' file
hello my friend
poof

【讨论】:

++ 表示性能良好、可读、可移植和泛化良好的解决方案。 对于我来说,缓慢、脆弱、复杂、不可移植、不可扩展的 sed 和 bash “解决方案”正在获得支持,这绝对让我感到震惊。当然这是正确的做法。【参考方案2】:

sed方法:

sed -n '1N;2N;/bar[^\n]*$/P;N;D' file.txt

输出:

hello my friend
foo
kouki

详情

1N;2N; - 将前 3 行读入模式空间

/bar[^\n]*$/ - 检查最后一行是否匹配 bar。 ([^\n]*$ - 确保它是捕获的 3 行部分的最后一行)

P; - 如果找到上述匹配,则打印模式空间的第一行

N - 将换行符添加到模式空间,然后将下一行输入附加到模式空间

D - 删除模式空间中的文本直到第一个换行符,并使用生成的模式空间重新启动循环(即关于前 3 行 - 第一行 hello my friend 将被打印并从模式空间和新循环将在下一行开始foo)

【讨论】:

我正在寻找这个,但你更快更强大! P;N;D的使用很好的示范! @RomanPerekhrest 哇,看起来非常优雅+1。我不熟悉PNDsed 中的使用(我现在在man 中读到了它们)。您介意解释一下它是如何工作的吗? 虽然我不知道为什么,但这个解决方案不适用于 BSD/macOS sed。然而,我不愿意投入工作去寻找原因,这让我们明白了这一点,@Remi.b:你不熟悉PN 和@987654341 并非偶然@:这些功能鲜为人知,因为它们令人费解。像 dawg 这样的awk 解决方案更容易理解和概括。 这是gnu-sed 只是应该说明。 POSIX sed - 不 @Remi.b sed 是一个出色的工具,用于在单个行上进行简单的替换,但这就是 all 应该使用的,因为对于其他任何事情,awk 解决方案会更清晰,更简单、更健壮、更便携、更高效且更易于扩展。如果您使用 s、g 和 p(带 -n)以外的 sed 结构,那么您使用的结构在 1970 年代中期 awk 被发明时就已经过时了,所以不要浪费时间学习它们,只要学习awk 代替。【参考方案3】:

纯bash

o=0 a=()
while read -r line;do
    a+=("$line")
    [ "$line" ] && [ -z "$line//*bar*" ] && echo $a[o-2]
    ((o++))
  done <file.txt
hello my friend
foo
kouki

或者,因为你说的是​​regex

while read -r line;do
    a+=("$line")
    [[ $line  =~ bar ]] && echo $a[o-2]
    ((o++))
  done <file.txt

但是,对于表演,我更喜欢第一种语法...

作为一个函数

grepIndex ()  
    local o=0 a=() line
    while read -r line; do
        a+=("$line")
        [ "$line" ] && [ -z "$line//*$1*" ] && echo $a[o-$2]
        ((o++))
    done


grepIndex <file.txt bar 2
hello my friend
foo
kouki

可以写成这样

grepIndex() 
    local o=0 a=() line
    while read -r line;do
        a+=("$line")
        [[ $line =~ $1 ]] && echo $a[o-$2]
        ((o++))
    done

也是。

注意:

如果 pure bash 在处理小文件时要快得多,那么对于大文件,bash 就变得矫枉过正!!看看RomanPerekhrest's answer!使用 sed 可以 成为最有效的解决方案之一(在大文件上)!

【讨论】:

我认为 underkill 可能是更好的术语:除了几行之外,纯 bash 解决方案将非常缓慢。如果您只有几行代码,那么启动awk 等外部实用程序所花费的绝对 时间将无关紧要,因为它可能仍然足够快。简而言之:除了非常特殊的场景,不要在 shell 循环中读取行。顺便说一句:在您的 read 命令前面加上 IFS= ,否则前导和尾随空格将被修剪。 阅读why-is-using-a-shell-loop-to-process-text-considered-bad-practice 了解一些但不是全部的不这样做的原因。只需使用 awk,它是发明 shell 的人发明的工具,用于从 shell 调用此类工作。

以上是关于从文件中读取索引“n”低于与给定正则表达式匹配的行的所有行的主要内容,如果未能解决你的问题,请参考以下文章

如何从 Fortran 模式匹配的行开始读取数据?

正则表达式,匹配除 \r \n 之外的所有内容作为普通字符

❤️Linux三剑客与管道符正则表达式的使用❤️

从正则表达式模式返回不匹配的行

在python中实施多线程以读取文件中的行,并检查该行是否与给定的字符串匹配[closed]

三剑客之SED