bash - por - Cómo extraer una columna de un archivo csv
ordenar datos en archivo csv (11)
Si tengo un archivo csv, ¿hay una manera rápida de imprimir los contenidos de solo una columna? Es seguro suponer que cada fila tiene el mismo número de columnas, pero el contenido de cada columna tendría una longitud diferente.
Aterrizó aquí buscando extraer de un archivo separado por pestañas. Pensé que agregaría.
cat textfile.tsv | cut -f2 -s
Donde -f2
extrae el 2, columna indexada distinta de cero, o la segunda columna.
La forma más sencilla en que pude hacer esto fue simplemente usar csvtool . También tuve otros casos de uso para usar csvtool y puede manejar las comillas o los delimitadores de manera apropiada si aparecen dentro de los datos de la columna en sí.
csvtool format ''%(2)/n'' input.csv
Reemplazar 2 con el número de columna extraerá efectivamente los datos de columna que está buscando.
Las otras respuestas funcionan bien, pero como pidió una solución utilizando solo el shell bash, puede hacer esto:
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
Y luego puede sacar las columnas (la primera en este ejemplo) como sigue:
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
Así que hay un par de cosas pasando aquí:
while IFS=,
- esto significa usar una coma como el IFS (Separador interno de campos), que es lo que el shell usa para saber qué separa los campos (bloques de texto). Así que decir IFS =, es como decir "a, b" es lo mismo que "ab" sería si IFS = "" (que es lo que es por defecto).read -a csv_line;
- esto significa leer en cada línea, una a la vez y crear una matriz donde cada elemento se llama "csv_line" y enviarlo a la sección "do" de nuestro ciclo whiledo echo "${csv_line[0]}";done < file
- ahora estamos en la fase "do", y estamos diciendo que echo el 0 ° elemento de la matriz "csv_line". Esta acción se repite en cada línea del archivo. La< file
parte de< file
está diciendo al ciclo while de dónde leer. NOTA: recuerde, en bash, las matrices están indexadas en 0, por lo que la primera columna es el 0 ° elemento.
Entonces ahí lo tienes, sacando una columna de un CSV en el caparazón. Las otras soluciones son probablemente más prácticas, pero esta es pura bash.
Muchas respuestas para estas preguntas son excelentes y algunas incluso han investigado los casos de las esquinas. Me gustaría agregar una respuesta simple que pueda ser de uso diario ... donde en su mayoría ingresas en esos casos de esquina (como haber escapado de comas o comas entre comillas, etc.).
FS (Separador de campo) es la variable cuyo valor se dafaulted al espacio. Entonces awk por defecto se divide en el espacio para cualquier línea.
Entonces, al usar BEGIN (Ejecutar antes de tomar entrada) podemos establecer este campo a cualquier cosa que queramos ...
awk ''BEGIN {FS = ","}; {print $3}''
El código anterior imprimirá la tercera columna en un archivo csv.
Necesitaba un análisis CSV correcto, no cut
/ awk
y oración. Estoy intentando esto en un mac sin csvtool
, pero los Mac vienen con ruby, así que puedes hacer:
echo "require ''csv''; CSV.read(''new.csv'').each {|data| puts data[34]}" | ruby
No puedes hacerlo sin un analizador de CSV completo.
Podrías usar awk para esto. Cambie ''$ 2'' a la enésima columna que desee.
awk -F "/"*,/"*" ''{print $2}'' textfile.csv
Puede usar GNU Awk, consulte este artículo de la guía del usuario . Como una mejora de la solución presentada en el artículo (en junio de 2015), el siguiente comando gawk permite comillas dobles dentro de los campos con comillas dobles; una comilla doble está marcada por dos comillas dobles consecutivas ("") allí. Además, esto permite campos vacíos, pero incluso esto no puede manejar campos de líneas múltiples . El siguiente ejemplo imprime la tercera columna (a través de 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)
Tenga en cuenta el uso de dos2unix
para convertir posibles saltos de línea estilo DOS (CRLF es decir "/ r / n") y codificación UTF-16 (con marca de orden de bytes) a "/ n" y UTF-8 (sin marca de orden de bytes), respectivamente . Los archivos CSV estándar usan CRLF como salto de línea, ver Wikipedia .
Si la entrada puede contener campos de líneas múltiples, puede usar la siguiente secuencia de comandos. Tenga en cuenta el uso de una cadena especial para separar registros en la salida (ya que la nueva línea separador predeterminada podría ocurrir dentro de un registro). Nuevamente, el siguiente ejemplo imprime la tercera columna (a través de 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)
Hay otro acercamiento al problema. csvquote puede generar contenidos de salida de un archivo CSV modificado para que los caracteres especiales dentro del campo se transformen para que las herramientas habituales de procesamiento de texto de Unix puedan utilizarse para seleccionar determinada columna. Por ejemplo, el siguiente código genera la tercera columna:
csvquote textfile.csv | cut -d '','' -f 3 | csvquote -u
csvquote
se puede usar para procesar archivos grandes arbitrarios.
Utilizando este código por un tiempo, no es "rápido" a menos que cuente "cortar y pegar desde ".
Utiliza operadores $ {##} y $ {%%} en un bucle en lugar de IFS. Llama ''err'' y ''die'', y solo admite coma, guión y pipa como caracteres SEP (eso es todo lo que necesitaba).
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*}
}
Ejemplo:
$ 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
[dumb @ one pts] $ cat> file # Primero, crearemos un CSV básico
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
[dumb @ one pts] $ awk -F, ''{print $ 1}'' archivo
un
1
un
1
sí. cat mycsv.csv | cut -d '','' -f3
cat mycsv.csv | cut -d '','' -f3
imprimirá la tercera columna.