¿Cómo manipular un archivo CSV con sed o awk?


23

¿Cómo puedo hacer lo siguiente a un archivo CSV usando sedo awk?

  • Eliminar una columna
  • Duplicar una columna
  • Mover una columna

Tengo una gran mesa con más de 200 filas, y no estoy tan familiarizado sed.


1
Cross publicado en AskUbuntu
enzotib

@enzotib, ¿puedes publicar el enlace?
n0pe

@MaxMackie askubuntu.com/questions/88142/… . No puedo encontrar un mod allí a esta hora, así que lo señalé pidiéndoles que migren si están dispuestos; ya tiene una respuesta aceptada, así que no estoy seguro de si lo harán
Michael Mrozek

@MichaelMrozek, hmmm, ¿qué pasa generalmente en estas situaciones? ¿Simplemente conservamos los duplicados?
n0pe

1
A menos que necesite ejecutar en un sistema que solo tenga herramientas básicas disponibles, consulte ¿Existe una herramienta de línea de comandos sólida para procesar archivos csv?
Gilles 'SO- deja de ser malvado'

Respuestas:


7

Además de cómo cortar y reorganizar los campos (cubiertos en las otras respuestas), existe el problema de los extravagantes campos CSV.

Si sus datos entran en esta categoría "peculiar", un poco de filtrado previo y posterior puede encargarse de ello. Los filtros que se muestran a continuación requieren los personajes \x01, \x02, \x03, \x04que no aparecen en cualquier parte de sus datos.

Aquí están los filtros envueltos alrededor de un simple awkvolcado de campo.

Nota: el campo cinco tiene un diseño de "campo entre comillas" no válido / incompleto, pero es benigno al final de una fila (dependiendo del analizador CSV). Pero, por supuesto, causaría resultados problemáticos no acelerados si se cambiara de su posición actual de fin de fila .

Actualizar; user121196 ha señalado un error cuando una coma precede a una cita final. Aquí está la solución.

Los datos

cat <<'EOF' >file
field one,"fie,ld,two",field"three","field,\",four","field,five
"15111 N. Hayden Rd., Ste 160,",""
EOF

El código

sed -r 's/^/,/; s/\\"/\x01/g; s/,"([^"]*)"/,\x02\1\x03/g; s/,"/,\x02/; :MC; s/\x02([^\x03]*),([^\x03]*)/\x02\1\x04\2/g; tMC; s/^,// ' file |
  awk -F, '{ for(i=1; i<=NF; i++) printf "%s\n", $i; print NL}' |
    sed -r 's/\x01/\\"/g; s/(\x02|\x03)/"/g; s/\x04/,/g' 

La salida:

field one
"fie,ld,two"
field"three"
"field,\",four"
"field,five

"15111 N. Hayden Rd., Ste 160,"
""

Aquí está el pre filtro , expandido con comentarios.
El filtro posterior es solo una inversión de \x01. \x02` \x03`\x04

sed -r '
    s/^/,/                # add a leading comma delimiter
    s/\\"/\x01/g          # obfuscate escaped quotation-mark (\")
    s/,"([^"]*)"/,\x02\1\x03/g    # obfuscate quotation-marks
    s/,"/,\x02/           # when no trailing quote on last field  
    :MC                   # obfuscate commas embedded in quotes
    s/\x02([^\x03]*),([^\x03]*)/\x02\1\x04\2/g
    tMC
    s/^,//                # remove spurious leading delimiter
'

¿Cómo eliminarías la enésima columna basada en este filtro?
user121196

@ user121196 - Como se mencionó en su oración inicial, esta respuesta muestra una manera de hacer que los datos CSV sean más consistentes ... por ejemplo. reemplazando temporalmente una coma incrustada con comillas con un carácter de ficha neutral ... y luego volviéndola a una coma después del movimiento / corte / eliminación. Nuevamente, como se mencionó, el paso mover / cortar / eliminar se reemplaza por un simple volcado de campo awk .
Peter.O

1
falla para este caso: "15111 N. Hayden Rd., Ste 160,", ""
usuario121196

@ user121196: Gracias por señalarlo. He actualizado la respuesta con una solución.
Peter

15

Esto depende de si su archivo CSV usa comas solo para delimitadores, o si tiene una locura como:

campo uno, "campo dos", campo tres

Esto supone que está utilizando un archivo CSV simple:

Eliminar una columna

