#bash #awk #grep #cut
#bash #awk #grep #вырезать
Вопрос:
У меня есть файл журнала (около 50 тыс. строк) в формате:
email1@gmail.com:address0:some_details0
email2@gmail.com:address1:some_details1
email1@yahoo.com:address2:some_details2
email2@yahoo.com:address3:some_details3
Я пытаюсь прочитать этот файл и разделить его на две папки (gmail.com и yahoo.com ), а затем запишите каждую строку в уникальный файл, названный в честь идентификатора электронной почты. Мой приведенный ниже код работает, но он очень медленный. Может кто-нибудь, пожалуйста, помочь мне сделать это быстрее и эффективнее? Было бы оценено.
#/bin/sh
grep -hv -P "[^[:ascii:]]" * |
awk -F":" '
{
if ($1 ~ /^[[:alnum:]_. -] @[[:alnum:]_.-] .[[:alnum:]]/ amp;amp; NF>1 amp;amp; $NF!="")
{
split($1, arr, "@")
system("mkdir -p "tolower(arr[2]))
print $0 >> tolower(arr[2])"/"tolower(arr[1])
}
}'
PS: регулярное выражение — это базовая проверка правильности адреса электронной почты. Я не выполняю чрезмерно тяжелую проверку. Сначала я подумал, что регулярное выражение замедляет мой код, но на самом деле это не так. Даже без регулярного выражения код работает очень медленно. Я думаю, что ввод-вывод замедляет это. Как мы можем улучшить?
Ответ №1:
В основном это порождает новую подоболочку для вызова mkdir один раз на строку ввода, из-за чего ваш код выполняется так медленно. Вместо этого сделайте что-то вроде этого:
filename = tolower(arr[1])
dirname = tolower(arr[2])
if ( !seen[dirname] ) {
system("mkdir -p 47" dirname "47")
}
print > (dirname "/" filename)
таким образом, вы создаете подоболочку для вызова mkdir только один раз для каждого каталога.
Обратите внимание, что если вы не используете GNU awk, вы столкнетесь с ошибкой «слишком много открытых файлов», когда создадите около дюжины выходных файлов, и даже с GNU awk это будет замедляться по мере открытия большего количества выходных файлов, что также может повлиять на производительность вашего кода. Общее решение для этого — сначала отсортировать входной файл по адресу электронной почты, а затем закрывать текущий выходной файл каждый раз, когда меняется адрес электронной почты (новое имя выходного файла).
Учитывая это, вот как я бы действительно написал вашу программу:
#!/usr/bin/env bash
grep -hv -P '[^[:ascii:]]' "${@:--}" |
sort -t':' -k1,1 -s |
awk -F':' '
!($1 ~ /^[[:alnum:]_. -] @[[:alnum:]_.-] .[[:alnum:]]/ amp;amp; NF>1 amp;amp; $NF!="") { next }
{ curr = tolower($1) }
curr != prev {
close(out)
split(curr, arr, /@/)
filename = arr[1]
dirname = arr[2]
if ( !seen[dirname] ) {
system("mkdir -p 47" dirname "47")
}
out = dirname "/" filename
prev = $1
}
{ print > out }
'
Я использовал GNU sort выше -s
для «стабильной сортировки», если у вас этого нет и вы заботитесь о том, чтобы относительный порядок строк ввода для данного адреса электронной почты сохранялся в выходных данных, есть другие способы справиться с этим, например awk -v OFS=':' '{print NR, $0}' | sort -t':' -k2,2 -k1,1n | cut -d':' -f2-
.
Комментарии:
1. Привет @Ed. еще раз спасибо за помощь. Я получаю сообщение об ошибке: grep: недопустимое имя символьного класса
2. Ах, так вот почему
-P
у вас был вызов grep. Я просто скопировал код, который у вас был для этого, но избавился от него,-P
поскольку не видел в нем смысла, и теперь добавил его обратно.
Ответ №2:
Вот так:
awk -F'[@:]' '{system("mkdir -p 47"$2"47");f=$2"/"$1;print>>f;close(f)}' file
-F'[@:]'
устанавливает разделитель поля ввода в значение или @
или :
, что позволяет использовать следующие записи:
email1 gmail.com address0 some_details0
email2 gmail.com address1 some_details1
email1 yahoo.com address2 some_details2
email2 yahoo.com address3 some_details3
Выходное имя файла — это просто второе поле ‘/’ первое поле, когда записи разделяются таким образом. print >> $2"/"$1
добавит текущую запись в этот файл. Если он не существует, awk создаст его.
close(f)
после использования, чтобы убедиться, что у нас не закончатся файловые дескрипторы, когда входной файл содержит (слишком) много разных доменов и, следовательно, выходных файлов.