最快的方法 - 仅当文件尚不存在时才在文件中附加一行

时间:2017-09-25 15:17:21

标签: bash optimization grep

给出了这个问题Appending a line to a file only if it does not already exist

有没有比@drAlberT提供的解决方案更快的方法?

grep -q -F 'string' foo.bar || echo 'string' >> foo.bar

我已经实现了上述解决方案,我必须在500k行文件上进行迭代(即检查一行是否已经在500k行中设置)。而且,我要运行这个过程很多次,可能是1000到5000万次。不用说它有点慢,因为在我的服务器上运行需要25-30ms(总共3-10天的运行时间)。

编辑:流程如下:我有一个500k行的文件,每次运行时,我可能会得到10-30个新行,并检查它们是否已经存在。如果不是我添加它们,那么我重复多次。我的500k行文件的顺序非常重要,因为我正在使用另一个进程。

EDIT2 :500k行文件总是包含唯一的行,我只关心“满行”,没有子串。

非常感谢!

4 个答案:

答案 0 :(得分:3)

将数百万次传递文件转换为包含数百万次操作的脚本将为您节省大量开销。在文件的每次传递中搜索单个标签是非常低效的;你可以搜索尽可能多的标签,只需一次通过文件即可轻松适应内存。

可能还有以下几点。

awk 'NR==FNR { a[$0]++; next }
    $0 in a { delete a[$0] }
    1
    END { for (k in a) print k }' strings bigfile >bigfile.new

如果你不能同时将strings放入内存中,那么将它分成合适的块显然可以让你在你拥有块的过程中完成这个。

另一方面,如果您已经(有效地)将输入组划分为10-30个标签组,那么显然您只能在一次通过中搜索那些10-30。不过,这应该可以为你提供10-30倍的速度提升。

这假定“线”始终是实线。如果标签可以是输入文件中一行的子字符串,反之亦然,则需要进行一些重构。

答案 1 :(得分:3)

很少有人建议改进:

  1. 尝试使用awk代替grep,以便您可以检测字符串并将其写入一个操作中;
  2. 如果您确实使用grep,请不要使用Bash循环将每个可能的匹配项提供给grep,然后将该单词附加到该文件中。相反,将所有潜在的行读入grep作为匹配(使用-f file_name)并打印匹配。然后反转匹配并追加倒置匹配。请参阅此处的最后一个管道;
  3. 一看到字符串(单个字符串)就退出,而不是继续遍历大文件;
  4. 不要用一行或几行来调用脚本数百万次 - 组织胶水脚本(我想在Bash中),以便核心脚本被所有行调用一次或几次; < / LI>
  5. 也许使用多核,因为文件不相互依赖。也许使用GNU Parallel(或者您可以使用支持线程的Python或Ruby或Perl。)
  6. 考虑这个awk要添加一行:

    $ awk -v line=line_to_append 'FNR==NR && line==$0{f=1; exit} 
                                  END{if (!f) print line >> FILENAME}' file
    

    或多行:

    $ awk 'FNR==NR {lines[$0]; next} 
           $0 in lines{delete lines[$0]} 
           END{for (e in lines) print e >> FILENAME}' lines file
    

    使用Unix words文件副本(235,886行)和五行lines文件的一些时间有两个重叠:

    $ echo "frob
    knob
    kabbob
    stew
    big slob" > lines
    $ time awk 'FNR==NR {lines[$0]; next} 
       $0 in lines{delete lines[$0]} 
       END{for (e in lines) print e >> FILENAME}' lines words
    real    0m0.056s
    user    0m0.051s
    sys 0m0.003s
    $ tail words
    zythum
    Zyzomys
    Zyzzogeton
    frob
    kabbob
    big slob
    

    编辑2

    尝试这两者中最好的:

    $ time grep -x -f lines words | 
           awk 'FNR==NR{a[$0]; next} !($0 in a)' - lines >> words
    real    0m0.012s
    user    0m0.010s
    sys     0m0.003s
    

    说明:

    1. grep -x -f lines words找到字数为
    2. 的行
    3. awk 'FNR==NR{a[$0]; next} !($0 in a)' - lines将这些内容反转为不在单词中的行
    4. >> words将这些附加到文件

答案 2 :(得分:1)

如果重复项在文件中无效,只需将它们全部附加并过滤掉重复项:

cat myfile mynewlines | awk '!n[$0]++' > mynewfile

这将允许在几秒钟内追加数百万行。

如果订单另外无关紧要且您的文件超过几千兆字节,则可以使用sort -u代替。

答案 3 :(得分:0)

让脚本在使用原始文件后从stdin读取新行。所有行都存储在一个关联数组中(没有任何压缩,如md5sum)。

附加后缀&#39; x&#39;旨在处理输入,例如&#39; -e&#39 ;;可能存在更好的方式。

#!/bin/bash
declare -A aa
while read line; do aa["x$line"]=1; 
done < file.txt
while read line; do
  if [ x${aa[$line]} == x ]; then
    aa[$line]=1;
    echo "x$line" >> file.txt
  fi
done