Grep огромное количество шаблонов из огромного файла


18

У меня есть файл, который растет примерно на 200 000 строк в день, и он все состоит из блоков из трех строк как таковых:

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

Теперь у меня есть другой файл, из которого я извлекаю около 10000 шаблонов клавиш, например 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

Пример кода очищает огромный файл 10000 раз, и я запускаю этот цикл примерно раз в минуту в течение всего дня .

Поскольку огромный файл продолжает расти, что я могу сделать, чтобы сделать все это быстрее и сэкономить процессор? Интересно, поможет ли сортировка файла по его ключу (если так, как?) Или использование БД вместо простого текста ...


Ответы:


11

Этот ответ основан на awkответе, опубликованном potong .
Это в два раза быстрее, чем commметод (в моей системе), для тех же 6 миллионов строк в основном файле и 10 тысяч ключей ... (теперь обновлен для использования FNR, NR)

Несмотря на то, что она 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 -> mainfileи file2 -> keysс gawk и mawk, и он выводит неправильные ключи.
Тереза ​​и Джуниор

file1 имеет ключи, имена и задания.
Тереза ​​и Джуниор

'mainfile' - это большой файл (с ключами, именами и заданиями). Я просто назвал его «mainfile», потому что я все время путался, какой файл какой (file1 vs file2) .. «keys» содержит только 10 тысяч или столько же ключей. Для вашей ситуации НЕ перенаправляйте что-либо . .. просто используйте file1 EOF file2 Это имена ваших файлов. "EOF" - это 1-строчный файл, созданный сценарием для указания конца первого файла (файла основных данных) и начала второго файла ( ключи). awkПозволяет читать в серии файлов .. В этом случае в этой серии есть 3 файла. Выходные данные идут вstdout
Peter.O

Этот скрипт будет печатать любые ключи, которые присутствуют mainfile, И он также будет печатать любые ключи из keysфайла, который НЕ находится в mainfile... Это, вероятно, то, что происходит ... (Я посмотрю немного дальше ...
Peter.O

Спасибо, @ Peter.O! Поскольку файлы являются конфиденциальными, я пытаюсь создать образцы файлов $RANDOMдля загрузки.
Тереза ​​и Джуниор

16

Проблема, конечно, в том, что вы запускаете grep для большого файла 10000 раз. Вы должны прочитать оба файла только один раз. Если вы хотите остаться за пределами языков сценариев, вы можете сделать это следующим образом:

  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

2
Просто небольшая заметка, вы можете сделать это, не имея дело с временными файлами, используя, <(grep...sort)где имена файлов.
Кевин

Спасибо, но очистка и сортировка файлов занимает гораздо больше времени, чем мой предыдущий цикл (+ 2 минуты.).
Тереза ​​и Джуниор

@Teresa e Junior. Насколько велик ваш основной файл? ... Вы упомянули, что он растет со скоростью 200 000 строк в день, но не настолько, насколько он велик ... Чтобы уменьшить объем данных, которые вам нужно обрабатывать, вы можете прочитать только 200 000 строк текущего дня, сделав заметку о последний обработанный номер строки (вчера) и использующийся tail -n +$linenumдля вывода только самых последних данных. Таким образом, вы будете обрабатывать только приблизительно 200 000 строк каждый день ... Я только что проверил это с 6 миллионами строк в основном файле и 10 тысячами ключей ... время : реальные 0m0.016s, пользовательские 0m0.008s, sys 0m0.008s
Peter.O

Я действительно весьма озадачен / любопытен тем, как вы можете выполнить поиск основного файла в 10 000 раз и найти его быстрее, чем этот метод, который выполняет поиск только один раз (и один раз для гораздо меньшего файла1 ) ... Даже если ваш сортировка занимает больше времени, чем мой тест, я просто не могу понять, что чтение большого файла, которое много раз не перевешивает ни одного вида (по времени)
Peter.O

8

Да, обязательно используйте базу данных. Они созданы именно для таких задач.


Благодарность! У меня нет большого опыта работы с базами данных. Какую базу данных вы рекомендуете? У меня установлен MySQL и установлена ​​команда sqlite3.
Тереза ​​и Джуниор

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

1
Я попытался с gawk и mawk, и он выводит неправильные ключи ...
Teresa e Junior

@ Peter.OI предположил, что основной файл имеет уникальные ключи, а этот файл 2 является подмножеством основного файла.
потонг

@potong Второй работает хорошо и очень быстро! Спасибо!
Тереза ​​и Джуниор

@Teresa e Junior Вы уверены, что он все еще работает правильно? .. Используя предоставленные вами тестовые данные , которые должны выдать 5000 ключей, при запуске он выдает 136703 ключа, так же, как я получил, пока не понял, каковы ваши требования. ... @potong Конечно! FNR == NR (я никогда не использовал это раньше :)
Peter.O

2

С таким большим количеством данных вы должны действительно переключиться на базу данных. В то же время, чтобы достичь достойной производительности, вам нужно не искать по file1отдельности каждый ключ. Запустите один, grepчтобы извлечь все неисключенные ключи одновременно. Так как это grepтакже возвращает строки, которые не содержат ключа, отфильтруйте их.

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

( -Fxозначает буквально искать целые строки. -f -означает читать список шаблонов из стандартного ввода.)


Если я не ошибаюсь, это не решает проблему хранения ключей, которых нет в большом файле, а будет хранить ключи, которые в нем находятся.
Кевин

@Kevin точно, и это заставило меня использовать цикл.
Тереза ​​и Джуниор

@TeresaeJunior: добавление -v( -Fxv) может позаботиться об этом.
Приостановлено до дальнейшего уведомления.

@DennisWilliamson Это выберет все строки в большом файле, которые не соответствуют ни одной в файле ключа, включая имена, задания и т. Д.
Кевин

@Kevin Спасибо, я неправильно понял вопрос. Я добавил фильтр для неключевых строк, хотя теперь я предпочитаю использоватьcomm .
Жиль "ТАК - перестань быть злым"

2

Позвольте мне подтвердить то, что сказали другие: «Отведите тебя в базу данных!»

Есть MySQL бинарных файлов свободно доступных для большинства платформ.

Почему не SQLite? Это основано на памяти, загружая плоский файл при запуске, а затем закрывая его, когда вы закончите. Это означает, что если ваш компьютер выходит из строя или происходит процесс SQLite, то же самое происходит и со всеми данными.

Ваша проблема выглядит как пара строк SQL и будет выполняться за миллисекунды!

После установки MySQL (который я рекомендую по сравнению с другими вариантами) я бы выложил $ 40 за книгу поваров О'Рейли от Энтони Молинаро, в которой много проблемных образцов, начиная с простых SELECT * FROM tableзапросов и проходя через агрегаты и множественные объединения.


Да, я начну переносить свои данные в SQL через несколько дней, спасибо! Скрипты awk мне очень помогли, пока я все не сделал!
Тереза ​​и младший

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).


