Se ho un file csv, esiste un modo rapido per stampare il contenuto di una singola colonna? È lecito ritenere che ogni riga abbia lo stesso numero di colonne, ma il contenuto di ogni colonna avrebbe una lunghezza diversa.
Se ho un file csv, esiste un modo rapido per stampare il contenuto di una singola colonna? È lecito ritenere che ogni riga abbia lo stesso numero di colonne, ma il contenuto di ogni colonna avrebbe una lunghezza diversa.
Risposte:
Potresti usare awk per questo. Cambia "$ 2" nell'ennesima colonna che desideri.
awk -F "\"*,\"*" '{print $2}' textfile.csv
gawk -F"|" "{print $13}" files*.csv
...,"string,string",...
"
e l'ultima terminerà con"
awk -F "\"*;\"*" '{print $2}' textfile.csv
sì. cat mycsv.csv | cut -d ',' -f3
stamperà la terza colonna.
awk
Il modo più semplice in cui sono stato in grado di farlo è stato usare semplicemente csvtool . Ho avuto anche altri casi d'uso per utilizzare csvtool e può gestire le virgolette o i delimitatori in modo appropriato se compaiono all'interno dei dati della colonna stessa.
csvtool format '%(2)\n' input.csv
La sostituzione di 2 con il numero di colonna estrarrà efficacemente i dati della colonna che stai cercando.
cat input.csv | csvtool formath '%(2)\n' -
Nota So che cat qui è inutile ma sostituiscilo con qualsiasi comando che normalmente esporterebbe un csv.
format '%(2)\n'
comando non è in grado di dire dove finisce un campo. (csvtool 1.4.2)
csvtool
sembrano richiedere l'utilizzo -
come nome del file di input per leggere da stdin.
csvtool format '%(1),%(10)\n' - < in.csv > out.csv
Atterrato qui cercando di estrarre da un file separato da tabulazioni. Ho pensato di aggiungere.
cat textfile.tsv | cut -f2 -s
Where -f2
estrae la colonna indicizzata 2, diversa da zero, o la seconda colonna.
cat
non è necessario:< textfile.tsv cut -f2 -s
Molte risposte a queste domande sono ottime e alcuni hanno persino esaminato i casi d'angolo. Vorrei aggiungere una risposta semplice che può essere di uso quotidiano ... in cui per lo più ti trovi in quei casi angolari (come l'escape di virgole o virgole tra virgolette ecc.).
FS (Field Separator) è la variabile il cui valore predefinito è spazio. Quindi awk per impostazione predefinita si divide nello spazio per qualsiasi riga.
Quindi usando BEGIN (Esegui prima di prendere l'input) possiamo impostare questo campo su tutto ciò che vogliamo ...
awk 'BEGIN {FS = ","}; {print $3}'
Il codice sopra stamperà la terza colonna in un file csv.
Le altre risposte funzionano bene, ma poiché hai chiesto una soluzione usando solo la shell bash, puoi farlo:
AirBoxOmega:~ d$ cat > file #First we'll create a basic CSV
a,b,c,d,e,f,g,h,i,k
1,2,3,4,5,6,7,8,9,10
a,b,c,d,e,f,g,h,i,k
1,2,3,4,5,6,7,8,9,10
a,b,c,d,e,f,g,h,i,k
1,2,3,4,5,6,7,8,9,10
a,b,c,d,e,f,g,h,i,k
1,2,3,4,5,6,7,8,9,10
a,b,c,d,e,f,g,h,i,k
1,2,3,4,5,6,7,8,9,10
a,b,c,d,e,f,g,h,i,k
1,2,3,4,5,6,7,8,9,10
E poi puoi estrarre le colonne (la prima in questo esempio) in questo modo:
AirBoxOmega:~ d$ while IFS=, read -a csv_line;do echo "${csv_line[0]}";done < file
a
1
a
1
a
1
a
1
a
1
a
1
Quindi ci sono un paio di cose in corso qui:
while IFS=,
- questo sta dicendo di usare una virgola come IFS (Internal Field Separator), che è ciò che la shell usa per sapere cosa separa i campi (blocchi di testo). Quindi dire IFS =, è come dire "a, b" è lo stesso di "a b" se IFS = "" (che è quello che è per impostazione predefinita).
read -a csv_line;
- questo significa leggere in ogni riga, uno alla volta e creare un array in cui ogni elemento è chiamato "csv_line" e inviarlo alla sezione "do" del nostro ciclo while
do echo "${csv_line[0]}";done < file
- ora siamo nella fase "do" e stiamo dicendo echo l'elemento 0 ° dell'array "csv_line". Questa azione viene ripetuta su ogni riga del file. La < file
parte sta solo dicendo al ciclo while da dove leggere. NOTA: ricorda, in bash, gli array sono indicizzati 0, quindi la prima colonna è l'elemento 0.
Quindi il gioco è fatto, estraendo una colonna da un CSV nella shell. Le altre soluzioni sono probabilmente più pratiche, ma questa è pura bash.
Puoi usare GNU Awk, vedi questo articolo della guida per l'utente . Come miglioramento della soluzione presentata nell'articolo (giugno 2015), il seguente comando gawk consente le virgolette doppie all'interno dei campi tra virgolette doppie; una virgoletta doppia è contrassegnata da due virgolette doppie consecutive (""). Inoltre, questo consente campi vuoti, ma anche questo non può gestire campi multilinea . L'esempio seguente stampa la terza colonna (tramite c=3
) di textfile.csv:
#!/bin/bash
gawk -- '
BEGIN{
FPAT="([^,\"]*)|(\"((\"\")*[^\"]*)*\")"
}
{
if (substr($c, 1, 1) == "\"") {
$c = substr($c, 2, length($c) - 2) # Get the text within the two quotes
gsub("\"\"", "\"", $c) # Normalize double quotes
}
print $c
}
' c=3 < <(dos2unix <textfile.csv)
Notare l'uso di dos2unix
per convertire possibili interruzioni di riga in stile DOS (CRLF cioè "\ r \ n") e la codifica UTF-16 (con contrassegno dell'ordine dei byte) in "\ n" e UTF-8 (senza contrassegno dell'ordine dei byte), rispettivamente. I file CSV standard utilizzano CRLF come interruzione di riga, vedere Wikipedia .
Se l'input può contenere campi multilinea, è possibile utilizzare il seguente script. Notare l'uso di una stringa speciale per separare i record nell'output (poiché la nuova riga di separazione predefinita potrebbe verificarsi all'interno di un record). Di nuovo, il seguente esempio stampa la terza colonna (tramite c=3
) di textfile.csv:
#!/bin/bash
gawk -- '
BEGIN{
RS="\0" # Read the whole input file as one record;
# assume there is no null character in input.
FS="" # Suppose this setting eases internal splitting work.
ORS="\n####\n" # Use a special output separator to show borders of a record.
}
{
nof=patsplit($0, a, /([^,"\n]*)|("(("")*[^"]*)*")/, seps)
field=0;
for (i=1; i<=nof; i++){
field++
if (field==c) {
if (substr(a[i], 1, 1) == "\"") {
a[i] = substr(a[i], 2, length(a[i]) - 2) # Get the text within
# the two quotes.
gsub(/""/, "\"", a[i]) # Normalize double quotes.
}
print a[i]
}
if (seps[i]!=",") field=0
}
}
' c=3 < <(dos2unix <textfile.csv)
C'è un altro approccio al problema. csvquote può produrre il contenuto di un file CSV modificato in modo che i caratteri speciali all'interno del campo vengano trasformati in modo che i normali strumenti di elaborazione del testo Unix possano essere utilizzati per selezionare determinate colonne. Ad esempio, il codice seguente restituisce la terza colonna:
csvquote textfile.csv | cut -d ',' -f 3 | csvquote -u
csvquote
può essere utilizzato per elaborare file arbitrari di grandi dimensioni.
Ecco un esempio di file CSV con 2 colonne
myTooth.csv
Date,Tooth
2017-01-25,wisdom
2017-02-19,canine
2017-02-24,canine
2017-02-28,wisdom
Per ottenere la prima colonna, usa:
cut -d, -f1 myTooth.csv
f sta per campo ed sta per delimitatore
L'esecuzione del comando precedente produrrà il seguente output.
Produzione
Date
2017-01-25
2017-02-19
2017-02-24
2017-02-28
Per ottenere solo la seconda colonna:
cut -d, -f2 myTooth.csv
Ed ecco l'output Output
Tooth
wisdom
canine
canine
wisdom
incisor
Un altro caso d'uso:
Il tuo file di input CSV contiene 10 colonne e desideri le colonne dalla 2 alla 5 e le colonne 8, utilizzando la virgola come separatore ".
cut usa -f (che significa "campi") per specificare le colonne e -d (che significa "delimitatore") per specificare il separatore. È necessario specificare quest'ultimo perché alcuni file possono utilizzare spazi, tabulazioni o due punti per separare le colonne.
cut -f 2-5,8 -d , myvalues.csv
cut è un'utilità di comando ed ecco alcuni altri esempi:
SYNOPSIS
cut -b list [-n] [file ...]
cut -c list [file ...]
cut -f list [-d delim] [-s] [file ...]
Avevo bisogno di un'adeguata analisi CSV, non cut
/ awk
e della preghiera. Lo sto provando su un Mac senza csvtool
, ma i Mac vengono forniti con Ruby, quindi puoi fare:
echo "require 'csv'; CSV.read('new.csv').each {|data| puts data[34]}" | ruby
Per prima cosa creeremo un CSV di base
[dumb@one pts]$ cat > file
a,b,c,d,e,f,g,h,i,k
1,2,3,4,5,6,7,8,9,10
a,b,c,d,e,f,g,h,i,k
1,2,3,4,5,6,7,8,9,10
Quindi otteniamo la prima colonna
[dumb@one pts]$ awk -F , '{print $1}' file
a
1
a
1
Penso che il modo più semplice sia usare csvkit :
Ottiene la seconda colonna:
csvcut -c 2 file.csv
Tuttavia, c'è anche csvtool e probabilmente una serie di altri strumenti bash csv là fuori:
sudo apt-get install csvtool
(per sistemi basati su Debian)
Ciò restituirebbe una colonna con la prima riga contenente "ID".
csvtool namedcol ID csv_file.csv
Questo restituirebbe la quarta riga:
csvtool col 4 csv_file.csv
Se desideri eliminare la riga di intestazione:
csvtool col 4 csv_file.csv | sed '1d'
Mi chiedo perché nessuna delle risposte finora abbia menzionato csvkit.
csvkit è una suite di strumenti a riga di comando per convertire e lavorare con CSV
Lo uso esclusivamente per la gestione dei dati csv e finora non ho riscontrato un problema che non sono riuscito a risolvere utilizzando cvskit.
Per estrarre una o più colonne da un file cvs è possibile utilizzare l' csvcut
utility che fa parte del toolbox. Per estrarre la seconda colonna usa questo comando:
csvcut -c 2 filename_in.csv > filename_out.csv
Se le stringhe nel csv sono quotate, aggiungi il carattere di virgolette con l' q
opzione:
csvcut -q '"' -c 2 filename_in.csv > filename_out.csv
Installa con pip install csvkit
o sudo apt install csvkit
.
Non puoi farlo senza un parser CSV completo.
cut
contare?
Uso questo codice da un po 'di tempo, non è "veloce" a meno che non conti "taglia e incolla da stackoverflow".
Utilizza gli operatori $ {##} e $ {%%} in un ciclo invece di IFS. Chiama "err" e "die" e supporta solo virgole, trattini e pipe come caratteri SEP (è tutto ciò di cui avevo bisogno).
err() { echo "${0##*/}: Error:" "$@" >&2; }
die() { err "$@"; exit 1; }
# Return Nth field in a csv string, fields numbered starting with 1
csv_fldN() { fldN , "$1" "$2"; }
# Return Nth field in string of fields separated
# by SEP, fields numbered starting with 1
fldN() {
local me="fldN: "
local sep="$1"
local fldnum="$2"
local vals="$3"
case "$sep" in
-|,|\|) ;;
*) die "$me: arg1 sep: unsupported separator '$sep'" ;;
esac
case "$fldnum" in
[0-9]*) [ "$fldnum" -gt 0 ] || { err "$me: arg2 fldnum=$fldnum must be number greater or equal to 0."; return 1; } ;;
*) { err "$me: arg2 fldnum=$fldnum must be number"; return 1;} ;;
esac
[ -z "$vals" ] && err "$me: missing arg2 vals: list of '$sep' separated values" && return 1
fldnum=$(($fldnum - 1))
while [ $fldnum -gt 0 ] ; do
vals="${vals#*$sep}"
fldnum=$(($fldnum - 1))
done
echo ${vals%%$sep*}
}
Esempio:
$ CSVLINE="example,fields with whitespace,field3"
$ $ for fno in $(seq 3); do echo field$fno: $(csv_fldN $fno "$CSVLINE"); done
field1: example
field2: fields with whitespace
field3: field3
Puoi anche usare il ciclo while
IFS=,
while read name val; do
echo "............................"
echo Name: "$name"
done<itemlst.csv
echo '1,"2,3,4,5",6' | awk -F "\"*,\"*" '{print $2}'
stamperà2
invece di2,3,4,5
.