Come posso fare quanto segue su un file CSV usando sed
o 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
.
Come posso fare quanto segue su un file CSV usando sed
o awk
?
Ho un grande tavolo con oltre 200 file e non ne ho molta familiarità sed
.
Risposte:
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
, \x04
non appaiono da nessuna parte nei dati.
Ecco i filtri racchiusi in una semplice awk
discarica 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
'
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:
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 -d
e 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-1
campi, al n
campo th e al resto, e saltare l'output del n
th (qui n
è 2, quindi il primo gruppo corrisponde al 1
tempo corrispondente:) \{1\}
:
$ sed 's/\(\([^,]\+,\)\{1\}\)[^,]\+,\(.*\)/\1\3/' /path/to/your/file
Ci sono molti modi per farlo awk
, nessuno particolarmente elegante. Puoi usare un for
ciclo, 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 substr
per 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ò
In sed
questo è 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 awk
modo 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 substr
modo:
$ awk -F, '{print $1 "," $2 "," substr($0, length($1)+2)}' /path/to/your/file
(tcdyl ha trovato un metodo migliore nella sua risposta )
Penso che la sed
soluzione segua naturalmente dagli altri, ma inizia a diventare ridicolmente lunga
awk
è la tua scommessa migliore. awk
stampa 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.
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
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)
Questo funzionerà per l'eliminazione
awk '{$2="";$0=$0;$1=$1}1'
Ingresso
a b c d
Produzione
a c d