从大型文件中获取大量Grep模式


18

我有一个文件,每天增长约200,000行,并且全部由以下三行组成:

1358726575123       # key
    Joseph Muller   # name
    carpenter       # job
9973834728345
    Andres Smith
    student
7836472098652
    Mariah Anthony
    dentist

现在,我还有另一个文件,可以从中提取大约10,000个键模式,例如1358726575123。然后,我for使用这些模式运行循环,并且必须对照第一个文件检查它们。如果文件不包含这种模式,则将模式保存在第三个文件中以进行进一步处理:

for number in $(grep -o '[0-9]\{12\}' file2); do  # finds about 10.000 keys
     if ! grep -q ^$number$ file1; then           # file1 is a huge file
         printf "$number\n" >>file3               # we'll process file3 later
     fi
done

示例代码将一个巨大的文件捕获了10,000次,我整天大约每分钟运行一次此循环。

由于巨大的文件不断增长,我该怎么做才能更快地节省所有CPU?我想知道是否通过密钥对其进行排序(如果可以的话,如何进行排序)还是使用db而不是纯文本排序会有所帮助...


Answers:


11

此答案基于potongawk发布的答案。对于主文件中相同的600万行1万个键, 它的速度是方法(在我的系统上)的两倍...(现在已更新为使用FNR, NR)
comm

尽管awk它比当前系统快,并且将为您和您的计算机提供一些喘息的空间,但是请注意,当数据处理如您所描述的那样密集时,通过切换到专用数据库将获得最佳的总体效果;例如。SQlite,MySQL ...


awk '{ if (/^[^0-9]/) { next }              # Skip lines which do not hold key values
       if (FNR==NR) { main[$0]=1 }          # Process keys from file "mainfile"
       else if (main[$0]==0) { keys[$0]=1 } # Process keys from file "keys"
     } END { for(key in keys) print key }' \
       "mainfile" "keys" >"keys.not-in-main"

# For 6 million lines in "mainfile" and 10 thousand keys in "keys"

# The awk  method
# time:
#   real    0m14.495s
#   user    0m14.457s
#   sys     0m0.044s

# The comm  method
# time:
#   real    0m27.976s
#   user    0m28.046s
#   sys     0m0.104s


这很快,但是我对awk不太了解:文件名应该是什么样?我尝试file1 -> mainfilefile2 -> keys与gawk和mawk一起使用,它输出了错误的密钥。
Teresa e Junior

file1具有键,名称和作业。
Teresa e Junior

“ mainfile”是大文件(包含键,名称和作业)。我只是把它叫做“mainfile”,因为我一直得到混合了哪个文件是哪个(文件1 VS文件2)..‘键’只包含10万人,或包含很多,键..为了您的situaton不重定向点儿。 ..只使用file1 EOF file2 它们是文件的名称。“ EOF”是脚本读取的1行文件,用于指示第一个文件(主数据文件)的末尾和第二个文件的开头(键)。 awk允许您读取一系列文件。在这种情况下,该系列中有3个文件。输出到stdout
Peter.O 2012年

