Remova duplicatas csv com base no primeiro valor, mantendo a linha mais longa entre duplicatas

Remova duplicatas csv com base no primeiro valor, mantendo a linha mais longa entre duplicatas

Tenho muitos arquivos CSV diferentes em uma pasta (megadrive.txt, snes.txt) assim:

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;;;;;

Nestes CSVs, tenho muitas, muitas linhas e muitas têm o mesmo primeiro campo. Quero processar esses arquivos em lote e, em cada arquivo, manter apenas a linha mais longa para cada primeiro campo. Por exemplo, a saída deve ser:

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;;;;;

Em particular

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

ambos os registros têm o primeiro campo duplicado, mas a segunda entrada é mais longa, então eu gostaria de manter a segunda entrada e remover todas as linhas mais curtas com o mesmo primeiro campo.

Como posso fazer isso?

Responder1

Presumo que seus campos sejam definidos por ;. E que não pode haver ;dentro de um campo. Se essas suposições forem verdadeiras, você pode fazer:

$ 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;;;;;

No entanto, isso tem a desvantagem de precisar armazenar uma linha por primeiro campo na memória e isso pode ser um problema para arquivos grandes. Nesse caso, você pode tentar isto:

$ 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;;;;;

Você pode aplicar qualquer uma das soluções a todos os seus arquivos com um simples loop de shell:

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

Ou

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

Responder2

Experimente com 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;;;;;

Ambas as classificações usarão ;como delimitador de campo ( -t';'). O primeiro inverterá a classificação ( -r), para que os campos vazios venhamdepoisos campos não vazios, e a segunda classificação classificará pelo primeiro campo ( -k1,1) e removerá linhas extras com o mesmo primeiro campo ( -u= uniq), mas, caso contrário, manterá a ordem definida pela primeira classificação ( -s= estável).

Isso pressupõe que, em vez da linha "mais longa", como diz o título, você realmente deseja a linha "mais completa", ou seja. entre duas linhas com o mesmo primeiro campo, a mais curta tem sempre umsubconjuntodos campos do mais longo (que é o único caso em que descartar as linhas mais curtas pode fazer algum sentido, IMHO). Ele também pressupõe que sua implementação de classificação tenha uma -sopção (estável): tanto a classificação GNU (Linux) quanto a classificação BSD têm.

Se você quiser fazer isso em um lote de arquivos, você deve usar 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 {} +

Ajuste os predicados do find ( -name, etc) e remova apenas o echode antes mvse estiver pronto para destruir seus arquivos existentes.

informação relacionada