grep с шаблоном из одного файла (3.2Gb )соответствие в другом файле (4.8Gb)

Идея : Существуют резервные копии.bashrc,.profile и т. д. в /etc/skel/. Таким образом, можно заменить поврежденный.bashrc, просто перехитрив оттуда.

Внимание! : Если вы замените файл .bashrc новым, он удалит все другие (и )изменения, которые вы в него внесли. Например, можно добавить псевдонимы, пользовательскую функцию или PATH в.bashrc. При замене файла все эти изменения будут потеряны. Лучше сохранить копию измененного файла.bashrc перед его заменой. Позже можно аккуратно извлечь из него нужную деталь. Чтобы сохранить резервную копию измененного файла.bashrc в вашем домашнем каталоге с именем my _bashrc, используйте в терминале следующее:

/bin/cp ~/.bashrc ~/my_bashrc

Почему /bin/cp : В случае, если вы перепутали свою переменную $PATH при изменении  ~/.bashrc все исполняемые файлы будут недоступны с вашего терминала, и cp больше не будет работать. Поэтому рекомендуется вызывать cp с его полным путем как /bin/cp, когда вы пытаетесь скопировать что-то с поврежденным ~/.bashrc.

Наконец, используйте следующую команду в своем терминале, чтобы заменить ~/.bashrc новой копией:

/bin/cp /etc/skel/.bashrc ~/

Он заменит ваш поврежденный файл ~/.bashrc новым. После этого вам нужно исходник ~/.bashrc, чтобы изменение произошло сразу, пропишите в терминале,

. ~/.bashrc

или,

source ~/.bashrc

или, если это не сработает, вы можете закрыть терминал и снова открыть его.

7
02.08.2016, 00:47
7 ответов

Что-то вроде этого могло бы сработать, но я не уверен, что это хорошая идея в зависимости от вашего варианта использования (не проверено):

while read f2line
do
  f1=$(grep $line file1)

  [[ ! -z $f1 ]] && echo $f1line 
done < file2

Другое возможное решение, если вам нужен более однострочный метод (быстро протестировано ниже) :

grep . file2 | xargs -i^ grep ^ file1

В результате получилось:

root@7Z233W1 (/tmp)# cat f1
John:myemail@gmail.com:johnson123:22hey
Erik:thatwhatsup@gmail.com:johnson133:22hey
Robert:whatsup@gmail.com:johnson123:21hey
Johnnny:bro@gmail.com:johnson123:22hey

root@7Z233W1 (/tmp)# cat f2
1@gmail.com
rsdoge@gmail.com
mynameiscurt@hotmail.com
myemail@gmail.com

root@7Z233W1 (/tmp)# grep . f2 | xargs -i^ grep ^ f1
John:myemail@gmail.com:johnson123:22hey
1
27.01.2020, 20:14

Важный отказ от ответственности : Я проверил это на данных, представленных в вопросе. Загрузка нескольких гигабайт данных в базу данных SQLite может занять много времени. Запросы с использованием двух текстовых полей могут быть неэффективными. Может влиять производительность диска. И т. Д. И т. Д.

Следующий сценарий sh создаст базу данных SQLlite database.db (этот файл будет удален, если он уже существует), создать таблицы qadr и данные , и загрузить данные в две таблицы ( файл1 в данные и файл2 в кадр ). Затем он создаст индекс для data.adr .

#!/bin/sh

address_file="file2"
data_file="file1"

database="database.db"

rm -f "$database"

sqlite3 "$database" <<END_SQL
CREATE TABLE qadr ( adr TEXT );
CREATE TABLE data ( name TEXT, adr TEXT, tag1 TEXT, tag2 TEXT );
.separator :
.import "$data_file" data
.import "$address_file" qadr
VACUUM;
CREATE UNIQUE INDEX adri ON data(adr);
VACUUM;
END_SQL

Создание индекса предполагает, что адреса в file1 уникальны (то есть, что второе поле : -delimited является уникальным). Если это не так, удалите UNIQUE из оператора CREATE INDEX (в идеале они уникальны, а в идеале - строки в file2 также уникальны).

Я никогда не работал с SQLite и такими объемами данных, но знаю, что многогигабайтный импорт в MongoDB и MySQL может быть очень медленным, а создание индекса также может занять много времени. По сути, я говорю, что просто бросаю это кому-то, у кого есть много данных для тестирования.

Тогда это вопрос одного простого запроса:

$ sqlite3 database.db 'SELECT data.* FROM data JOIN qadr ON (data.adr = qadr.adr)'
John|myemail@gmail.com|johnson123|22hey

или, возможно, даже просто

$ sqlite3 database.db 'SELECT * FROM data NATURAL JOIN qadr'
John|myemail@gmail.com|johnson123|22hey

Кто-то с большим знанием SQLite наверняка даст конструктивный комментарий по этому поводу.

2
27.01.2020, 20:14

