У меня есть огромный файл csv (~ 17 ГБ), и мне нужно создать новый файл, содержащий все строки, в которых значение последнего столбца появляется более одного раза, к сожалению, файл плохо отформатирован. Есть значения, содержащие запятые, например, строка 6 в приведенном ниже примере:
entity,entity_type,component_id
[email protected],email,1111
[email protected],email,2222
15158112233,phone,3333
15158990000,phone,2222
hello,[email protected],email,3333
1327168,phone,4444
fds_213445,device,3333
для следующего примера я ожидаю, что этот новый файл:
[email protected],email,2222
15158990000,phone,2222
15158112233,phone,3333
hello,[email protected],email,3333
fds_213445,device,3333
В настоящее время я использую наивное решение:
Но, как я уже сказал, это решение очень наивно и работает очень долго (почти неделю и все еще работает...)
Как я могу создать новый файл, содержащий все строки с component_id, которые появляются в bash более одного раза и эффективным способом?
Спасибо @ RavinderSingh13, я добавил усилия, как вы и предложили.
С awk
и двумя проходами по файлу? Первый подсчитывает вхождения последнего поля, второй печатает только дубликаты.
awk -F, 'FNR == NR { ids[$NF]++; next }
ids[$NF] > 1 || FNR == 1' hugefile.csv hugefile.csv > newfile.csv
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
после завершения чтения 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
ПРИМЕЧАНИЕ. Мое третье решение состояло в том, чтобы передать Input_file 2 раза в awk
, который уже описан Шоном в его ответе :), поэтому удалил его отсюда. Кроме того, они протестированы с показанными образцами, а НЕ с огромным набором данных, к вашему сведению.
Вот способ сделать это без одновременного чтения всего файла в память в 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
[email protected],email,2222
15158112233,phone,3333
15158990000,phone,2222
hello,[email protected],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
[email protected],email,2222
15158112233,phone,3333
15158990000,phone,2222
hello,[email protected],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,[email protected],email,1111
1,2222,1,3,[email protected],email,2222
1,3333,1,4,15158112233,phone,3333
1,2222,2,5,15158990000,phone,2222
1,3333,2,6,hello,[email protected],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,[email protected],email,1111
1,2222,2,5,15158990000,phone,2222
1,2222,1,3,[email protected],email,2222
1,3333,3,8,fds_213445,device,3333
1,3333,2,6,hello,[email protected],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,[email protected],email,2222
1,3333,3,8,fds_213445,device,3333
1,3333,2,6,hello,[email protected],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,[email protected],email,2222
1,3333,1,4,15158112233,phone,3333
1,2222,2,5,15158990000,phone,2222
1,3333,2,6,hello,[email protected],email,3333
1,3333,3,8,fds_213445,device,3333
$ ... | cut -d, -f5-
entity,entity_type,component_id
[email protected],email,2222
15158112233,phone,3333
15158990000,phone,2222
hello,[email protected],email,3333
fds_213445,device,3333
Разве sorts
каждому не требуется все в памяти?
@keithpjolley нет, это то, что я упомянул в тексте - ...only sort has to handle the whole input at once and it's designed to do so by using demand paging, etc....
поэтому ему не нужно хранить все это в памяти сразу.
Я не уверен, что вы подразумеваете под решением «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]
Вывод с данными вашего примера:
[email protected],email,2222
15158990000,phone,2222
15158112233,phone,3333
hello,[email protected],email,3333
fds_213445,device,3333
На SO пользователям настоятельно рекомендуется добавлять свои усилия в свои вопросы, поэтому, пожалуйста, добавляйте свои усилия в свой вопрос, спасибо.