Comment manipuler un fichier CSV avec sed ou awk?

23

Comment puis-je effectuer les opérations suivantes sur un fichier CSV à l'aide de sedou awk?

  • Supprimer une colonne
  • Dupliquer une colonne
  • Déplacer une colonne

J'ai une grande table avec plus de 200 lignes, et je ne connais pas très bien sed.

Binoy Babu
la source
1
Cross publié sur AskUbuntu
enzotib
@enzotib pouvez-vous publier le lien?
n0pe
@MaxMackie askubuntu.com/questions/88142/… . Je ne peux pas mettre la main sur un mod là à cette heure, alors je l'ai signalé en leur demandant de migrer s'ils le souhaitent; il a déjà une réponse acceptée, donc je ne sais pas s'ils le feront
Michael Mrozek
@MichaelMrozek, hmmm que se passe-t-il habituellement dans ces situations? Gardons-nous simplement les doublons?
n0pe
1
Sauf si vous devez exécuter sur un système qui ne dispose que d'outils de base, voir Existe
Gilles 'SO- arrête d'être méchant'

Réponses:

7

Outre la façon de couper et de réorganiser les champs (traités dans les autres réponses), il y a le problème des champs CSV originaux.

Si vos données entrent dans cette catégorie "décalée", un peu de pré et post filtrage peut s'en occuper. Les filtres ci - dessous ont besoin les caractères \x01, \x02, \x03, \x04apparaissent nulle part dans vos données.

Voici les filtres enroulés autour d'un simple awkvidage de champ.

Remarque: le champ-cinq a une disposition de "champ entre guillemets" invalide / incomplète, mais elle est bénigne à la fin d'une ligne (en fonction de l'analyseur CSV). Mais, bien entendu, cela entraînerait des résultats inattendus problématiques s'il était échangé de sa position de fin de ligne actuelle .

Mise à jour; user121196 a signalé un bogue lorsqu'une virgule précède une citation finale . Voici le correctif.

Les données

cat <<'EOF' >file
field one,"fie,ld,two",field"three","field,\",four","field,five
"15111 N. Hayden Rd., Ste 160,",""
EOF

Le code

sed -r 's/^/,/; s/\\"/\x01/g; s/,"([^"]*)"/,\x02\1\x03/g; s/,"/,\x02/; :MC; s/\x02([^\x03]*),([^\x03]*)/\x02\1\x04\2/g; tMC; s/^,// ' file |
  awk -F, '{ for(i=1; i<=NF; i++) printf "%s\n", $i; print NL}' |
    sed -r 's/\x01/\\"/g; s/(\x02|\x03)/"/g; s/\x04/,/g' 

Le résultat:

field one
"fie,ld,two"
field"three"
"field,\",four"
"field,five

"15111 N. Hayden Rd., Ste 160,"
""

Voici le pré filtre , développé avec des commentaires.
Le post-filtre n'est qu'un renversement de \x01. \x02, \x03,\x04

sed -r '
    s/^/,/                # add a leading comma delimiter
    s/\\"/\x01/g          # obfuscate escaped quotation-mark (\")
    s/,"([^"]*)"/,\x02\1\x03/g    # obfuscate quotation-marks
    s/,"/,\x02/           # when no trailing quote on last field  
    :MC                   # obfuscate commas embedded in quotes
    s/\x02([^\x03]*),([^\x03]*)/\x02\1\x04\2/g
    tMC
    s/^,//                # remove spurious leading delimiter
'
Peter.O
la source
comment supprimeriez-vous la nième colonne basée sur ce filtre?
user121196
@ user121196 - Comme mentionné dans sa première phrase, cette réponse montre un moyen de rendre les données CSV plus cohérentes. Par exemple. en remplaçant temporairement une virgule intégrée aux guillemets par un caractère de jeton neutre ... puis en la retournant dans une virgule après le déplacement / coupe / suppression. Encore une fois, comme mentionné, l'étape déplacer / couper / supprimer est remplacée par un simple vidage de champ awk .
Peter.O
1
il échoue pour ce cas: "15111 N. Hayden Rd., Ste 160,", ""
user121196
@ user121196: Merci de l'avoir signalé. J'ai mis à jour la réponse avec un correctif.
Peter.O
15

Cela dépend si votre fichier CSV utilise des virgules uniquement pour les délimiteurs, ou si vous avez une folie comme:

champ un, "champ, deux", champ trois

Cela suppose que vous utilisez un simple fichier CSV:

Supprimer une colonne

Vous pouvez vous débarrasser d'une seule colonne de plusieurs façons; J'ai utilisé la colonne 2 comme exemple. La façon la plus simple est probablement d'utiliser cut, qui vous permet de spécifier un délimiteur -det les champs que vous souhaitez imprimer -f; cela lui dit de se séparer par des virgules et le champ de sortie 1 et les champs 3 jusqu'à la fin:

