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.
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.
Respostas:
Você poderia usar o awk para isso. Altere '$ 2' para a enésima coluna desejada.
awk -F "\"*,\"*" '{print $2}' textfile.csv
gawk -F"|" "{print $13}" files*.csv
...,"string,string",...
"
e a última terminará com"
awk -F "\"*;\"*" '{print $2}' textfile.csv
sim. cat mycsv.csv | cut -d ',' -f3
imprimirá a 3ª coluna.
awk
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.
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.
format '%(2)\n'
comando não poderia dizer onde termina um campo. (csvtool 1.4.2)
csvtool
parecem exigir o uso -
como nome de arquivo de entrada para ler stdin.
csvtool format '%(1),%(10)\n' - < in.csv > out.csv
Desembarcou aqui procurando extrair de um arquivo separado por tabulações. Pensei em acrescentar.
cat textfile.tsv | cut -f2 -s
Onde -f2
extrai o 2, coluna indexada diferente de zero, ou a segunda coluna.
cat
é desnecessário:< textfile.tsv cut -f2 -s
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.
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 < file
parte é 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.
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 dos2unix
para 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.
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 ...]
Eu precisava de análise CSV adequada, não cut
/ awk
e 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
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
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'
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
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 csvcut
utilitá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 q
opção:
csvcut -q '"' -c 2 filename_in.csv > filename_out.csv
Instale com pip install csvkit
ou sudo apt install csvkit
.
Você não pode fazer isso sem um analisador CSV completo.
cut
?
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
Você também pode usar o loop 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}'
irá imprimir em2
vez de2,3,4,5
.