【问题标题】:Grep ‘\<’ and ‘\>’ between two files两个文件之间的grep‘\<’和‘\>’
【发布时间】:2014-03-15 10:49:31
【问题描述】:

a.txt 包含单词,b.txt 包含字符串。

我想知道 b.txt 中有多少字符串以 a.txt 中的单词开头或结尾。

我在 GREP-s 用户手册中找到了这个: " 假设我要搜索整个单词,而不是单词的一部分? grep -w '你好' * 仅搜索作为完整单词的“hello”实例;它不匹配“奥赛罗”。如需更多控制,请使用“\”来匹配单词的开头和结尾。例如:

      grep 'hello\>' *

只搜索以‘hello’结尾的词,所以它匹配‘Othello’这个词。”

但我不知道如何修改它来解决我的问题。

示例:a.txt

apple
peach
potato
green
big
pink

b.txt

greenapple
bigapple
rottenapple
pinkpeach
xxlpotatoxxx

输出

ends.txt

3 apple greenapple bigapple rottenapple
1 peach pinkpeach

starts.txt

1 green greenapple
1 big bigapple
1 pink pinkpeach

我在这里收到了一些想法: grep two files (a.txt, b.txt) - how many lines in b.txt starts (or ends) with the words from a.txt - output: 2 files with the results

但由于 a.txt 包含大约 50K 行,而 b.txt 包含超过 100M 行,我认为 grep 是唯一的解决方案。

【问题讨论】:

标签: bash grep


【解决方案1】:

最好的办法是编写一个脚本,循环遍历文件的每一行,其中包含模式和 grep 用于另一个文件中的模式:

以下将获得 startsWith 字符串:

while read -r w; do
  start=($(grep "^${w}" b.txt));
  (( ${#start[@]} != 0 )) && echo "${#start[@]} $w ${start[@]}";
done < a.txt

在您的示例输入上执行它,它会产生:

1 green greenapple
1 big bigapple
1 pink pinkpeach

同样,您可以编写另一个获取 endsWith 字符串的单行代码:

while read -r w; do
  end=($(grep "${w}$" b.txt));
  (( ${#end[@]} != 0 )) && echo "${#end[@]} $w ${end[@]}";
done < a.txt

会产生:

3 apple greenapple bigapple rottenapple
1 peach pinkpeach

编辑:如果你想将输出重定向到单独的文件,你可以在一个循环中完成这两个部分:

> startswith.txt     # Truncate the output files to begin with
> endswith.txt
while read -r w; do
  start=($(grep "^${w}" b.txt));
  (( ${#start[@]} != 0 )) && echo "${#start[@]} $w ${start[@]}" >> startswith.txt;
  end=($(grep "${w}$" b.txt));
  (( ${#end[@]} != 0 )) && echo "${#end[@]} $w ${end[@]}" >> endswith.txt;
done < a.txt

【讨论】:

  • 嗨,工作起来像一个魅力,但仍然很慢 :( 如果不计算点击数会更快吗?(仅:找到这个,这里,这里,等等,新行......)
  • @fobiss 鉴于 (​​1) 输入文件的大小,(2) 约束,即获取计数以及 grep 输出,我怀疑它是否会更快。此外,从某种意义上说,这是相当自定义的,您希望输出在一种情况下以字符串开头,在另一种情况下以字符串结尾。
  • @fobiss 如果您不希望 (1) 计数,(2) 正在搜索的字符串,那么您可以使其加速的唯一方法是。
  • @fobiss,最好使用split 拆分大文件。然后,你会感受到处理的速度。
  • 如何将输出转发到文件而不是回显?我已经减小了文件的大小,现在它的工作方式好多了:))
【解决方案2】:

awk 将是我在这里的第一选择。即使在您的条件下,它也应该表现得很好。

awk '
    NR == FNR {word[$1]; next} 
    {
        for (w in word) {
            if ($1 ~ "^" w) starts[w] = starts[w] $1 " "
            if ($1 ~ w "$") ends[w] = ends[w] $1 " "
        }
    } 
    END {
        for (w in ends) {
            n = split(ends[w], a)
            print n, w, ends[w] > "ends.txt"
        }
        for (w in starts) {
            n = split(starts[w], a)
            print n, w, starts[w] > "starts.txt"
        }
    }
' a.txt b.txt
$ cat ends.txt
3 apple greenapple bigapple rottenapple
1 peach pinkpeach
$ cat starts.txt
1 pink pinkpeach
1 big bigapple
1 green greenapple

【讨论】:

  • +1;也许使用starts[w] = starts[w] (starts[w] ? " " : "") $1 修剪startend 数组元素末尾的额外空间,ends[w] 同上。
  • 你可以这样做,但这显然不是问题。当字段分隔符为“”(默认 FS)时,split 函数也会修剪前导和尾随空格
【解决方案3】:

您可以使用简单的 bash 脚本:

#!/bin/bash

INPUT=a.txt
SEARCH=b.txt
OUTS=starts.txt
OUTE=ends.txt

while read line ; do
    echo -n "$line " >> "$OUTS"
    echo -n "$line " >> "$OUTE"
    grep "$line\>" "$SEARCH" | xargs >> "$OUTE"
    grep "\<$line" "$SEARCH" | xargs >> "$OUTS"
done < "$INPUT"

(这不会打印行前的匹配数)

【讨论】:

  • 你到底是什么意思?
【解决方案4】:

这一行

for a in `cat a.txt` ; do echo $a ; grep -c $a\\\>\\\|\\\<$a b.txt ; done

产生这个输出:

apple
3
peach
1
potato
0
green
1
big
1
pink
1

虽然这不是替代方案产生的漂亮输出,但它很简洁,并且在 a.txt 中每行只执行一次 grep

【讨论】:

  • 感谢您的提示。这也是一个解决方案,但我想知道在哪里找到了什么。 :)
猜你喜欢
  • 2023-04-04
  • 2017-03-24
  • 1970-01-01
  • 1970-01-01
  • 2020-10-29
  • 1970-01-01
  • 2021-10-11
  • 1970-01-01
  • 2014-10-03
相关资源
最近更新 更多