Como extrair uma coluna de um arquivo csv

111

Se eu tiver um arquivo csv, há uma maneira rápida do bash para imprimir o conteúdo de apenas uma coluna? É seguro presumir que cada linha tem o mesmo número de colunas, mas o conteúdo de cada coluna teria um comprimento diferente.

user788171
fonte

Respostas:

135

Você poderia usar o awk para isso. Altere '$ 2' para a enésima coluna desejada.

awk -F "\"*,\"*" '{print $2}' textfile.csv
synthesizerpatel
fonte
13
echo '1,"2,3,4,5",6' | awk -F "\"*,\"*" '{print $2}'irá imprimir em 2vez de 2,3,4,5.
Igor Mikushkin
Se você for um cara de sorte usando GNU Tools no Windows, você pode executar o mesmo comando que @IgorMikushkin da seguinte forma:gawk -F"|" "{print $13}" files*.csv
Elidio Marquina
10
Eu acho que essa falha quando existem cadeias que contêm uma vírgula, ou seja,...,"string,string",...
nitrato de sódio
Acho que para o primeiro e último colume, isso terá alguma falha. A primeira coluna começará com "e a última terminará com"
BigTailWolf
Alguns programas retornam arquivos CSV com delimitadores diferentes, portanto, pode ser necessário alterar a expressão regular de acordo. Exemplo para um delimitador de ponto e vírgula: awk -F "\"*;\"*" '{print $2}' textfile.csv
gekkedev
88

sim. cat mycsv.csv | cut -d ',' -f3imprimirá a 3ª coluna.

madrag
fonte
8
A menos que a coluna dois contenha uma vírgula, nesse caso você obteria a segunda metade da coluna dois. Caso no ponto <col1>, "3.000", <col2>. Minha resposta não é muito melhor com relação a esse problema. Portanto, não fique chateado.
synthesizerpatel
@synthesizerpatel Concordo melhor em usarawk
MattSizzle
1
Não temos certeza se seu arquivo CSV contém aspas duplas para diferenciar os diferentes valores. Seria melhor que ele fornecesse um arquivo de entrada para que possamos avaliar a solução mais adequada.
Idriss Neumann
50

A maneira mais simples de fazer isso foi usar apenas csvtool . Eu também tive outros casos de uso para usar csvtool e ele pode lidar com as aspas ou delimitadores de forma adequada se eles aparecerem nos próprios dados da coluna.

csvtool format '%(2)\n' input.csv

Substituir 2 pelo número da coluna extrairá efetivamente os dados da coluna que você está procurando.

Samar
fonte
14
Esta deve ser a resposta aceita. Essa ferramenta sabe como lidar com arquivos CSV, muito além de tratar uma vírgula como um separador de campo. Para extrair a 2ª coluna, "csvtool col 2 input.csv"
Vladislavs Dovgalecs
3
Apenas um aviso ... se você quiser usar csvtool com entrada padrão (exemplo csv vem de outro comando) é algo parecido com isso cat input.csv | csvtool formath '%(2)\n' -Nota Eu sei que cat aqui é inútil, mas submeta-o para qualquer comando que normalmente exportaria um csv.
General Redneck
Se houver campos de várias linhas, o format '%(2)\n'comando não poderia dizer onde termina um campo. (csvtool 1.4.2)
jarno
1
As versões mais recentes de csvtoolparecem exigir o uso -como nome de arquivo de entrada para ler stdin.
Connor Clark,
@GeneralRedneck por que usar gato? e seu formato não é formatocsvtool format '%(1),%(10)\n' - < in.csv > out.csv
sijanec
14

Desembarcou aqui procurando extrair de um arquivo separado por tabulações. Pensei em acrescentar.

cat textfile.tsv | cut -f2 -s

Onde -f2extrai o 2, coluna indexada diferente de zero, ou a segunda coluna.

cevaris
fonte
simples, também o ponto, e mais facilmente adaptável do que os outros exemplos. obrigado!
Nick Jennings
6
Nitpicking, mas caté desnecessário:< textfile.tsv cut -f2 -s
Anne van Rossum
8

