Réorganisation des colonnes à l'aide de awk


12

J'essaie de déplacer la 7e colonne de mon fichier csv à la fin en utilisant

awk -F '{print $1,$2,$3,$4,$5,$6,$8,$9,$10,$11,$7}',OFS= "$file"

où $ file est un fichier .csv dans un répertoire. Cependant, la sortie est

awk:                          ^ syntax error

Quelqu'un sait-il comment corriger cette erreur?


7
Lorsque vous affichez des erreurs awk, vous devez montrer le tout. Le ^indique la partie spécifique de la commande où l'erreur a été rencontrée.
terdon

Réponses:


10

L' -Foption a besoin d'un argument: -F,par exemple.

La fin du awkscript doit être séparée par un (caractère espace) avec le reste des paramètres.

Si le séparateur de champ est ,et que vous souhaitez le conserver, et si le nombre de colonnes est constant et inférieur ou égal à 11, essayez ceci:

awk -F, '{print $1,$2,$3,$4,$5,$6,$8,$9,$10,$11,$7}' OFS=, "$file"

8
@anuribs très peu de programmes le permettent. La manière standard est command file > newfile && mv newfile file. Cela dit, la version plus récente de GNU awkpour soutenir ceci: gawk -i inplace '{blah blah}' file.
terdon

1
Alternativement, au lieu de mv newfile filevous pouvez utiliser cat newfile > file ; rm -f newfile- cela préserve l'inode et les autorisations de file.
cas

et c'est généralement une bonne idée d'utiliser mktempplutôt que de coder en dur les noms de fichiers temporaires dans les scripts. par exempletf=$(mktemp) ; command file > "$tf" ; cat "$tf" > file ; rm -f "$tf"
cas

8

Une solution plus courte serait

awk -F',+' -v OFS=, '{$(NF+1)=$7; $7=""; $0=$0; $1=$1}1' file

Je ne sais pas si ,+cela fonctionnera dans toutes les awkversions, mais fonctionne au moins dans GNU awk, également avec le -cmode de compatibilité.

Explication:

  • $(NF+1)=$7: nous ajoutons d'abord le 7ème champ à la fin de la ligne (pourrait être $12=$7dans ce cas)
  • $7="": à l'étape suivante, le 7ème champ est effacé (mais les délimiteurs environnants restent)
  • pour supprimer les délimiteurs, nous devons réinitialiser l'enregistrement entier (via $0=$0) en traitant plusieurs virgules comme séparateur de champ (cela se fait via -F',+', +signifie ici une ou plusieurs fois), et également réorganiser l'enregistrement actuel via $1=$1pour forcer la reconstruction de la ligne en utilisant le champ de sortie précédemment défini séparateur (défini par une option -v OFS=,)
  • après tout mélange, nous sommes prêts à imprimer le résultat avec 1

Exemple d'entrée:

1,2,3,4,5,6,7,8,9,10,11

production

1,2,3,4,5,6,8,9,10,11,7

Et si les autres colonnes sont vides? Mais, oui, FS est une expression régulière dans POSIX (s'il s'agit de plusieurs caractères), ,+devrait donc fonctionner.
Random832

(1) Je comprends que faire «disparaître» la septième colonne de données d'entrée, et pas seulement la mettre à zéro, est une partie délicate de ce problème. Mais, comme le dit Random832, votre solution encombre les colonnes vides (par exemple, all,ball,call,,,fallall,ball,call,fall). (2)  $(NF+1)=$7est une approche intelligente. À mon humble avis, $0 = $0 OFS $7est un peu plus clair, seulement quelques caractères de plus, et il semble faire la même chose. Pouvez-vous penser à une situation dans laquelle $0 = $0 OFS $7ne fait pas la même chose que votre code?
G-Man dit `` Réintègre Monica '' le

@ Random832 @ G-Man oui, certains cas marginaux comme les champs vides, les lignes vides ou NF <7 doivent être traités séparément ou on doit réorganiser le code. Ce n'est qu'une idée, pas une "solution complète" pour tous les cas généraux, cela devrait être clair. $0=$0 OFS $7est probablement identique à $(NF+1)=$7, mais uniquement avec le reste du code inchangé, pas en général.
jimmij