该脚本将打印是存在于任何按键mainfile它也将打印在任何键keys这是文件mainfile......这可能是发生了什么......(我会看远一点到它...
Peter.O 2012年

谢谢@ Peter.O!由于文件是机密文件,因此我尝试创建$RANDOM用于上传的示例文件。
Teresa e Junior

16

当然,问题是您在大文件上运行grep 10,000次。您应该只读取两个文件一次。如果您不想使用脚本语言,则可以通过以下方式进行操作:

  1. 从文件1中提取所有数字并对其进行排序
  2. 从文件2中提取所有数字并对它们进行排序
  3. comm在已排序的列表上运行以获取仅第二个列表中的内容

像这样:

$ grep -o '^[0-9]\{12\}$' file1 | sort -u -o file1.sorted
$ grep -o  '[0-9]\{12\}'  file2 | sort -u -o file2.sorted
$ comm -13 file1.sorted file2.sorted > file3

请参阅man comm

如果您可以每天截断大文件(例如日志文件),则可以保留已排序数字的缓存,而不必每次都将其完整解析。


1
整齐!测试了2秒(在不是特别快的驱动器上),主文件中有200,000条随机行条目(即600,000行)和143,000条随机密钥(这就是我的测试数据最终的输出方式)...经过测试,并且可以正常工作(但您知道: )...我确实不知道{12}.. OP是否使用了12,但是示例键的长度为13 ...
Peter.O,2012年

2
只是一点点注意,您可以通过使用<(grep...sort)文件名所在的位置来执行此操作而无需处理临时文件。
凯文(Kevin)

谢谢,但是对文件进行grepping和排序所需的时间比我之前的循环要长得多(+2分钟)。
Teresa e Junior

@Teresa e Junior。您的主文件有多大?...您已经提到它每天以200,000行的速度增长,但是并没有那么大...为了减少您需要处理的数据量,您可以通过记下当前天的200,000行最后处理的行号(昨天),tail -n +$linenum用于仅输出最新数据。这样,您每天将只处理大约200,000行。我刚刚在主文件中使用了600万行和1万个密钥对其进行了测试…… 时间:真实0m0.016s,用户0m0.008s,sys 0m0.008s
Peter.O 2012年

我真的很疑惑/好奇如何将grep主文件10,000次并找到比该方法更快的方法,该方法只抓一次一次(对于较小的file1一次)...即使您的排序比我花费的时间更长测试中,我只是无法理解这样的想法:多次读取文件不会超过单个文件(按时间排序)
Peter.O 2012年

8

是的,一定要使用数据库。它们正是针对此类任务而制作的。


谢谢!我没有太多的数据库经验。您推荐哪个数据库?我已经安装了MySQL和sqlite3命令。
Teresa e Junior

1
它们两者都很好,sqlite更简单,因为它基本上只是一个文件和一个访问它的SQL API。使用MySQL,您需要设置一个MySQL服务器才能使用它。虽然这也不是很困难,但最好还是先使用sqlite。
米卡·菲舍尔

3

这可能对您有用:

 awk '/^[0-9]/{a[$0]++}END{for(x in a)if(a[x]==1)print x}' file{1,2} >file3

编辑:

修改了脚本以允许两个文件中都有重复项和未知密钥,但仍然会从第一个文件中生成的密钥生成第二个文件中不存在的密钥:

 awk '/^[0-9]/{if(FNR==NR){a[$0]=1;next};if($0 in a){a[$0]=2}}END{for(x in a)if(a[x]==1)print x}' file{1,2} >file3

这将丢失在主文件中多次出现的新密钥(因此,在密钥文件中多次出现的新密钥)似乎要求主文件的数组计数增量不能超过1,或其他等效的解决方法(+1,因为它非常接近标记)
Peter.O 2012年

1
我尝试了gawk和mawk,它输出了错误的密钥...
Teresa e Junior

@ Peter.OI假定主文件具有唯一密钥,并且文件2是主文件的子集。
potong 2012年

@potong第二个效果很好,非常快!谢谢!
Teresa e Junior

@Teresa e Junior您确定它能正常工作吗?..使用您提供测试数据,该数据应该输出5000个键,当我运行它时,它会产生136703个键,就像我最终了解您的要求之前一样... @potong当然!FNR == NR(我以前从未使用过它:)
Peter.O 2012年

2

有了这么多数据,您实际上应该切换到数据库。同时,要取得接近良好性能的任何结果,您要做的一件事就是不要分别搜索file1每个键。运行一次即可一次grep提取所有未排除的键。由于那grep还会返回不包含键的行,因此将其过滤掉。

grep -o '[0-9]\{12\}' file2 |
grep -Fxv -f - file1 |
grep -vx '[0-9]\{12\}' >file3

-Fx表示从字面上搜索整行。-f -表示从标准输入中读取模式列表。)


除非我没有记错,否则这不会解决存储不在大文件中的密钥的问题,它将存储其中的密钥。
凯文(Kevin)2012年

完全是@Kevin,这迫使我使用循环。
Teresa e Junior

@TeresaeJunior:添加-v-Fxv)可以解决这个问题。
暂停,直到另行通知。

@DennisWilliamson这将拿起所有的线路中的大文件不匹配任何密钥文件,包括姓名,职位等
凯文·

@Kevin谢谢,我想念这个问题。我为非关键行添加了过滤器,尽管我现在更喜欢使用comm
吉尔斯(Gillles)“所以别再作恶了”

2

请允许我加强其他人所说的:“将您导入数据库!”

大多数平台免费提供MySQL二进制文件。

为什么不使用SQLite?它基于内存,在启动时加载平面文件,然后在完成后将其关闭。这意味着,如果您的计算机崩溃或SQLite进程消失,那么所有数据也将消失。

您的问题看起来像只是几行SQL,并且将在几毫秒内运行!

安装完MySQL之后(我建议您选择其他方式),我将花40美元购买Anthony Molinaro的O'Reilly的SQL Cookbook,该书有很多问题模式,从简单的SELECT * FROM table查询开始,经过汇总和多次联接。


是的,过几天我将开始将数据迁移到SQL,谢谢!在完成所有工作之前,awk脚本一直对我有很大帮助!
Teresa e Junior

1

我不确定这是否是您要查找的确切输出,但是最简单的方法可能是:

grep -o '[0-9]\{12\}' file2 | sed 's/.*/^&$/' > /tmp/numpatterns.grep
grep -vf /tmp/numpatterns.grep file1 > file3
rm -f /tmp/numpatterns.grep

您还可以使用:

sed -ne '/.*\([0-9]\{12\}.*/^\1$/p' file2 > /tmp/numpatterns.grep
grep -vf /tmp/numpatterns.grep file1 > file3
rm -f /tmp/numpatterns.grep

每一个都创建一个临时模式文件,该文件用于从大文件(file1)中收集数字。


我相信这也会找到大文件中的数字,而不是大文件中的数字。
凯文(Kevin)

正确,我没有看到“!” 在OP中。只需使用 grep -vf代替即可grep -f
Arcege 2012年