$ cut -d, -f1,3- /path/to/your/file

Si vous avez réellement besoin d'utiliser sed, vous pouvez écrire une expression régulière qui correspond aux premiers n-1champs, au nchamp th et au reste, et ignorer la sortie du nth (ici n2, donc le premier groupe correspond à l' 1heure :) \{1\}:

$ sed 's/\(\([^,]\+,\)\{1\}\)[^,]\+,\(.*\)/\1\3/' /path/to/your/file

Il existe plusieurs façons de le faire awk, aucune d'entre elles n'est particulièrement élégante. Vous pouvez utiliser une forboucle, mais gérer la virgule de fin est une douleur; en ignorant que ce serait quelque chose comme:

$ awk -F, '{for(i=1; i<=NF; i++) if(i != 2) printf "%s,", $i; print NL}' /path/to/your/file

Je trouve plus facile de sortir le champ 1, puis substrde tout retirer après le champ 2:

$ awk -F, '{print $1 "," substr($0, length($1)+length($2)+3)}' /path/to/your/file

C'est ennuyeux pour les colonnes plus loin

Duplication d'une colonne

Dans sedc'est essentiellement la même expression que précédemment, mais vous capturez également la colonne cible et incluez ce groupe plusieurs fois dans le remplacement:

$ sed 's/\(\([^,]\+,\)\{1\}\)\([^,]\+,\)\(.*\)/\1\3\3\4/' /path/to/your/file

Dans awkle cas de la boucle for, ce serait quelque chose comme (en ignorant à nouveau la virgule de fin):

$ awk -F, '{
for(i=1; i<=NF; i++) {
    if(i == 2) printf "%s,", $i;
    printf "%s,", $i
}
print NL
}' /path/to/your/file

Le substrchemin:

$ awk -F, '{print $1 "," $2 "," substr($0, length($1)+2)}' /path/to/your/file

(tcdyl a trouvé une meilleure méthode dans sa réponse )

Déplacer une colonne

Je pense que la sedsolution découle naturellement des autres, mais elle commence à devenir ridiculement longue

Michael Mrozek
la source
Voilà une réponse chargée! +1 :)
jaypal singh
Ridiculement long? Pah !
Gilles 'SO- arrête d'être méchant'
12

awkest votre meilleur pari. awkimprime les champs par numéro, donc ...

awk 'BEGIN { FS=","; OFS=","; } {print $1,$2,$3}' file

Pour supprimer une colonne, pas l'imprimer:

 awk 'BEGIN { FS=","; OFS=","; } {print $1,$3}' file

Pour modifier la commande:

awk 'BEGIN { FS=","; OFS=","; } {print $3,$1,$2}' file

Redirigez vers un fichier de sortie.

awk 'BEGIN { FS=","; OFS=","; } {print $3,$1,$2}' file > output.file

awk peut également formater la sortie.

Sortie au format awk

Panthère
la source
Puisque c'est CSV, vous aurez également besoin BEGIN { FS=","; OFS=","; }.
1
Je pense que même FS = OFS = "," fonctionnera.
5

Étant donné un fichier délimité par des espaces au format suivant:

1 2 3 4 5

Vous pouvez supprimer le champ 2 avec awk comme ceci:

awk '{ sub($2,""); print}' file

qui revient

1  3 4 5

Remplacez la colonne 2 par la colonne n, le cas échéant.

Pour dupliquer la colonne 2,

awk '{ col = $2 " " $2; $2 = col; print }' file

qui revient

1 2 2 3 4 5

Pour commuter les colonnes 2 et 3,

awk '{temp = $2; $2 = $3; $3 = temp; print}'

qui revient

1 3 2 4 5

awk est généralement très bon pour gérer le concept de champs . Si vous avez affaire à un fichier CSV et non à un fichier délimité par des espaces, vous pouvez simplement utiliser

awk -F,

pour définir votre champ comme une virgule, au lieu d'un espace (qui est la valeur par défaut). Il existe un certain nombre de bonnes ressources awk en ligne, dont une que je liste comme source ci-dessous.

Source pour # 3

tcdyl
la source
Je ne sais pas grand-chose awk, mais il semble produire un espace séparé, même si le séparateur de champ est ,(le séparateur de champ contrôle simplement la façon dont il gère les entrées)
Michael Mrozek
@MichaelMrozek: oui, c'est la variable OFS awk qui contrôle le séparateur de champ de sortie.
enzotib
Oui, et comme je le mentionne dans ma réponse, vous pouvez passer l'option -F à awk pour changer le délimiteur (par exemple -F,)
tcdyl
0

Cela fonctionnera pour la suppression

awk '{$2="";$0=$0;$1=$1}1'

Contribution

a b c d

Sortie

a c d
Steven Penny
la source