Come manipolare un file CSV con sed o awk?


23

Come posso fare quanto segue su un file CSV usando sedo awk?

  • Elimina una colonna
  • Duplica una colonna
  • Sposta una colonna

Ho un grande tavolo con oltre 200 file e non ne ho molta familiarità sed.


1
Cross pubblicato su AskUbuntu
enzotib il

@enzotib puoi pubblicare il link?
n0pe

@MaxMackie askubuntu.com/questions/88142/… . Non riesco a trovare un mod lì a quest'ora, quindi l'ho segnalato chiedendo loro di migrare se sono disposti; ha già una risposta accettata, quindi non sono sicuro che lo faranno
Michael Mrozek

@MichaelMrozek, hmmm cosa succede di solito in queste situazioni? Conserviamo semplicemente i duplicati?
n0pe

1
A meno che non sia necessario eseguire su un sistema in cui sono disponibili solo strumenti di base, consultare Esiste un robusto strumento da riga di comando per l'elaborazione di file CSV?
Gilles 'SO- smetti di essere malvagio'

Risposte:


7

Oltre a come tagliare e riorganizzare i campi (trattati nelle altre risposte), c'è il problema di eccentrici campi CSV.

Se i tuoi dati rientrano in questa categoria "eccentrica", un po 'di pre e post filtro possono occuparsene. I filtri riportati di seguito richiedono i personaggi \x01, \x02, \x03, \x04non appaiono da nessuna parte nei dati.

Ecco i filtri racchiusi in una semplice awkdiscarica di campo.

Nota: field-five ha un layout "campo tra virgolette" non valido / incompleto, ma è benigno alla fine di una riga (a seconda del parser CSV). Ma, naturalmente, provocherebbe risultati inaspettati e problematici se venisse sostituito dalla sua attuale posizione di fine fila .

Aggiornare; user121196 ha segnalato un bug quando una virgola precede una virgoletta finale. Ecco la soluzione.

I dati

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

Il codice

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' 

L'output:

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

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

Ecco il pre-filtro , espanso con commenti.
Il filtro post è solo un'inversione di \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
'

come cancelleresti l'ennesima colonna in base a questo filtro?
user121196

@ user121196 - Come menzionato nella frase di apertura, questa risposta mostra un modo per rendere più coerenti i dati CSV. es. sostituendo temporaneamente una virgola incorporata tra virgolette con un carattere token neutro ... e quindi ripristinandola in una virgola dopo lo spostamento / taglio / eliminazione. Ancora una volta, come detto, il passaggio di spostamento / taglio / eliminazione viene sostituito da un semplice dump di campo awk .
Peter,

1
fallisce per questo caso: "15111 N. Hayden Rd., Ste 160,", ""
user121196

@ user121196: Grazie per averlo segnalato. Ho aggiornato la risposta con una correzione.
Peter.O

15

Questo dipende dal fatto che il tuo file CSV usi le virgole solo per i delimitatori o se hai una follia come:

campo uno, "campo, due", campo tre

Ciò presuppone che tu stia utilizzando un semplice file CSV:

Rimozione di una colonna

Puoi sbarazzarti di una singola colonna in molti modi; Ho usato la colonna 2 come esempio. Il modo più semplice è probabilmente quello di utilizzare cut, che consente di specificare un delimitatore -de quali campi si desidera stampare -f; questo dice di dividere su virgole e campo di output 1 e campi 3 fino alla fine:

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

Se hai effettivamente bisogno di usare sed, puoi scrivere un'espressione regolare che corrisponda ai primi n-1campi, al ncampo th e al resto, e saltare l'output del nth (qui nè 2, quindi il primo gruppo corrisponde al 1tempo corrispondente:) \{1\}:

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

Ci sono molti modi per farlo awk, nessuno particolarmente elegante. Puoi usare un forciclo, ma trattare con la virgola finale è una seccatura; ignorando che sarebbe qualcosa del tipo:

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

Trovo più semplice l'output del campo 1 e quindi utilizzare substrper estrarre tutto dopo il campo 2:

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

Questo è fastidioso per le colonne più avanti però

Duplicazione di una colonna

In sedquesto è essenzialmente la stessa espressione di prima, ma acquisisci anche la colonna di destinazione e includi quel gruppo più volte nella sostituzione:

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

Nel awkmodo for loop sarebbe qualcosa di simile (ignorando di nuovo la virgola finale):

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

Il substrmodo:

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

(tcdyl ha trovato un metodo migliore nella sua risposta )

Spostare una colonna

Penso che la sedsoluzione segua naturalmente dagli altri, ma inizia a diventare ridicolmente lunga


Questa è una risposta carica! +1 :)
jaypal singh


12

awkè la tua scommessa migliore. awkstampa i campi per numero, quindi ...

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

Per rimuovere una colonna, non stamparla:

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

Per modificare l'ordine:

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

Reindirizzare a un file di output.

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

awk può anche formattare l'output.

Uscita in formato Awk


Dal momento che è CSV, avrai anche bisogno BEGIN { FS=","; OFS=","; }.

1
Penso che anche FS = OFS = "," funzionerà.

5

Dato un file delimitato da spazi nel seguente formato:

1 2 3 4 5

Puoi rimuovere il campo 2 con awk in questo modo:

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

che ritorna

1  3 4 5

Sostituire la colonna 2 con la colonna n dove appropriato.

Per duplicare la colonna 2,

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

che ritorna

1 2 2 3 4 5

Per cambiare colonna 2 e 3,

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

che ritorna

1 3 2 4 5

awk è generalmente molto bravo a gestire il concetto di campi . Se hai a che fare con un CSV e non con un file delimitato da spazi, puoi semplicemente utilizzare

awk -F,

per definire il campo come una virgola, anziché uno spazio (che è l'impostazione predefinita). Ci sono un certo numero di buone risorse di awk online, una delle quali ho elencato come fonte di seguito.

Fonte per # 3


Non ne so molto awk, ma sembra che l'output sia separato dallo spazio anche se il separatore di campo è ,(il separatore di campo controlla solo come gestisce l'input)
Michael Mrozek

@MichaelMrozek: sì, è la variabile awk OFS che controlla il separatore del campo di output.
enzotib,

Sì, e come ho già detto nella mia risposta, puoi passare l'opzione -F a awk per modificare il delimitatore (ad esempio -F,)
tcdyl

0

Questo funzionerà per l'eliminazione

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

Ingresso

a b c d

Produzione

a c d
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.