sed,xargs和stdbuf-如何仅从文件中获取模式的前n个匹配项

时间:2018-06-28 15:34:37

标签: sed xargs

我有一个带有模式的文件(1行= 1个模式),我想在一个大的文本文件上查找-在infile的每一行中只能找到一个(或没有)模式。找到匹配项后,我想立即在匹配项之前检索字符。第一部分是获取sed的模式

cat patterns.txt | xargs -I '{}' sed -n 's/{}.*$//p' bigtext.txt

那行得通-缺点是我可能要参加数十万场比赛。我不需要/不需要所有比赛-公平表示1K点击就足够了。这就是我苦苦挣扎的地方:我已经读过,为了限制sed的点击次数,我应该使用stdbuf(在我的情况下为gstdbuf),然后将整个内容通过头部传递。但是我不确定stdbuf命令的位置:

cat patterns.txt | xargs -I '{}' gstdbuf -oL -eL sed -n 's/{}.*$//p' bigtext.txt | head -n100

当我尝试此操作时,该过程花费的时间就好像是在整个文件上运行sed,然后获得该输出的head一样,而我希望在100或1000个匹配项后停止搜索。关于实现此目标的最佳方法的任何想法?

2 个答案:

答案 0 :(得分:1)

您提供的Oneliner确实是您想要的吗? Esp。因为您提到了一个公平的样本。因为就目前而言,它会将patterns.txt馈入xargs ...,它将继续为每个模式逐个调用sedxargs的整个输出被馈送到头部,在n行之后将其斩波。换句话说,您的第一个模式已经用尽了您想查看的所有行,即使其他模式在匹配显示给您之前发生的行上可能已经匹配了许多次。水平尺之间的详细示例。


如果我有patterns.txt个:

_Pat1
_Pat2
_Pat3

bigtext.txt一起使用:

1matchx_Pat1x
2matchx_Pat2x 
2matchy_Pat2y 
2matchz_Pat2z 
3matchx_Pat3x 
3matchy_Pat3y 
3matchz_Pat3z
1matchy_Pat1y 
1matchz_Pat1z 

我将您的oneliner限制为五个匹配,但我没有得到结果(文件中找到的所有三个模式的前五个匹配项):

1matchx
2matchx
2matchy
2matchz
3matchx

但是(_Pat1的所有(3个)补丁加上_Pat2的2个匹配项,之后我就用光了输出行):

1matchx
1matchy
1matchz
2matchx
2matchy

由于您的性能问题部分与之相关。我不得不承认我无法复制它。我已从注释中以您的示例为例,通过重复模式并运行oneliner,将“大”文件吹大到1GB大小:

$ time { cat patterns.txt | xargs -I '{}' stdbuf -oL sed -n 's/{}.*$//p' bigtext.txt | head -5 ; }
1aaa
2aaabbb
3aaaccc
1aaa
2aaabbb
xargs: stdbuf: terminated by signal 13

real    0m0.012s
user    0m0.013s
sys     0m0.008s

请注意,我删除了-eL,stderr通常是无缓冲的(这是您通常想要的),并且实际上在这里没有任何作用。还要注意,我在运行stdbuf时没有使用“ g”前缀,这告诉我您可能是在GNU工具不是默认系统的系统上……这可能是您得到不同行为的原因。我将尝试解释正在发生的事情,并尝试一些猜测...并给出建议。还要注意,我真的完全不需要使用stdbuf(操纵缓冲),或者说它对结果没有明显的影响,但是同样,这可能是特定于平台和工具(以及场景)的。 / p>