Работать с большими файлами довольно сложно, но вы можете сделать это за 3 шага:

  1. Сортировка file1 по второму полю

     sort -k2,2 -t: file1> file1.sorted 
     
  2. Sort file2

     sort file2> file2.sorted 
     
  3. Соединить 2 файла по электронной почте поле

     соединение -t: -2 2 file2.sorted file1.sorted -o 2.1,0,2.3,2.4> matched.txt 
     
8
27.01.2020, 20:14

Если вам нужно избежать решения БД (не знаю почему, мне это кажется лучшей идеей), вы можете сделать это, отсортировав два файла на адреса электронной почты, а затем с помощью команды join , которая приблизительно соответствует действиям БД.

Вот что я сделал:

sort -t: +1 file1 -o file1
sort file2 -o file2
join -t: -o 1.1,1.2,1.3,1.4 -1 2 file1 file2

Похоже, это правильно с вашими данными выборки. Он сортирует файлы по месту . Если вы этого не хотите, измените параметр -o в sort s на имена временных файлов, а затем используйте их в объединении. Кроме того, если у вас действительно есть не 4 поля в первом файле, вы должны учесть это в параметре -o для присоединиться к .

Для получения более подробной информации обратитесь к страницам руководства.

2
27.01.2020, 20:14

Я представляю второй ответ на этот вопрос (это интересная проблема). Это полностью отличается от моего решения SQLite и от довольно многообещающих решений sort + join , которые начинают появляться:

Использование вашего первоначального подхода с grep -f , но буквально немного решает проблему. Давайте разделим «файл запроса», file2 на управляемые фрагменты, используя разбиение .

Утилита split может разбить файл на несколько файлов меньшего размера на основе количества строк.

Файл 3,2 Гб со средней длиной строки из 20 символов содержит около 172 000 000 строк (если я не допустил арифметической ошибки). Возможно разделение на 2000 файлов по 85000 строк в файле.

Итак,

$ mkdir testing
$ cd testing
$ split -l 85000 -a 4 ../file2

Параметр -a 4 указывает split использовать четыре символа после начального x для создания имен файлов для новых файлов. Файлы будут называться xaaaa , xaaab и т. Д.

Затем запустите исходный grep -f на них:

for f in x????; do
  grep -F -f "$f" ../file1
done

Это может make grep может хранить в памяти гораздо меньший набор шаблонов запросов.

ОБНОВЛЕНИЕ : с 145 526 885 строками используйте split -l 72000 -a 4 для создания примерно 2000 файлов.

Не забывайте очищать каталог testing каждый раз, когда вы пытаетесь создать новый набор разделенных файлов.

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

5
27.01.2020, 20:14

Вот версия сценария Кусалананды, который использует perl для преобразования file1 из : разделены на TAB, разделенные ранее загрузив его в sqlite3 .

Встроенный скрипт perl проверяет наличие 5 полей, а не 4. Если есть, он добавляет поле 3 к полю 2 (восстанавливая : , которое было удалено autosplit), затем удаляет поле 3.

#!/bin/sh

address_file="file2"
data_file="file1"

database="database.db"

rm -f "$database"

sqlite3 "$database" <<END_SQL
CREATE TABLE qadr ( adr TEXT );
CREATE TABLE data ( name TEXT, adr TEXT, tag1 TEXT, tag2 TEXT );
.mode line
.import "$address_file" qadr
END_SQL

perl -F: -lane 'if (@F == 5) {
    $F[1] .= ":" . $F[2];  # perl arrays are zero-based
    delete $F[2];
  };
  print join("\t",@F);' $data_file | 
    sqlite3 "$database" -separator $'\t' '.import /dev/stdin data'


sqlite3 "$database" <<END_SQL
VACUUM;
CREATE UNIQUE INDEX adri ON data(adr);
VACUUM;
END_SQL

IMO, sqlite не подходит для такой большой базы данных. Я бы рекомендовал вместо этого использовать mysql или postgresql . Для такого рода задач чистая скорость mysql , вероятно, делает его лучшим выбором - он быстрее для простых вещей, таких как эта, но postgresql намного быстрее для более сложных задач - по моему опыту, pg «умный и быстрый» "(т.е.он может добиться значительных улучшений скорости в сложных задачах, работая умно, а не усердно), mysql «тупой быстрый» (то есть он много работает, не имея особых возможностей для умной работы).

Приведенный выше сценарий можно легко адаптировать для работы с клиентами командной строки psql или mysql вместо sqlite3 , но я бы изменил ] CREATE TABLE команды для использования фиксированного размера CHARACTER (size) вместо TEXT , где size - разумное предположение, какой максимальный размер для каждое поле - например, возможно 255 символов для поля adr и 10-50 символов для остальных.

одна из возможных оптимизаций состоит в том, чтобы тщательно выбирать размеры полей, чтобы каждая запись была четным делителем размера блока вашего диска (с учетом накладных расходов mysql / postgresql на каждую запись). 512 байтов должно хватить для всех распространенных размеров блоков. сделайте поля любого необходимого размера и добавьте дополнительное неиспользуемое поле CHARACTER (size) , чтобы компенсировать разницу. Смысл этого в том, чтобы записи никогда не пересекали границу блока, поэтому движку db нужно читать только один дисковый блок, чтобы получить все данные для данной записи (фактически, он будет читать несколько записей в одном блоке с большинство текущих размеров блоков, но это только помогает производительности, не может повредить ей).