5

Si vous imprimez avec OFS=, donc sans séparateur entre les champs, vous pouvez simplement enregistrer la valeur de $7dans une variable, définie $7sur vide et imprimer directement la ligne et la variable. Vous n'avez pas besoin de spécifier tous les champs:

$ cat file
1,2,3,4,5,6,7,8
$ awk -F, -vOFS= '{k=$7; $7=""; print $0,k}' file 
12345687


3

Vous n'avez pas dit spécifiquement que vous vouliez utiliser awk, et vous avez dit que vous vouliez utiliser l' édition en place comme prévu par sed -i, alors voici une sed -ivariante. Il awkest généralement préférable de travailler avec des colonnes, mais c'est un cas où je préfère sed, car il gère naturellement des nombres arbitraires de colonnes.

MOVECOL=7
N=$((MOVECOL-1))
sed -r -e "s/^(([^,]*,){$N})([^,]*),(.*)/\1\4,\3/" -i test.csv

Explication:

  • -r sélectionne des expressions régulières étendues afin d'éviter de nombreuses contre-obliques
  • le premier groupe est $ N répétitions de chaînes terminées par des virgules, en d'autres termes les colonnes avant celle que nous voulons déplacer, avec une virgule finale
  • le deuxième groupe est le $ N-ème répétition, on l'oublie
  • le troisième groupe est la colonne que nous voulons déplacer, sans la virgule finale
  • le quatrième groupe est composé de toutes les colonnes après celle que nous voulons déplacer, sans virgule avant
  • nous remplaçons par le premier groupe, le dernier groupe et la colonne que nous avons extraite, en insérant la virgule au besoin.

Bien sûr, cela ne fonctionnera pas avec des fichiers qui cachent des virgules entre guillemets (ou pire, les échappent), mais awk ne s'en occupera pas non plus sans quelques acrobaties sérieuses. Si vous avez ce problème, vous feriez mieux avec le perlmodule Text:CSVou le pythonmodule csv.


2

Quelques awkvariantes (en supposant que votre fichier se trouve à l'intérieur de la variable $file)

  • Ici, vous pouvez faire défiler toutes les colonnes, imprimer avec le séparateur de champ (OFS) et imprimer le terminateur d'enregistrement (ORS) à la fin de la ligne.

    awk  -F',' -v OFS=,                                \
    '{for(i=1;i<=NF;i++) if (i!=7) printf "%s",$i OFS; \
    printf "%s",$7;printf ORS}' "$file"
    
  • Ici avec l'utilisation d'une expression régulière et de la gensub()fonction

    gawk -F',+' -v OFS=, '{$0=gensub(/\s*\S+/,"",7) OFS $7}1' "$file"

    tuer le 7 e champ et l'imprimer à la fin de la ligne.

    • $0 est tout le record
    • $nest le nième record
    • NF est le nombre de champs de la ligne courante
    • OFS le séparateur déposé en sortie
    • ORS le terminateur d'enregistrement de sortie
    • 1est l'astuce pour dire awk trueet imprimer la valeur par défaut ( $0).

Mettre à jour ...

J'oublie presque, il est possible de décaler toutes les colonnes après la 7 ème .

awk  -F',' -v OFS=, '{tmp=$7; for(i=7;i<=NF;i++) $i=$(i+1); $NF=tmp}1 ' "$file"

(1) Sans doute, OFS $7serait plus robuste que "," $7. (2) Je pense que ", " $7c'est faux, dans la mesure où la question indique que l'OP ne veut pas d'espaces après les virgules. (Et, si les données d'entrée avaient des espaces après les virgules, alors $7commenceraient déjà par un espace, et vous en ajouteriez un supplémentaire.)
G-Man dit 'Reinstate Monica'

@ G-Man Il s'agissait principalement de proposer quelques idées, quelques variantes. Merci, pour le spot, je suis d'accord OFS $7, non seulement plus robuste, mais encore plus général ( "la hâte fait gaspiller" )
Hastur
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.