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 -s
opçã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 echo
de antes mv
se estiver pronto para destruir seus arquivos existentes.