сравнить файлы по строкам и создать новое одно bash-программирование

У меня есть два текстовых файла. Файл 2 имеет журналы более 1 000 000. Файл 1 имеет IP-адреса по очереди. Я хочу прочитать файл 2 строки и искать эти строки в файле 1, я имею в виду:

файл 1:

34.123.21.32 45.231.43.21 21.34.67.98 

файл 2:

 34.123.21.32 0.326 - [30/Oct/2013:06:00:06 +0200] 45.231.43.21 6.334 - [30/Oct/2013:06:00:06 +0200] 45.231.43.21 3.673 - [30/Oct/2013:06:00:06 +0200] 34.123.21.32 4.754 - [30/Oct/2013:06:00:06 +0200] 21.34.67.98 1.765 - [30/Oct/2013:06:00:06 +0200] ... 

Я хочу найти IP из файла 1 по строкам в файле 2 и аргументы времени печати (пример: 0.326) в новый файл.

Как я могу это сделать?

Присоединиться + сортировать

Если вы пытаетесь найти IP-адреса, присутствующие в обоих, вы можете использовать команду join но вам нужно будет использовать sort для предварительной сортировки файлов до их присоединения.

 $ join -o 2.2 <(sort file1) <(sort file2) 

пример

 $ join -o 2.2 <(sort file1) <(sort file2) 1.765 0.326 4.754 3.673 6.334 

Другой пример

файл 1a:

 $ cat file1a 34.123.21.32 45.231.43.21 21.34.67.98 1.2.3.4 5.6.7.8 9.10.11.12 

файл 2a:

 $ cat file2a 34.123.21.32 0.326 - [30/Oct/2013:06:00:06 +0200] 45.231.43.21 6.334 - [30/Oct/2013:06:00:06 +0200] 45.231.43.21 3.673 - [30/Oct/2013:06:00:06 +0200] 34.123.21.32 4.754 - [30/Oct/2013:06:00:06 +0200] 21.34.67.98 1.765 - [30/Oct/2013:06:00:06 +0200] 1.2.3.4 1.234 - [30/Oct/2013:06:00:06 +0200] 4.3.2.1 4.321 - [30/Oct/2013:06:00:06 +0200] 

Выполнение команды join :

 $ join -o 2.2 <(sort file1) <(sort file2) 1.234 1.765 0.326 4.754 3.673 6.334 

ПРИМЕЧАНИЕ . Первоначальный порядок file2 теряется с помощью этого метода из-за того, что мы сначала отсортировали его. Однако этот метод требует сканирования file2 только один раз, в результате.

Grep

Вы можете использовать grep для поиска совпадений в file2 используя строки, которые находятся в file1 , но этот метод не так эффективен, как первый метод, который я вам показал. Это file2 для поиска каждой строки в file1 .

 $ grep -f file1 file2 | awk '{print $2}' 

пример

 $ grep -f file1 file2 | awk '{print $2}' 0.326 6.334 3.673 4.754 1.765 1.234 

Улучшение производительности Grep

Вы можете ускорить работу grep , используя эту форму:

 $ LC_ALL=C grep -f file1 file2 | awk '{print $2}' 

Вы также можете сказать grep что укусы в file1 являются фиксированной длиной ( -F ), что также поможет повысить производительность.

 $ LC_ALL=C grep -Ff file1 file2 | awk '{print $2}' 

Как правило, в программном обеспечении вы пытаетесь избежать этого подхода, поскольку это, в основном, цикл в виде цикла. Но бывают моменты, когда это лучшее, что может быть достигнуто с помощью компьютера + программного обеспечения.

Рекомендации

  • Инструменты Linux для обработки файлов как наборов и выполнения заданных операций над ними

Вы можете сообщить grep чтобы получить его шаблоны из файла, используя ключ -f (который находится в стандарте POSIX ):

 sort file1 | uniq \ # Avoid duplicate entries in file1 | grep -f /dev/stdin file2 \ # Search in file2 for patterns piped on stdin | awk '{print $2}' \ # Print the second field (time) for matches > new_file # Redirect output to a new file 

Обратите внимание, что если один IP-адрес появляется несколько раз в file2 , все его записи времени будут напечатаны.