Muitas respostas para essas perguntas são ótimas e algumas até examinaram os casos de canto. Eu gostaria de adicionar uma resposta simples que pode ser de uso diário ... onde você geralmente entra nesses casos extremos (como vírgulas de escape ou vírgulas entre aspas etc.).

FS (Field Separator) é a variável cujo valor é padronizado para o espaço. Portanto, awk por padrão se divide no espaço para qualquer linha.

Então, usando BEGIN (Executar antes de inserir), podemos definir este campo para qualquer coisa que quisermos ...

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

O código acima imprimirá a 3ª coluna em um arquivo csv.

roteador
fonte
1
Eu tentei isso e ainda considera vírgulas dentro dos campos entre aspas.
Daniel C. Sobral de
5

As outras respostas funcionam bem, mas como você pediu uma solução usando apenas o shell bash, você pode fazer o seguinte:

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 então você pode retirar colunas (a primeira neste exemplo) assim:

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

Portanto, há algumas coisas acontecendo aqui:

  • while IFS=,- significa usar uma vírgula como IFS (Separador de campo interno), que é o que o shell usa para saber o que separa os campos (blocos de texto). Portanto, dizer IFS = é como dizer "a, b" é o mesmo que "a b" seria se IFS = "" (que é o que é por padrão).

  • read -a csv_line; - isso quer dizer leia em cada linha, um de cada vez e crie um array onde cada elemento é chamado de "csv_line" e envie para a seção "do" de nosso loop while

  • do echo "${csv_line[0]}";done < file- agora estamos na fase "do", e estamos dizendo echo o 0º elemento do array "csv_line". Esta ação é repetida em todas as linhas do arquivo. A < fileparte é apenas dizer ao loop while de onde ler. NOTA: lembre-se, em bash, os arrays são indexados em 0, então a primeira coluna é o 0º elemento.

Então aí está, puxando uma coluna de um CSV no shell. As outras soluções são provavelmente mais práticas, mas esta é pura bash.

drldcsta
fonte
5

Você pode usar o GNU Awk, consulte este artigo do guia do usuário . Como uma melhoria para a solução apresentada no artigo (em junho de 2015), o seguinte comando gawk permite aspas duplas dentro de campos com aspas duplas; uma aspa dupla é marcada por duas aspas duplas consecutivas (""). Além disso, isso permite campos vazios, mas mesmo isso não pode lidar com campos de várias linhas . O exemplo a seguir imprime a 3ª coluna (via c=3) de 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)

Observe o uso de dos2unixpara converter possíveis quebras de linha de estilo DOS (CRLF ou seja, "\ r \ n") e codificação UTF-16 (com marca de ordem de byte) para "\ n" e UTF-8 (sem marca de ordem de byte), respectivamente. Arquivos CSV padrão usam CRLF como quebra de linha, consulte Wikipedia .

Se a entrada pode conter campos de várias linhas, você pode usar o seguinte script. Observe o uso de string especial para separar registros na saída (uma vez que a nova linha do separador padrão pode ocorrer dentro de um registro). Novamente, o exemplo a seguir imprime a 3ª coluna (via c=3) de 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)

Existe outra abordagem para o problema. O csvquote pode gerar o conteúdo de um arquivo CSV modificado para que os caracteres especiais dentro do campo sejam transformados de forma que as ferramentas usuais de processamento de texto do Unix possam ser usadas para selecionar certas colunas. Por exemplo, o código a seguir gera a terceira coluna:

csvquote textfile.csv | cut -d ',' -f 3 | csvquote -u

csvquote pode ser usado para processar arquivos grandes arbitrários.

Jarno
fonte
5

Aqui está um exemplo de arquivo csv com 2 colunas

myTooth.csv

Date,Tooth
2017-01-25,wisdom
2017-02-19,canine
2017-02-24,canine
2017-02-28,wisdom

Para obter a primeira coluna, use:

cut -d, -f1 myTooth.csv

f representa campo ed representa delimitador

