
У меня в папке много разных 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
, если вы готовы испортить существующие файлы.