Это сделало работу менее чем за 2 секунды на 5-миллионном файле в моей системе.

Поскольку вы назвали свой вопрос bash programming, я отправлю пример полубаха.

Чистый баш:

Вы можете прочитать файл ip filter -file, а затем проверить строку за строкой и сопоставить ее с ними. Но на этом томе очень медленно.

Вы могли бы легко реализовать bubble -, select -, insertion -, merge sort и т. Д., Но, опять же, для такого объема было бы goner и, скорее всего, хуже, чем сравнение по строке. (Зависит от объема файла фильтра ).

sort + bash:

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


Во-первых, это вопрос о версии bash. По версии 4 (?) У нас есть mapfile который считывает файл в массив. Это намного быстрее, чем традиционное read -ra … В сочетании с sort он может быть написан с помощью чего-то вроде (для этой задачи):

 mapfile arr <<< "$(sort -bk1,1 "$file_in")" 

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

 declare -i match_index function in_array_bs() { local needle="$1" local -i max=$arr_len local -i min=0 local -i mid while ((min < max)); do (( (mid = ((min + max) >> 1)) < max )) || break if [[ "${arr[mid]// *}" < "$needle" ]]; then ((min = mid + 1)) else max=$mid fi done if [[ "$min" == "$max" && "${arr[min]// *}" == "$needle" ]]; then match_index=$min return 0 fi return 1 } 

Тогда вы сказали бы:

 for x in "${filter[@]}"; do if in_array_bs "$x"; then … # check match_index+0,+1,+2 etc. to cover duplicates. 

Пример сценария. (Не отлаживается), а просто как стартер. Для более низкого объема, где нужно было бы только зависеть от sort , это может быть шаблон. Но опять медленнее :

 #!/bin/bash file_in="file_data" file_srch="file_filter" declare -a arr # The entire data file as array. declare -i arr_len # The length of "arr". declare -i index # Matching index, if any. # Time print helper function for debug. function prnt_ts() { date +"%H:%M:%S.%N"; } # Binary search. function in_array_bs() { local needle="$1" local -i max=$arr_len local -i min=0 local -i mid while ((min < max)); do (( (mid = ((min + max) >> 1)) < max )) || break if [[ "${arr[mid]// *}" < "$needle" ]]; then ((min = mid + 1)) else max=$mid fi done if [[ "$min" == "$max" && "${arr[min]// *}" == "$needle" ]]; then index=$min return 0 fi return 1 } # Search. # "index" is set to matching index in "arr" by `in_array_bs()`. re='^[^ ]+ +([^ ]+)' function search() { if in_array_bs "$1"; then while [[ "${arr[index]// *}" == "$1" ]]; do [[ "${arr[index]}" =~ $re ]] printf "%s\n" "${BASH_REMATCH[1]}" ((++index)) done fi } sep="--------------------------------------------" # Timestamp start ts1=$(date +%s.%N) # Print debug information printf "%s\n%s MAP: %s\n%s\n" \ "$sep" "$(prnt_ts)" "$file_in" "$sep" >&2 # Read sorted file to array. mapfile arr <<< "$(sort -bk1,1 "$file_in")" # Print debug information. printf "%s\n%s MAP DONE\n%s\n" \ "$sep" "$(prnt_ts)" "$sep" >&2 # Define length of array. arr_len=${#arr[@]} # Print time start search printf "%s\n%s SEARCH BY INPUT: %s\n%s\n" \ "$sep" "$(prnt_ts)" "$file_srch" "$sep" >&2 # Read filter file. re_neg_srch='^[ '$'\t'$'\n'']*$' debug=0 while IFS=$'\n'$'\t'-" " read -r ip time trash; do if ! [[ "$ip" =~ $re_neg_srch ]]; then ((debug)) && printf "%s\n%s SEARCH: %s\n%s\n" \ "$sep" "$(prnt_ts)" "$ip" "$sep" >&2 # Do the search search "$ip" fi done < "$file_srch" # Print time end search printf "%s\n%s SEARCH DONE\n%s\n" \ "$sep" "$(prnt_ts)" "$sep" >&2 # Print total time ts2=$(date +%s.%N) echo $ts1 $ts2 | awk '{printf "TIME: %f\n", $2 - $1}' >&2