Зібрати величезну кількість візерунків з величезного файлу


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

Приклад коду охоплює величезний файл у 10000 разів, і я запускаю цей цикл приблизно раз на хвилину, протягом цілого дня .

Оскільки величезний файл постійно зростає, що я можу зробити, щоб зробити все це швидше і зберегти деякий процесор? Цікаво, чи допоможе сортування файлу якось за його ключем (якщо так, то як?) Чи використання db замість простого тексту допоможе ...


Відповіді:


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


Це швидко, але я не дуже добре розумію: як мають виглядати назви файлів? Я спробував file1 -> mainfileі file2 -> keysз gawk та mawk, і це видає неправильні клавіші.
Teresa e Junior

file1 має ключі, імена та завдання.
Teresa e Junior

'mainfile' - великий файл (з ключами, іменами та завданнями). Я просто назвав його «mainfile» , тому що я продовжував отримувати переплутали , який файл був , який (file1 file2 проти) .. "ключі" містить тільки 10 тисяч або скільки, ключі .. Для вашого situaton робити НЕ перенаправляти anyting. .. просто використовуйте file1 EOF file2 Вони називають ваші файли .. "EOF" - це однорядковий крейд файлу за сценарієм, щоб вказати кінець першого файлу (основний файл даних) та початок другого файлу ( ключі). awkдозволяють читати в серії файлів .. У цьому випадку в цій серії є 3 файли. Вихід іде наstdout
Peter.O

Цей скрипт буде друкувати будь-які ключі , які присутні в mainfile, і він буде також друкувати будь-які ключі з keysфайлу які НЕ в mainfile... Це, напевно , те , що відбувається ... (я буду дивитися трохи далі в нього ...
Пітер.О

Дякую, @ Peter.O! Оскільки файли конфіденційні, я намагаюся створити зразкові файли $RANDOMдля завантаження.
Teresa e Junior

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}.. ОР використовував 12, але приклади клавіш тривалі 13 ...
Пітер.О

2
Лише невелика примітка, ви можете це зробити, не маючи справи з тимчасовими файлами, використовуючи, <(grep...sort)де є назви файлів.
Кевін

Дякую, але збирання та сортування файлів займає набагато більше часу, ніж мій попередній цикл (+ 2 хв.).
Teresa e Junior

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

Мені справді дуже спантеличено / цікаво, як ви можете зібрати основний файл у 10000 разів і знайти його швидше, ніж цей метод, який лише один раз (і один раз для набагато меншого файлу1 ) ... Навіть якщо ваш сорт займає більше часу, ніж мій тест, я просто не можу
обернутись

8

Так, обов'язково використовуйте базу даних. Вони зроблені саме для таких завдань.


Спасибі! Я не маю великого досвіду роботи з базами даних. Яку базу даних ви рекомендуєте? У мене встановлено MySQL та команду sqlite3.
Teresa e Junior

1
Вони обидва чудово для цього, sqlite простіший, оскільки це доступ до файлу та API SQL. За допомогою 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

@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 точно, і це змусило мене використовувати цикл.
Teresa e Junior

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

@DennisWilliamson Це дозволить вибрати всі рядки у великому файлі, які не відповідають жодному з ключових файлів, включаючи імена, завдання тощо.
Кевін,

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

2

Дозвольте мені підкріпити те, що інші сказали: "Забери тебе до бази даних!"

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

Чому б не SQLite? Це на основі пам'яті, завантажуючи плоский файл під час його запуску, а потім закриваючи його, коли закінчите. Це означає, що якщо ваш комп'ютер виходить з ладу або процес SQLite проходить, то це робить і всі дані.

Ваша проблема схожа лише на пару рядків SQL і працюватиме за мілісекунди!

Після установки MySQL (який я рекомендую перед іншими варіантами) я б виділив $ 40 за SQL Cookbook O'Reilly від Ентоні Молінаро, в якому є безліч проблемних моделей, починаючи з простих 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).


Я вважаю, що це теж знаходить номери, що знаходяться у великому файлі, а не ті, що їх немає.
Кевін

Правильно, я не бачив "!" в ОП. Просто потрібно використовувати grep -vfзамість grep -f.
Арседж

2
Ні @arcege, grep -vf не відображатиме клавіші, які не відповідають, він відображатиме все, включаючи імена та завдання.
Teresa e Junior

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) і друкує будь-які ключі, вилучені з файлу ключів, а не ( -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, у такому порядку.
Кевін

Так, це працює, але набагато повільніше, ніж цикл.
Teresa e Junior

1

Файл ключів не змінюється? Тоді вам слід уникати пошуку старих записів знову і знову.

З tail -fвами можна отримати вихідний файл, що зростає.

tail -f growingfile | grep -f keyfile 

grep -f читає шаблони з файлу, один рядок як візерунок.


Це було б добре, але ключ файлів завжди різний.
Teresa e Junior

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

У мене достатньо пам’яті, але я знайшов цю ще повільніше. Дякую, хоча!
Teresa e Junior

1

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

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

% python -V
Python 2.7.2+

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

Нижче наводиться простий скрипт python, який буде аналізувати формат файлу, який ви подали в якості прикладу, а потім виконує простий запит "виділити всі" і виводить усе, що зберігається в db.

#!/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працює аналогічно для оболонок скриптів (пакети.debian.org/squeeze/ sqlite3 ), хоча я ніколи не використовував його.
Teresa e Junior

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