Executar o comando acima produzirá a seguinte saída.

Resultado

Date
2017-01-25
2017-02-19
2017-02-24
2017-02-28

Para obter apenas a 2ª coluna:

cut -d, -f2 myTooth.csv

E aqui está a saída de saída

Tooth
wisdom
canine
canine
wisdom
incisor

Outro caso de uso:

Seu arquivo de entrada csv contém 10 colunas e você deseja as colunas 2 a 5 e 8, usando a vírgula como separador ".

cut usa -f (que significa "campos") para especificar colunas e -d (que significa "delimitador") para especificar o separador. Você precisa especificar o último porque alguns arquivos podem usar espaços, tabulações ou dois-pontos para separar colunas.

cut -f 2-5,8 -d , myvalues.csv

cut é um utilitário de comando e aqui estão mais alguns exemplos:

SYNOPSIS
     cut -b list [-n] [file ...]
     cut -c list [file ...]
     cut -f list [-d delim] [-s] [file ...]
Stryker
fonte
4

Eu precisava de análise CSV adequada, não cut/ awke oração. Estou tentando isso em um mac sem csvtool, mas os macs vêm com ruby, então você pode fazer:

echo "require 'csv'; CSV.read('new.csv').each {|data| puts data[34]}" | ruby
Darth Egregious
fonte
4

Primeiro, criaremos um CSV básico

[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

Então temos a 1ª coluna

[dumb@one pts]$  awk -F , '{print $1}' file  
a  
1  
a  
1
Raj Velayudhan
fonte
3
csvtool col 2 file.csv 

onde 2 é a coluna na qual você está interessado

você também pode fazer

csvtool col 1,2 file.csv 

para fazer várias colunas

exussum
fonte
3

Acho que o mais fácil é usar o csvkit :

Obtém a 2ª coluna: csvcut -c 2 file.csv

No entanto, também existe o csvtool e provavelmente várias outras ferramentas csv bash por aí:

sudo apt-get install csvtool (para sistemas baseados em Debian)

Isso retornaria uma coluna com a primeira linha contendo 'ID'. csvtool namedcol ID csv_file.csv

Isso retornaria a quarta linha: csvtool col 4 csv_file.csv

Se você quiser descartar a linha do cabeçalho:

csvtool col 4 csv_file.csv | sed '1d'

palavras para o sábio
fonte
2

Eu me pergunto por que nenhuma das respostas até agora mencionou csvkit.

csvkit é um conjunto de ferramentas de linha de comando para converter e trabalhar com CSV

documentação csvkit

Eu o utilizo exclusivamente para gerenciamento de dados csv e até agora não encontrei nenhum problema que não pudesse resolver com o cvskit.

Para extrair uma ou mais colunas de um arquivo cvs, você pode usar o csvcututilitário que faz parte da caixa de ferramentas. Para extrair a segunda coluna, use este comando:

csvcut -c 2 filename_in.csv > filename_out.csv 

página de referência do csvcut

Se as strings no csv estiverem entre aspas, adicione o caractere de aspas com a qopção:

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

Instale com pip install csvkitou sudo apt install csvkit.

Bytes de som
fonte
1

Você não pode fazer isso sem um analisador CSV completo.

Peter Krumins
fonte
1
Quando algo conta como um analisador CSV completo? Conta cut?
HelloGoodbye
0

Estou usando esse código há algum tempo, ele não é "rápido" a menos que você conte "cortar e colar do stackoverflow".

Ele usa os operadores $ {##} e $ {%%} em um loop em vez de IFS. Ele chama 'err' e 'morrer', e suporta apenas vírgula, traço e tubo como caracteres SEP (isso é tudo que eu precisava).

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*}
}

Exemplo:

$ 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
qneill
fonte
0

Você também pode usar o loop while

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

        echo Name: "$name"
done<itemlst.csv
K.Sopheak
fonte
Este código produz um aviso Shellcheck : SC2034 . A pesquisa retorna essa pergunta como o primeiro resultado ao procurar maneiras de contornar o aviso.
jww