Распечатать все дубликаты в плохо отформатированном csv

У меня есть огромный файл 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

В настоящее время я использую наивное решение:

  1. Подсчитайте размер каждого компонента и сохраните в файле А.
  2. Удалите из файла A все компоненты с размером = 1.
  3. Выполнение скрипта по всем идентификаторам компонентов в файле A распечатывает строки совпадения из исходного файла в новый файл результатов.

Но, как я уже сказал, это решение очень наивно и работает очень долго (почти неделю и все еще работает...)

Как я могу создать новый файл, содержащий все строки с component_id, которые появляются в bash более одного раза и эффективным способом?

На SO пользователям настоятельно рекомендуется добавлять свои усилия в свои вопросы, поэтому, пожалуйста, добавляйте свои усилия в свой вопрос, спасибо.

RavinderSingh13 13.12.2020 14:39

Спасибо @ RavinderSingh13, я добавил усилия, как вы и предложили.

Shmulik Franko 13.12.2020 15:03
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
2
109
4
Перейти к ответу Данный вопрос помечен как решенный

Ответы 4

Ответ принят как подходящий

С 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 13.12.2020 16:33

@keithpjolley нет, это то, что я упомянул в тексте - ...only sort has to handle the whole input at once and it's designed to do so by using demand paging, etc.... поэтому ему не нужно хранить все это в памяти сразу.

Ed Morton 13.12.2020 16:38

Я не уверен, что вы подразумеваете под решением «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

Другие вопросы по теме