当您从一行的末尾读取该行时,head会读取从xargs(扩展为sed(或stdbuf包装)插入的标准输入)运行xargs分叉,它们都连接到其写端),直到达到要打印的行数限制,然后head终止。这样做会“破坏”管道,并且xargssed(或包裹在其中的stdbuf)会收到SIGPIPE信号,并且默认情况下它们也会终止(您可以参见我的运行结果:xargs: stdbuf: terminated by signal 13

stdbuf -oL的功能以及有人可能建议的理由。当不再使用控制台进行读/写时(通常是行缓冲的),而使用管道时,通常会看到缓冲的I / O。 stdbuf -oL将其更改为行缓冲。没有它,涉及的过程将以更大的块进行通信,并且可能需要更长的时间来实现head,它已经完成并且不需要进一步的输入,而sed一直在运行,以查看是否还有其他合适的匹配项。如前所述,在我的系统(4K缓冲区)以及该示例(重复模式)上,这没有什么实际的区别。还要注意,虽然行缓冲降低了不知道我们无法完成的风险,但确实增加了进程之间通信所涉及的开销。

那么,为什么这些机制不会为您带来相同的预期结果?我想到了几个选择:

  • 由于每个模式分叉并运行sed一次,因此每次生成整个文件。可能会发生一系列多次运行而没有任何点击的情况。我猜可能是这种情况。
  • 由于您提供了sed的文件供您读取,因此您可能拥有sed的另一种实现,该实现尝试在对文件内容采取行动之前先读取更多内容(一次读取4K) 。可能原因不大,但是从理论上讲,您也可以逐行喂sed来迫使较小的块并尽早获取SIGPIPE

现在假设实际上不需要按模式匹配的顺序模式,那么以上所有内容的摘要将是:首先将模式处理为一个模式,然后对“ big”文件执行一次遍历(可选地,将输出限制为课程)。可能有必要从外壳程序大部分切换到使用起来更舒适的东西,或者至少不保留可能变得令人困惑的oneliner格式。


不符合我自己的建议,awk这样的脚本会打印出前5个点击并退出:

awk -v patts="$(cat patterns.txt)" -v last=5 'BEGIN{patts="(" patts ; gsub(/\n/, "|", patts) ; sub(/.$/, ")", patts); cnt=1 ;} $0~patts{sub(patts ".*", ""); print; cnt++;} cnt>last{exit;}' bigtext.txt

答案 1 :(得分:1)

您可以使用-f file指定具有与grep命令匹配的模式的文件。您还可以指定退出-m count

之前要查找的匹配项数

因此,此命令将使您获得匹配的前5行:

grep -f patterns.txt -m 5  bigtext.txt

现在将匹配修剪到该行的末尾,要困难一些。 假设您使用bash,我们可以从文件中构建一个正则表达式,如下所示:

  while IFS='' read -r line || [[ -n "$line" ]]; do
    subRegex="s/$line.*//;"${subRegex}
  done < patterns.txt

然后在sed命令中使用它。结果代码变为:

  while IFS='' read -r line || [[ -n "$line" ]]; do
    subRegex="s/$line.*//;"${subRegex}
  done < patterns.txt
  grep -f patterns.txt -m 5  bigtext.txt | sed "$subRegex"

sed命令仅在已经与grep匹配的行上运行,因此它应该相当有效。

现在,如果您多次调用它,可以将其放在函数中

function findMatches() {
  local matchCount=${1:-5}  # default to 5 matches
  local subRegex

  while IFS='' read -r line || [[ -n "$line" ]]; do
    subRegex="s/$line.*//;"${subRegex}
  done < patterns.txt

  grep -f patterns.txt -m ${matchCount}  bigtext.txt | sed "${subRegex}"
}

然后您可以这样称呼

findMatches 5
findMatches 100

更新

根据您提供的示例文件,此解决方案确实产生了预期的结果1aaa 2aaabbb 3aaaccc 4aaa 5aaa

但是,假设您对每个模式的长度为120个字符,并且bigfile的每一行为250个字符(文件大小为10 GB)进行注释。

您没有提到可能有多少种模式。因此,我进行了测试,似乎内联完成的sed命令在50个模式之前已经崩溃了。

(当然,如果您的样本确实是数据的外观,则可以基于非AGCT而不是基于patterns文件对每一行进行修整。这样会更快)

但是基于原始问题。您可以基于patterns.txt在单独的文件中生成sed脚本。像这样:

  sed -e "s/^/s\//g;s/$/.*\$\/\/g/g;" patterns.txt > temp.sed

然后在sed命令上使用此临时文件。

 grep -f patterns.txt -m 5 bigtext.txt | sed -f temp.sed

grep在找到X个匹配项后停止,并且sed修剪了这些…新功能在几秒钟内在我的计算机上运行。 为了进行测试,我创建了一个2GB文件,包含250个字符的AGCT组合。另一个文件有50多个模式,每个字符120个字符,其中一些模式取自bigtext文件的随机行。

function findMatches() {
  sed -e "s/^/s\//g;s/$/.*\$\/\/g/g;" patterns.txt > temp.sed
  grep -f patterns.txt -m ${1:-5}   bigtext.txt | sed -f temp.sed
}