2
否@ arcege,grep -vf将不会显示不匹配的键,它将显示所有内容,包括名称和作业。
Teresa e Junior

1

我完全同意您获得数据库(MySQL非常易于使用)。在开始运行之前,我喜欢Angus的comm解决方案,但是有很多人正在尝试grep并弄错了,以至于我认为我将展示(或至少一种)正确的解决方法grep

grep -o '[0-9]\{12\}' keyfile | grep -v -f <(grep -o '^[0-9]\{12\}' bigfile) 

第一个grep获取密钥。第三个grep(中的<(...))接受大文件中使用的所有键,<(...)并将其像文件一样传递给-f第二个grep中的参数。这导致第二个grep将其用作要匹配的行列表。然后,它使用它来匹配管道(第一个grep)中的输入(键列表),并打印从键文件而不是-v大文件中提取的所有键。

当然,您可以使用必须跟踪并记得删除的临时文件来执行此操作:

grep -o '[0-9]\{12\}'  keyfile >allkeys
grep -o '^[0-9]\{12\}' bigfile >usedkeys
grep -v -f usedkeys allkeys

这会打印出所有allkeys未出现在中的行usedkeys


不幸的是,它很,并且40秒后出现内存错误:grep: Memory exhausted
Peter.O 2012年

@ Peter.O但这是正确的。无论如何,这就是为什么我会comm按顺序建议数据库或的原因。
凯文(Kevin)

是的,可以,但是比循环慢得多。
Teresa e Junior

1

密钥文件不变吗?然后,您应该避免一次又一次搜索旧条目。

有了它,tail -f您可以获取正在增长的文件的输出。

tail -f growingfile | grep -f keyfile 

grep -f从文件中读取模式,一行作为模式。


那样很好,但是密钥文件总是不同的。
Teresa e Junior

1

不想发布我的答案,因为我认为不应使用shell脚本处理如此大量的数据,并且已经给出了使用数据库的正确答案。但是从现在起还有其他7种方法...

读取内存中的第一个文件,然后抓取第二个文件中的数字,并检查值是否存储在内存中。grep如果您有足够的内存来加载整个文件,那它应该比s 更快。

declare -a record
while read key
do
    read name
    read job
    record[$key]="$name:$job"
done < file1

for number in $(grep -o '[0-9]\{12\}' file2)
do
    [[ -n ${mylist[$number]} ]] || echo $number >> file3
done

我有足够的内存,但是我发现这甚至更慢。不过谢谢!
Teresa e Junior

1

我同意@ jan-steinman的观点,您应该将数据库用于此类任务。如其他答案所示,有很多方法可以将解决方案与shell脚本结合在一起,但是如果您要使用和维护代码的时间超过任何时间,那么这样做会导致很多痛苦。只是一个为期一天的一次性项目。

假设您在Linux机器上,则很可能默认情况下安装了Python,其中包括自Python v2.5起的sqlite3库。您可以使用以下方法检查Python版本:

% python -V
Python 2.7.2+

我建议使用sqlite3库,因为它是所有平台(包括Web浏览器内部!)都存在的基于文件的简单解决方案,并且不需要安装服务器。本质上是零配置和零维护。

下面是一个简单的python脚本,它将解析您作为示例给出的文件格式,然后执行一个简单的“全选”查询并输出存储在数据库中的所有内容。

#!/usr/bin/env python

import sqlite3
import sys

dbname = '/tmp/simple.db'
filename = '/tmp/input.txt'
with sqlite3.connect(dbname) as conn:
    conn.execute('''create table if not exists people (key integer primary key, name text, job text)''')
    with open(filename) as f:
        for key in f:
            key = key.strip()
            name = f.next().strip()
            job = f.next().strip()
            try:
                conn.execute('''insert into people values (?,?,?)''', (key, name, job))
            except sqlite3.IntegrityError:
                sys.stderr.write('record already exists: %s, %s, %s\n' % (key, name, job))
    cur = conn.cursor()

    # get all people
    cur.execute('''select * from people''')
    for row in cur:
        print row

    # get just two specific people
    person_list = [1358726575123, 9973834728345]
    cur.execute('''select * from people where key in (?,?)''', person_list)
    for row in cur:
        print row

    # a more general way to get however many people are in the list
    person_list = [1358726575123, 9973834728345]
    template = ','.join(['?'] * len(person_list))
    cur.execute('''select * from people where key in (%s)''' % (template), person_list)
    for row in cur:
        print row

是的,这意味着您需要学习一些SQL,但是从长远来看,这是值得的。另外,除了解析日志文件之外,您还可以将数据直接写入sqlite数据库。


感谢您的python脚本!我认为/usr/bin/sqlite3外壳脚本的工作方式相同(packages.debian.org/squeeze/sqlite3),尽管我从未使用过。
Teresa e Junior

是的,您可以使用/usr/bin/sqlite3shell脚本,但是我建议避免使用shell脚本,除了简单的即弃程序之外,而应使用python这样的语言,该语言具有更好的错误处理能力,并且易于维护和扩展。
aculich 2012年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.