Come estrarre una colonna di un file CSV


111

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:


136

Potresti usare awk per questo. Cambia "$ 2" nell'ennesima colonna che desideri.

awk -F "\"*,\"*" '{print $2}' textfile.csv

13
echo '1,"2,3,4,5",6' | awk -F "\"*,\"*" '{print $2}'stamperà 2invece di 2,3,4,5.
Igor Mikushkin

Se sei un ragazzo fortunato che usa gli strumenti GNU in Windows, puoi eseguire lo stesso comando di @IgorMikushkin come segue:gawk -F"|" "{print $13}" files*.csv
Elidio Marquina

10
Penso che questo fallisca quando ci sono stringhe che contengono una virgola, cioè...,"string,string",...
nitrato di sodio il

Penso che per la prima e ultima colonna, questo avrà qualche difetto. La prima colonna inizierà con "e l'ultima terminerà con"
BigTailWolf

Alcuni programmi restituiscono file CSV con delimitatori diversi, quindi potrebbe essere necessario modificare l'espressione regolare di conseguenza. Esempio di delimitatore punto e virgola: awk -F "\"*;\"*" '{print $2}' textfile.csv
gekkedev

88

sì. cat mycsv.csv | cut -d ',' -f3stamperà la terza colonna.


8
A meno che la colonna due non contenga una virgola, nel qual caso si otterrebbe la seconda metà della colonna due. Caso in questione <col1>, "3,000", <col2>. La mia risposta non è molto migliore rispetto a questo problema però. Quindi non essere deluso.
synthesizerpatel

@synthesizerpatel Accetto di utilizzare meglioawk
MattSizzle

1
Non siamo sicuri che il suo file CSV contenga virgolette doppie per differenziare i diversi valori. Sarebbe meglio che fornisse un file di input in modo da poter valutare la soluzione più appropriata.
Idriss Neumann

50

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.


14
Questa dovrebbe essere la risposta accettata. Questo strumento sa come gestire i file CSV, ben oltre il trattamento di una virgola come separatore di campo. Per estrarre la seconda colonna, "csvtool col 2 input.csv"
Vladislavs Dovgalecs

3
Solo un avvertimento ... se vuoi usare csvtool con l'input standard (l'esempio csv proviene da un altro comando) è qualcosa del genere cat input.csv | csvtool formath '%(2)\n' -Nota So che cat qui è inutile ma sostituiscilo con qualsiasi comando che normalmente esporterebbe un csv.
Generale Redneck

Se ci sono campi multilinea, il format '%(2)\n'comando non è in grado di dire dove finisce un campo. (csvtool 1.4.2)
jarno

1
Le versioni più recenti di csvtoolsembrano richiedere l'utilizzo -come nome del file di input per leggere da stdin.
Connor Clark

@GeneralRedneck perché usare cat? ed è formato non formathcsvtool format '%(1),%(10)\n' - < in.csv > out.csv
sijanec

14

Atterrato qui cercando di estrarre da un file separato da tabulazioni. Ho pensato di aggiungere.

cat textfile.tsv | cut -f2 -s

Where -f2estrae la colonna indicizzata 2, diversa da zero, o la seconda colonna.


semplice, anche il punto, e più facilmente adattabile degli altri esempi. Grazie!
Nick Jennings

6
Nitpicking, ma catnon è necessario:< textfile.tsv cut -f2 -s
Anne van Rossum

8

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.


1
L'ho provato e considera ancora le virgole all'interno dei campi tra virgolette.
Daniel C. Sobral,

5

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 < fileparte 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.


5

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 dos2unixper 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.


5

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 ...]

4

Avevo bisogno di un'adeguata analisi CSV, non cut/ awke 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

4

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

3
csvtool col 2 file.csv 

dove 2 è la colonna che ti interessa

puoi anche farlo

csvtool col 1,2 file.csv 

per fare più colonne


3

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'


2

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

documentazione csvkit

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' csvcututility che fa parte del toolbox. Per estrarre la seconda colonna usa questo comando:

csvcut -c 2 filename_in.csv > filename_out.csv 

pagina di riferimento csvcut

Se le stringhe nel csv sono quotate, aggiungi il carattere di virgolette con l' qopzione:

csvcut -q '"' -c 2 filename_in.csv > filename_out.csv 

Installa con pip install csvkito sudo apt install csvkit.



0

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

0

Puoi anche usare il ciclo while

IFS=,
while read name val; do
        echo "............................"

        echo Name: "$name"
done<itemlst.csv

Questo codice produce un avviso Shellcheck : SC2034 . La ricerca restituisce questa domanda come primo risultato quando si cercano modi per eludere l'avvertimento.
jww
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.