#csv #awk
#csv #awk
Вопрос:
У меня есть огромный файл csv (~ 17 ГБ), и мне нужно создать новый файл, содержащий все строки, в которых значение последнего столбца появляется более одного раза, к сожалению, файл отформатирован неправильно. Есть значения, которые содержат запятые, например, строка 6 в приведенном ниже примере:
entity,entity_type,component_id
bla@gmail.com,email,1111
lalal@hotmail.com,email,2222
15158112233,phone,3333
15158990000,phone,2222
hello,world@gmail.com,email,3333
1327168,phone,4444
fds_213445,device,3333
для следующего примера я бы ожидал этот новый файл:
lalal@hotmail.com,email,2222
15158990000,phone,2222
15158112233,phone,3333
hello,world@gmail.com,email,3333
fds_213445,device,3333
В настоящее время я использую наивное решение:
- Подсчитайте размер каждого компонента и сохраните в файле A.
- Удалите из файла A все компоненты с размером = 1.
- Запуск в скрипте по всем идентификаторам компонентов в файле A распечатайте строки соответствия из исходного файла в новый файл результатов.
Но, как я уже сказал, это решение очень наивно и работает очень долго (почти неделю и все еще работает …)
Как я мог бы создать новый файл, содержащий все строки с component_id, которые появляются более одного раза в bash, и эффективным способом?
Комментарии:
1. Пользователям настоятельно рекомендуется добавлять свои усилия в свои вопросы, поэтому, пожалуйста, добавьте свои усилия в свой вопрос, спасибо.
2. Спасибо @RavinderSingh13, я добавил усилия, как вы и предлагали
Ответ №1:
С awk
и двумя проходами через файл? Первый подсчитывает вхождения последнего поля, второй печатает только дубликаты.
awk -F, 'FNR == NR { ids[$NF] ; next }
ids[$NF] > 1 || FNR == 1' hugefile.csv hugefile.csv > newfile.csv
Ответ №2:
1-е решение: использовать однократное чтение Input_file и играть с массивами, чтобы проверить, является ли значение последнего поля больше 1 во всем Input_file.
awk '
BEGIN{
FS=","
}
{
arr[$NF]
if(!temparr[$NF] ){
first[$NF]=$0
}
}
arr[$NF]>1{
if(first[$NF]){
print first[$NF]
delete first[$NF]
}
print
}
' Input_file
2-е решение: чтение всего Input_file и получение всех строк и значений последних полей в массивы и игра с ними в END
блоке awk
once Input_file завершается чтением.
awk '
BEGIN{
FS=","
}
{
arr[$NF]
if(!arr1[$NF] ){
arr2[ count]=$NF
}
val[$NF]=(val[$NF]?val[$NF] ORS:"")$0
}
END{
for(i=1;i<=count;i ){
if(arr[arr2[i]]>1){
print val[arr2[i]]
}
}
}' Input_file
ПРИМЕЧАНИЕ: Мое 3-е решение состояло в том, чтобы передать Input_file 2 раза, awk
который уже описан Шоном в его ответе 🙂 поэтому удалил его отсюда. Также они тестируются с показанными образцами, а НЕ с огромным набором данных, к вашему сведению.
Ответ №3:
Вот способ сделать это, не считывая весь файл в память сразу в awk, при этом имея возможность обрабатывать входные данные, поступающие из канала или файла (так что это будет работать, даже если ввод был выводом какой-либо другой команды, без создания временного файла, содержащего все входные данные) исохранение исходного порядка ввода и заголовка.
Ввод из файла:
$ awk 'BEGIN{FS=OFS=","} {print (NR>1), $NF, cnt[$NF], NR, $0}' file |
sort -t, -k1,1n -k2,2 -k3,3nr |
awk -F, '$2!=p2{p2=$2; p3=$3} (NR==1) || (p3>1)' |
sort -t, -k4,4n |
cut -d, -f5-
entity,entity_type,component_id
lalal@hotmail.com,email,2222
15158112233,phone,3333
15158990000,phone,2222
hello,world@gmail.com,email,3333
fds_213445,device,3333
или ввод из канала:
$ cat file |
awk 'BEGIN{FS=OFS=","} {print (NR>1), $NF, cnt[$NF], NR, $0}' |
sort -t, -k1,1n -k2,2 -k3,3nr |
awk -F, '$2!=p2{p2=$2; p3=$3} (NR==1) || (p3>1)' |
sort -t, -k4,4n |
cut -d, -f5-
entity,entity_type,component_id
lalal@hotmail.com,email,2222
15158112233,phone,3333
15158990000,phone,2222
hello,world@gmail.com,email,3333
fds_213445,device,3333
Обратите внимание, что этот подход позволяет вам получать входные данные из канала, а не только из файла, поэтому вы можете передавать в него выходные данные другой команды, если хотите. В приведенном выше примере требуется обрабатывать только sort
весь ввод сразу, и он предназначен для этого с помощью подкачки по запросу и т. Д. Поэтому Крайне маловероятно, что возникнут какие-либо проблемы с обработкой большого ввода.
Вот что делает скрипт по шагам, чтобы вы могли видеть, как это работает:
$ awk 'BEGIN{FS=OFS=","} {print (NR>1), $NF, cnt[$NF], NR, $0}' file
0,component_id,1,1,entity,entity_type,component_id
1,1111,1,2,bla@gmail.com,email,1111
1,2222,1,3,lalal@hotmail.com,email,2222
1,3333,1,4,15158112233,phone,3333
1,2222,2,5,15158990000,phone,2222
1,3333,2,6,hello,world@gmail.com,email,3333
1,4444,1,7,1327168,phone,4444
1,3333,3,8,fds_213445,device,3333
$ ... | sort -t, -k1,1n -k2,2 -k3,3nr
0,component_id,1,1,entity,entity_type,component_id
1,1111,1,2,bla@gmail.com,email,1111
1,2222,2,5,15158990000,phone,2222
1,2222,1,3,lalal@hotmail.com,email,2222
1,3333,3,8,fds_213445,device,3333
1,3333,2,6,hello,world@gmail.com,email,3333
1,3333,1,4,15158112233,phone,3333
1,4444,1,7,1327168,phone,4444
$ ... | awk -F, '$2!=p2{p2=$2; p3=$3} (NR==1) || (p3>1)'
0,component_id,1,1,entity,entity_type,component_id
1,2222,2,5,15158990000,phone,2222
1,2222,1,3,lalal@hotmail.com,email,2222
1,3333,3,8,fds_213445,device,3333
1,3333,2,6,hello,world@gmail.com,email,3333
1,3333,1,4,15158112233,phone,3333
$ ... | sort -t, -k4,4n
0,component_id,1,1,entity,entity_type,component_id
1,2222,1,3,lalal@hotmail.com,email,2222
1,3333,1,4,15158112233,phone,3333
1,2222,2,5,15158990000,phone,2222
1,3333,2,6,hello,world@gmail.com,email,3333
1,3333,3,8,fds_213445,device,3333
$ ... | cut -d, -f5-
entity,entity_type,component_id
lalal@hotmail.com,email,2222
15158112233,phone,3333
15158990000,phone,2222
hello,world@gmail.com,email,3333
fds_213445,device,3333
Комментарии:
1. Разве
sorts
для каждого не требуется все в памяти?2. @keithpjolley нет, это то, что я упомянул в тексте,
...only sort has to handle the whole input at once and it's designed to do so by using demand paging, etc....
поэтому не обязательно сохранять все это в памяти сразу.
Ответ №4:
Я не уверен, что вы подразумеваете под решением «bash». bash — неподходящий инструмент для такого рода задач — не говоря уже о том, что кто-то не придумает элегантное решение для всех bash… В любом случае, поскольку awk
ответы уже появились, я решил попробовать решение на python. При этом весь файл загружается в память, но большинство компьютеров в 2020 году должны нормально обрабатывать файл объемом 17 ГБ. Это делает один проход при чтении файла.
python3 -c "import csv,collections;d=collections.defaultdict(list);[d[r[-1]].append(r) for r in csv.reader(open('hugefile.csv'))];[print(','.join(r)) for (k,v) in d.items() for r in v if len(v)>1]"
Разбито:
import csv,collections
#Create a defaultdict that accepts lists:
d=collections.defaultdict(list)
# For each row in the csv file append the row to the dict with
# the last field (id) as the key:
[d[r[-1]].append(r) for r in csv.reader(open('hugefile.csv'))]
# Print each value in the dict if value if the value has more than one row in it.
[print(','.join(r)) for (k,v) in d.items() for r in v if len(v)>1]
Вывод с вашими примерами данных:
lalal@hotmail.com,email,2222
15158990000,phone,2222
15158112233,phone,3333
hello,world@gmail.com,email,3333
fds_213445,device,3333