Я считаю, что это также находит числа, которые находятся в большом файле, а не те, которые нет.
Кевин

Правильно, я не видел '!' в ОП. Просто нужно использовать grep -vfвместо grep -f.
Arcege

2
Нет @arcege, grep -vf не будет отображать не соответствующие ключи, он будет отображать все, включая имена и задания.
Тереза ​​и Джуниор

1

Я полностью согласен с тем, что вы получаете базу данных (MySQL довольно прост в использовании). До тех пор, пока вы не запустите это, мне нравится 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

@ Peter.O Но ​​это правильно. Во всяком случае, поэтому я бы предложил базу данных или comm, в таком порядке.
Кевин

Да, это работает, но намного медленнее, чем цикл.
Тереза ​​и Джуниор

1

Ключевой файл не меняется? Тогда вам следует избегать поиска старых записей снова и снова.

С помощью tail -fвы можете получить вывод растущего файла.

tail -f growingfile | grep -f keyfile 

grep -f читает шаблоны из файла, одна строка как шаблон.


Это было бы хорошо, но файл ключей всегда отличается.
Тереза ​​и Джуниор

1

Не собирался публиковать свой ответ, потому что я думал, что такой объем данных не должен обрабатываться сценарием оболочки, и правильный ответ для использования базы данных уже был дан. Но с тех пор есть 7 других подходов ...

Читает первый файл в памяти, затем ищет второй файл для чисел и проверяет, хранятся ли значения в памяти. Это должно быть быстрее, чем несколько grepсекунд, если у вас достаточно памяти для загрузки всего файла, то есть.

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

У меня достаточно памяти, но я нашел этот еще медленнее. Спасибо хоть!
Тереза ​​и Джуниор

1

Я согласен с @ jan-steinman, что вы должны использовать базу данных для такого рода задач. Есть много способов собрать воедино решение с помощью сценария оболочки, как показывают другие ответы, но выполнение этого способа приведет к большим страданиям, если вы собираетесь использовать и поддерживать код в течение любого промежутка времени больше, чем просто однодневный одноразовый проект.

Предполагая, что вы используете Linux, у вас, скорее всего, установлен Python по умолчанию, который включает библиотеку sqlite3, начиная с Python v2.5. Вы можете проверить свою версию Python с:

% python -V
Python 2.7.2+

Я рекомендую использовать библиотеку sqlite3, потому что это простое файловое решение, которое существует для всех платформ (в том числе внутри вашего веб-браузера!) И не требует установки сервера. По сути, нулевая конфигурация и нулевое обслуживание.

Ниже приведен простой скрипт на 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 ), хотя я никогда не использовал его.
Тереза ​​и Джуниор

Да, вы можете использовать /usr/bin/sqlite3сценарии оболочки, однако я рекомендую избегать сценариев оболочки, за исключением простых одноразовых программ, и вместо этого использовать язык, такой как python, который лучше обрабатывает ошибки и его легче поддерживать и расширять.
aculich
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.