Puede deshacerse de una sola columna de muchas maneras; Usé la columna 2 como ejemplo. Probablemente sea la forma más fácil de usar cut, lo que le permite especificar un delimitador -dy qué campos desea imprimir -f; esto le dice que se divida en comas y en el campo de salida 1 y los campos 3 hasta el final:

$ cut -d, -f1,3- /path/to/your/file

Si realmente necesita usar sed, puede escribir una expresión regular que coincida con los primeros n-1campos, el ncampo th y el resto, y omitir la salida nth (aquí nes 2, por lo que el primer grupo coincide con el 1tiempo :) \{1\}:

$ sed 's/\(\([^,]\+,\)\{1\}\)[^,]\+,\(.*\)/\1\3/' /path/to/your/file

Hay varias formas de hacerlo awk, ninguna de ellas particularmente elegante. Puede usar un forbucle, pero lidiar con la coma final es un dolor; ignorando que sería algo como:

$ awk -F, '{for(i=1; i<=NF; i++) if(i != 2) printf "%s,", $i; print NL}' /path/to/your/file

Me resulta más fácil generar el campo 1 y luego usarlo substrpara sacar todo después del campo 2:

$ awk -F, '{print $1 "," substr($0, length($1)+length($2)+3)}' /path/to/your/file

Sin embargo, esto es molesto para las columnas más adelante

Duplicar una columna

En sedesto es en esencia la misma expresión que antes, pero también se captura la columna de destino e incluir ese grupo varias veces en la sustitución:

$ sed 's/\(\([^,]\+,\)\{1\}\)\([^,]\+,\)\(.*\)/\1\3\3\4/' /path/to/your/file

En awkla forma de bucle for, sería algo como (nuevamente ignorando la coma final):

$ awk -F, '{
for(i=1; i<=NF; i++) {
    if(i == 2) printf "%s,", $i;
    printf "%s,", $i
}
print NL
}' /path/to/your/file

El substrcamino:

$ awk -F, '{print $1 "," $2 "," substr($0, length($1)+2)}' /path/to/your/file

(A tcdyl se le ocurrió un método mejor en su respuesta )

Moviendo una columna

Creo que la sedsolución se deriva naturalmente de las demás, pero comienza a ser ridículamente larga.


Esa es una respuesta cargada! +1 :)
jaypal singh

Ridículamente largo? Pah !
Gilles 'SO- deja de ser malvado'

12

awkes tu mejor apuesta awkimprime los campos por número, así que ...

awk 'BEGIN { FS=","; OFS=","; } {print $1,$2,$3}' file

Para eliminar una columna, no imprimirla:

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

Para cambiar el orden:

awk 'BEGIN { FS=","; OFS=","; } {print $3,$1,$2}' file

Redireccionar a un archivo de salida.

awk 'BEGIN { FS=","; OFS=","; } {print $3,$1,$2}' file > output.file

awk puede formatear la salida también.

Awk formato de salida


Como es CSV, también lo necesitarás BEGIN { FS=","; OFS=","; }.

1
Creo que incluso FS = OFS = "," funcionará.

5

Dado un archivo delimitado por espacios en el siguiente formato:

1 2 3 4 5

Puede eliminar el campo 2 con awk así:

awk '{ sub($2,""); print}' file

que vuelve

1  3 4 5

Reemplace la columna 2 con la columna n donde sea apropiado.

Para duplicar la columna 2,

awk '{ col = $2 " " $2; $2 = col; print }' file

que vuelve

1 2 2 3 4 5

Para cambiar las columnas 2 y 3,

awk '{temp = $2; $2 = $3; $3 = temp; print}'

que vuelve

1 3 2 4 5

awk es generalmente muy bueno para tratar el concepto de campos . Si está tratando con un archivo CSV, y no con un archivo delimitado por espacios, simplemente puede usar

awk -F,

para definir su campo como una coma, en lugar de un espacio (que es el valor predeterminado). Hay una serie de buenos recursos awk en línea, uno de los cuales enumero como fuente a continuación.

Fuente para # 3


No sé mucho al respecto awk, pero parece que está separado por espacios, incluso si el separador de campo está ,(el separador de campo solo controla cómo maneja la entrada)
Michael Mrozek

@MichaelMrozek: sí, es la variable OFS awk que controla el separador de campo de salida.
enzotib

Sí, y como mencioné en mi respuesta, puede pasar la opción -F a awk para cambiar el delimitador (por ejemplo, -F,)
tcdyl

0

Esto funcionará para eliminar

awk '{$2="";$0=$0;$1=$1}1'

Entrada

a b c d

Salida

a c d
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.