Riorganizzare le colonne usando awk


12

Sto cercando di spostare la settima colonna del mio file CSV alla fine utilizzando

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

dove $ file è un file .csv in una directory. Tuttavia, l'output è

awk:                          ^ syntax error

Qualcuno sa come correggere questo errore?


7
Quando mostri errori awk, devi mostrare tutto. La ^indica la parte specifica del comando in cui è stato rilevato l'errore.
terdon

Risposte:


10

L' -Fopzione ha bisogno di un argomento: -F,ad esempio.

La fine dello awkscript deve essere separata con un (space char) con il resto dei parametri.

Se il separatore di campo è ,e si desidera mantenerlo e se il numero di colonne è costante e inferiore o uguale a 11, provare a questo:

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

8
@anuribs pochi programmi lo consentono. Il modo standard è command file > newfile && mv newfile file. Detto questo, la versione più recente di GNU awkper sostenere questo: gawk -i inplace '{blah blah}' file.
terdon

1
in alternativa, invece di mv newfile filete puoi usare cat newfile > file ; rm -f newfile- questo conserva l'inode e le autorizzazioni di file.
Cas

ed è generalmente una buona idea usare mktemppiuttosto che codificare i nomi dei file temporanei negli script. ad esempiotf=$(mktemp) ; command file > "$tf" ; cat "$tf" > file ; rm -f "$tf"
cas

8

La soluzione più breve sarebbe

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

Non sono sicuro ,+che funzionerà in tutte le awkversioni, ma funziona almeno in awk GNU, anche con la -cmodalità ompatibilità.

Spiegazione:

  • $(NF+1)=$7: prima aggiungiamo il 7 ° campo alla fine della riga (potrebbe essere $12=$7in questo caso)
  • $7="": nel passaggio successivo il 7 ° campo viene cancellato (ma i delimitatori circostanti rimangono)
  • per rimuovere i delimitatori dobbiamo reimpostare l'intero record (via $0=$0) trattando più virgole come separatore di campo (questo viene fatto tramite -F',+', qui +significa una o più volte), e anche riorganizzare il record corrente tramite $1=$1per forzare la ricostruzione della linea utilizzando il campo di output precedentemente impostato separatore (impostato da un'opzione -v OFS=,)
  • dopo che tutto è stato mischiato, siamo pronti per stampare il risultato con 1

Esempio di input:

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

produzione

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

Cosa succede se le altre colonne sono vuote? Ma sì, FS è un'espressione regolare in POSIX (se contiene più caratteri), quindi ,+dovrebbe funzionare.
Casuale 832,

(1) Capisco che far scomparire la settima colonna di dati di input, e non solo impostarlo su null, è una parte difficile di questo problema. Ma, come dice Random832, la tua soluzione blocca colonne vuote (ad esempio, all,ball,call,,,fallall,ball,call,fall). (2)  $(NF+1)=$7è un approccio intelligente. IMHO, $0 = $0 OFS $7è un po 'più chiaro, solo un paio di personaggi in più, e sembra fare la stessa cosa. Riesci a pensare a una situazione in cui $0 = $0 OFS $7non fa lo stesso del tuo codice?
G-Man dice "Ripristina Monica" il

@ Random832 @ G-Man sì, alcuni casi limite come campi vuoti, righe vuote o NF <7 dovrebbero essere trattati separatamente o si dovrebbe riorganizzare il codice. Questa è solo un'idea, non una "soluzione completa" per tutti i casi generali, che dovrebbe essere chiara. $0=$0 OFS $7è probabilmente identico a $(NF+1)=$7, ma solo con il resto del codice invariato, non in generale.
Jimmij,

5

Se stai stampando con OFS=, quindi senza un separatore tra i campi, puoi semplicemente salvare il valore di $7in una variabile, impostare $7su vuoto e stampare direttamente la linea e la variabile. Non è necessario specificare tutti i campi:

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


3

Lei non ha espressamente detto che volevi usare awk, e hai detto che volevi usare la modifica sul posto come fornito da sed -i, ecco un sed -ivariante. Di solito awkè meglio per lavorare con le colonne, ma questo è un caso in cui preferisco sed, perché gestisce naturalmente un numero arbitrario di colonne.

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

Spiegazione:

  • -r seleziona regexps estesi in modo da evitare molte barre rovesciate
  • il primo gruppo è $ N ripetizioni di stringhe terminate da virgola, in altre parole le colonne prima di quella che vogliamo spostare, con una virgola finale
  • il secondo gruppo è la ripetizione di $ N-esima, ce ne dimentichiamo
  • il terzo gruppo è la colonna che vogliamo spostare, senza la virgola finale
  • il quarto gruppo è composto da tutte le colonne dopo quella che vogliamo spostare, senza virgola prima
  • sostituiamo con il primo gruppo, l'ultimo gruppo e la colonna che abbiamo estratto, inserendo la virgola secondo necessità.

Naturalmente questo non funzionerà con i file che nascondono le virgole tra virgolette (o peggio, sfuggirle), ma awk non lo gestirà senza alcune acrobazie serie. Se hai questo problema, staresti meglio con il perlmodulo Text:CSVo il pythonmodulo csv.


2

Un paio di awkvarianti (supponendo che il tuo file sia all'interno della variabile $file)

  • Qui è possibile scorrere per tutta la colonna, stampare con il separatore di campo (OFS) e stampare il terminatore di registrazione (ORS) alla fine della riga.

    awk  -F',' -v OFS=,                                \
    '{for(i=1;i<=NF;i++) if (i!=7) printf "%s",$i OFS; \
    printf "%s",$7;printf ORS}' "$file"
  • Qui con l'utilizzo di una regex e la gensub()funzione

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

    uccidendo il 7 ° campo e stampandolo alla fine della riga.

    • $0 è l'intero record
    • $nè l' ennesimo record
    • NF è il numero di campi della riga corrente
    • OFS il separatore archiviato in uscita
    • ORS il terminatore del record di output
    • 1è il trucco per dire di awk truee stampare il valore predefinito ( $0).

Aggiorna ...

Quasi dimentico, è possibile spostare tutte le colonne seguendo la settima .

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

(1) Probabilmente, OFS $7sarebbe più robusto di "," $7. (2) Credo che ", " $7sia sbagliato, in quanto la domanda indica che l'OP non vuole spazi dopo le virgole. (E, se i dati di input avessero spazi dopo le virgole, allora $7inizierebbero già con uno spazio e ne aggiungeresti uno in più.)
G-Man dice 'Reinstate Monica' il

@ G-Man Era principalmente quello di proporre alcune idee, alcune varianti. Grazie, per lo spot, sono d'accordo OFS $7, non solo più robusto, ma anche più generale ( "la fretta fa sprecare" )
Hastur,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.