首页 > 解决方案 > sed、xargs 和 stdbuf - 如何从文件中仅获取模式的前 n 个匹配项

问题描述

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

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

这行得通 - 缺点是我可能会有数十万场比赛。我不想要/不需要所有的比赛 - 1K 点击的公平表示就足够了。这就是我挣扎的地方:我已经读过,为了限制 sed 的点击次数,我应该使用stdbufgstdbuf在我的情况下)并将整个事情通过头部管道。但我不确定在哪里放置stdbuf命令:

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

当我尝试这个时,这个过程需要的时间就像它在整个文件上运行 sed 然后得到head那个输出一样,而我希望在 100 或 1000 个匹配后停止搜索。关于实现这一目标的最佳方法的任何想法?

标签: sedxargs

解决方案


您可以指定一个具有与 grep 命令匹配的模式的文件-f file。您还可以指定退出前要查找的匹配项数-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 个字符的评论,并且大文件的每一行为 250 个字符,文件大小为 10 GB。

你没有提到你可能有多少种模式。所以我进行了测试,似乎内联完成的 sed 命令在 50 个模式之前的某个地方分崩离析。

(当然,如果您的样本确实是数据的样子,那么您可以根据非 AGCT 而不是基于模式文件对每一行进行修剪。这会更快)

但基于原始问题。您可以根据 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 修剪那些...新功能在几秒钟内在我的机器上运行。为了测试,我创建了一个包含 250 个字符的 AGCT 组合的 2GB 文件。另一个包含 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
}

推荐阅读