https://dba.stackexchange.com/ , вероятно, лучший сайт для поиска или запроса информации об оптимизации размеров записей.

0
27.01.2020, 20:14

Ответ Костаса, вероятно, лучший для вашей конкретной проблемы, потому что у вас есть поле, которое имеет 100% совпадение.

Но если ваша проблема действительно заключалась в поиске миллионов регексов в миллиардах строк, то в GNU Parallel есть описание того, как это сделать: https://www.gnu.org/software/parallel/man.html#EXAMPLE:-Grepping-n-lines-for-m-regular-expressions

Самое простое решение для перебора большого файла для большого количества регексов:

grep -f regexps.txt bigfile

Или если регексы - фиксированные строки:

grep -F -f regexps.txt bigfile

Есть 3 ограничивающих фактора: CPU, RAM и дисковый ввод/вывод.

Оперативную память легко измерить: если процесс grep занимает большую часть свободной памяти (например, при запуске top), то оперативная память является ограничивающим фактором.

CPU также легко измерить: если grep занимает >90% CPU в top, то CPU является ограничивающим фактором, и распараллеливание ускорит этот процесс.

Сложнее определить, является ли дисковый ввод-вывод ограничивающим фактором, и в зависимости от дисковой системы распараллеливание может быть быстрее или медленнее. Единственный способ узнать наверняка - это тестирование и измерение.

Ограничивающий фактор: Оперативная память

Обычный grep -f regexs.txt bigfile работает независимо от размера bigfile, но если regexps.txt настолько велик, что не помещается в памяти, то вам нужно разделить это.

grep -F занимает около 100 байт оперативной памяти, а grep занимает около 500 байт оперативной памяти на 1 байт regexp. Так что если regexps.txt занимает 1% вашей оперативной памяти, то он может быть слишком большим.

Если вы можете преобразовать ваши regexp в фиксированные строки, сделайте это. Например, если все строки, которые вы ищете в bigfile, выглядят как:

ID1 foo bar baz Identifier1 quux
fubar ID2 foo bar baz Identifier2

то ваш regexps.txt можно преобразовать из:

ID1.*Identifier1
ID2.*Identifier2

в:

ID1 foo bar baz Identifier1
ID2 foo bar baz Identifier2

Таким образом, вы сможете использовать grep -F, который занимает на 80% меньше памяти и работает намного быстрее.

Если он все еще не помещается в памяти, вы можете сделать следующее:

parallel --pipepart -a regexps.txt --block 1M grep -F -f - -n bigfile |
sort -un | perl -pe 's/^\d+://'

1M должен быть вашей свободной памятью, разделенной на количество ядер и деленной на 200 для grep -F и на 1000 для обычного grep. В GNU/Linux можно сделать так:

free=$(awk '/^((Swap)?Cached|MemFree|Buffers):/ { sum += $2 }
          END { print sum }' /proc/meminfo)
percpu=$((free / 200 / $(parallel --number-of-cores)))k

parallel --pipepart -a regexps.txt --block $percpu --compress grep -F -f - -n bigfile |
sort -un | perl -pe 's/^\d+://'

Если вы можете смириться с дублированием строк и неправильным порядком, то быстрее сделать так:

parallel --pipepart -a regexps.txt --block $percpu --compress grep -F -f - bigfile

Ограничивающий фактор: CPU

Если CPU является ограничивающим фактором, следует распараллелить regexps:

cat regexp.txt | parallel --pipe -L1000 --round-robin --compress grep -f - -n bigfile |
sort -un | perl -pe 's/^\d+://'

Команда запустит один grep на каждый CPU и прочитает bigfile один раз на каждый CPU, но поскольку это делается параллельно, все чтения, кроме первого, будут кэшироваться в RAM. В зависимости от размера regexp.txt может быть быстрее использовать --block 10m вместо -L1000.

Некоторые системы хранения работают лучше при параллельном чтении нескольких блоков. Это верно для некоторых RAID-систем и некоторых сетевых файловых систем. Чтобы распараллелить чтение bigfile:

parallel --pipepart --block 100M -a bigfile -k --compress grep -f regexp.txt

Это позволит разделить bigfile на фрагменты по 100 МБ и запустить grep на каждом из этих фрагментов. Чтобы распараллелить чтение bigfile и regexp.txt, объедините эти два параметра, используя --fifo:

parallel --pipepart --block 100M -a bigfile --fifo cat regexp.txt \
\| parallel --pipe -L1000 --round-robin grep -f - {}

Если строка соответствует нескольким regexp, строка может быть продублирована.

Более крупная проблема

Если проблема слишком велика, чтобы быть решенной этим способом, вы, вероятно, готовы к Lucene.

4
27.01.2020, 20:14

Теги

Похожие вопросы