Удалить дубликаты CSV на основе первого значения, сохраняя самую длинную строку между дубликатами

Удалить дубликаты CSV на основе первого значения, сохраняя самую длинную строку между дубликатами

У меня в папке много разных CSV-файлов (megadrive.txt, snes.txt), например:

Aerial Assault (USA);Aerial Assault (USA);Sega Master System;;;;;;;;;0;;;;;
Aerial Assault (USA);Aerial Assault (USA);Sega Master System;;1990;Sega;Shooter;;;;;0;;;;;
After Burner (World);After Burner (World);Sega Master System;;;;;;;;;;;;;;
After Burner (World);After Burner (World);Sega Master System;;1988;Sega;Flying;;;;;0;;;;;
Air Rescue (Europe);Air Rescue (Europe);Sega Master System;;1992;Sega;Shooter;;;;;0;;;;;
Aladdin (Europe);Aladdin (Europe);Sega Master System;;1994;Sega;Platform;;;;;0;;;;;

В этих CSV-файлах у меня много-много строк, и многие из них имеют одинаковое первое поле. Я хочу пакетно обработать эти файлы и в каждом файле сохранить только самую длинную строку для каждого первого поля. Например, вывод должен быть таким:

Aerial Assault (USA);Aerial Assault (USA);Sega Master System;;1990;Sega;Shooter;;;;;0;;;;;
After Burner (World);After Burner (World);Sega Master System;;1988;Sega;Flying;;;;;0;;;;;
Air Rescue (Europe);Air Rescue (Europe);Sega Master System;;1992;Sega;Shooter;;;;;0;;;;;
Aladdin (Europe);Aladdin (Europe);Sega Master System;;1994;Sega;Platform;;;;;0;;;;;

В частности

Aerial Assault (USA);Aerial Assault (USA);Sega Master System;;;;;;;;;0;;;;;
Aerial Assault (USA);Aerial Assault (USA);Sega Master System;;1990;Sega;Shooter;;;;;0;;;;;

В обеих записях первое поле дублируется, но вторая запись длиннее, поэтому я хотел бы сохранить вторую запись и удалить все более короткие строки с одинаковым первым полем.

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

решение1

Я предполагаю, что ваши поля определены с помощью ;. И что внутри поля не может быть ;. Если эти предположения верны, вы можете сделать:

$ awk -F';' '{if(!a[$1]||length($0)>length(a[$1])){a[$1]=$0}}END{for(i in a){print a[i]}}' file.txt
Aerial Assault (USA);Aerial Assault (USA);Sega Master System;;1990;Sega;Shooter;;;;;0;;;;;
After Burner (World);After Burner (World);Sega Master System;;1988;Sega;Flying;;;;;0;;;;;
Aladdin (Europe);Aladdin (Europe);Sega Master System;;1994;Sega;Platform;;;;;0;;;;;
Air Rescue (Europe);Air Rescue (Europe);Sega Master System;;1992;Sega;Shooter;;;;;0;;;;;

Однако у этого есть недостаток: необходимо хранить одну строку на 1-е поле в памяти, и это может быть проблемой для больших файлов. Если так, вы можете попробовать это вместо этого:

$ awk '{print length($0)";"$0}' file.txt | sort -t';' -rnk1,1 | awk -F';' '++a[$2]==1' | cut -d';' -f2-
Aerial Assault (USA);Aerial Assault (USA);Sega Master System;;;;;;;;;0;;;;;
After Burner (World);After Burner (World);Sega Master System;;;;;;;;;;;;;;
Air Rescue (Europe);Air Rescue (Europe);Sega Master System;;1992;Sega;Shooter;;;;;0;;;;;
Aladdin (Europe);Aladdin (Europe);Sega Master System;;1994;Sega;Platform;;;;;0;;;;;

Вы можете применить любое из этих решений ко всем своим файлам с помощью простого цикла оболочки:

for f in *txt; do 
    awk -F';' '{if(!a[$1]||length($0)>length(a[$1])){a[$1]=$0}}END{for(i in a){print a[i]}}' "$f" > "$f".fixed
done

Или

for f in *txt; do 
    awk '{print length($0)";"$0}' file.txt | sort -t';' -rnk1,1 | 
        awk -F';' '++a[$2]==1' | cut -d';' -f2- > "$f".fixed
done

решение2

Попробуйте с sort(1):

sort -rt';' filename | sort -t';' -usk1,1

Aerial Assault (USA);Aerial Assault (USA);Sega Master System;;1990;Sega;Shooter;;;;;0;;;;;
After Burner (World);After Burner (World);Sega Master System;;1988;Sega;Flying;;;;;0;;;;;
Air Rescue (Europe);Air Rescue (Europe);Sega Master System;;1992;Sega;Shooter;;;;;0;;;;;
Aladdin (Europe);Aladdin (Europe);Sega Master System;;1994;Sega;Platform;;;;;0;;;;;

Обе сортировки будут использовать ;в качестве разделителя полей ( -t';'). Первая сортировка будет обратной ( -r), так что пустые поля будутпосленепустые поля, а вторая сортировка выполнит сортировку по первому полю ( -k1,1) и удалит лишние строки с тем же первым полем ( -u= uniq), но в остальном сохранит порядок, установленный первой сортировкой ( -s= stable).

Это предполагает, что вместо «самой длинной» строки, как указано в заголовке, вам на самом деле нужна «самая полная», т. е. из двух строк с одинаковым первым полем более короткая всегда имеетподмножествополей более длинной строки (это единственный случай, когда отбрасывание более коротких строк может иметь смысл, IMHO). Также предполагается, что ваша реализация сортировки имеет -s(стабильную) опцию: и сортировка GNU (Linux), и сортировка BSD имеют ее.

Если вы хотите сделать это с пакетом файлов, вам следует использовать find:

find dir -type f -name '*.txt' \
    -exec sh -c 'for f; do sort -rt";" "$f" |
    sort -t";" -usk1,1 > "$f.new" && echo mv "$f.new" "$f"; done' sh {} +

Отрегулируйте предикаты поиска ( -nameи т. д.) и удаляйте только echoпредыдущую строку mv, если вы готовы испортить существующие файлы.

Связанный контент