Mantenga solo las líneas que contienen el número exacto de delimitadores


9

Tengo un gran archivo csv con 10 campos separados por comas. Desafortunadamente, algunas líneas están mal formadas y no contienen exactamente 10 comas (lo que causa algunos problemas cuando quiero leer el archivo en R). ¿Cómo puedo filtrar solo las líneas que contienen exactamente 10 comas?


1
su pregunta y la pregunta vinculada no son la misma pregunta. pregunta cómo manejar líneas con no más o menos que un cierto número de coincidencias, mientras que esa pregunta requiere solo un recuento mínimo de coincidencias. la realidad es que la pregunta se responde con mayor facilidad: no requiere escanear una línea completa, o (al menos, como lo sedhace aquí) solo hasta una coincidencia más de la que se busca, aunque esta pregunta sí. No deberías haber cerrado esto.
mikeserv

1
en realidad, mirando más de cerca, el que pregunta no quiere más ni menos que partidos. esa pregunta necesita un nuevo título. pero la greprespuesta no es una respuesta aceptable para ninguna de las preguntas ...
mikeserv

Respuestas:


21

Otro POSIX:

awk -F , 'NF == 11' <file

Si la línea tiene 10 comas, habrá 11 campos en esta línea. Así que simplemente hacemos awkuso ,como delimitador de campo. Si el número de campos es 11, la condición NF == 11es verdadera, awkluego realiza la acción predeterminada print $0.


55
De hecho, eso fue lo primero que me vino a la mente sobre esta pregunta. Pensé que era excesivo, pero mirando el código ... seguro que está más claro. Para el beneficio de otros: -Festablece el separador de campo y se NFrefiere al número de campos en una línea dada. Como no {statement}se agrega ningún bloque de código a la condición NF == 11, la acción predeterminada es imprimir la línea. (@cuonglm, siéntase libre de incorporar esta explicación si lo desea.)
Comodín el

44
+1: solución muy elegante y legible que también es muy general. Por ejemplo, puedo encontrar todas las líneas mal formadas conawk -F , 'NF != 11' <file
Miroslav Sabo

@gardenhead: Es fácil obtenerlo, como puede ver el OP en su comentario. A veces respondo desde mi teléfono móvil, por lo que es difícil agregar la explicación detallada.
Cuonglm

1
@mikeserv: No, lo siento si te confundí, es solo mi mal inglés. No puede tener 11 campos con 1-9 comas.
Cuonglm

1
@ OlivierDulac: Te protege contra el inicio de archivos con -o con nombre -.
Cuonglm

8

Usando egrep(o grep -Een POSIX):

egrep "^([^,]*,){10}[^,]*$" file.csv

Esto filtra cualquier cosa que no contenga 10 comas: coincide con líneas completas ( ^al principio y $al final), que contiene exactamente diez repeticiones ( {10}) de la secuencia "cualquier número de caracteres excepto ',', seguido de un solo ','" ( ([^,]*,)), seguido de nuevo por cualquier número de caracteres excepto ',' ( [^,]*).

También puede usar el -xparámetro para soltar los anclajes:

grep -xE "([^,]*,){10}[^,]*" file.csv

Esto es menos eficiente que cuonglm 's awksolución sin embargo; este último es típicamente seis veces más rápido en mi sistema para líneas con alrededor de 10 comas. Las líneas más largas causarán grandes ralentizaciones.


5

El grepcódigo más simple que funcionará:

grep -xE '([^,]*,){10}[^,]*'

Explicación:

-xasegura que el patrón debe coincidir con la línea completa , en lugar de solo una parte de ella. Esto es importante para que no coincidan las líneas con más de 10 comas.

-E significa "expresión regular extendida", lo que hace que se reduzca la barra invertida en su expresión regular.

Los paréntesis se usan para agrupar, y {10}luego significa que debe haber exactamente diez coincidencias en una fila del patrón dentro de las paréntesis.

[^,]es una clase de caracteres; por ejemplo, [c-f]coincidiría con cualquier carácter único que sea a c, a d, an eo an f, y [^A-Z]coincidiría con cualquier carácter único que NO sea una letra mayúscula. Por lo tanto, [^,]coincide con cualquier carácter, excepto una coma.

El *después de que los medios de clase de caracteres "cero o más de estos."

Entonces, la parte regex ([^,]*,)significa "Cualquier carácter excepto una coma cualquier número de veces (incluyendo cero veces), seguido de una coma" y {10}especifica 10 de estos. Luego, [^,]*para que coincida con el resto de los caracteres que no son comas al final de la línea.


5
sed -ne's/,//11;t' -e's/,/&/10p' <in >out

Eso primero ramifica cualquier línea con 11 o más comas, y luego imprime lo que queda solo aquellas que coinciden con 10 comas.

Aparentemente respondí esto antes ... Aquí hay un plagio de de una pregunta que busca exactamente 4 ocurrencias de algún patrón:

Puede orientar [num]la aparición de un patrón con un s///comando de sustitución de sed simplemente agregando [num]el comando. Cuando testá buscando una sustitución exitosa y no especifica una :etiqueta de destino , el test se ramifica fuera del guión. Esto significa que todo lo que tiene que hacer es probar s///5o más comas, luego imprimir lo que queda.

O, al menos, que maneja las líneas que exceden su máximo de 4. Aparentemente, también tiene un requisito mínimo. Afortunadamente, eso es igual de simple:

sed -ne 's|,||5;t' -e 's||,|4p'

... solo reemplace la cuarta aparición de ,en una línea consigo mismo y agregue psu s///pista a las banderas de sustitución. Debido a que las líneas que coinciden ,5 o más veces ya se han eliminado, las líneas que contienen 4 ,coincidencias contienen solo 4.


1
@cuonglm: eso es lo que tenía en realidad al principio, pero la gente siempre me dice que debería escribir un código más legible. ya que puedo leer las cosas que otros discuten como ilegibles, no estoy seguro de qué guardar y qué dejar ... Así que puse la segunda coma.
mikeserv

@cuonglm, puedes burlarte de mí, no herirá mis sentimientos. Puedo tomar un chiste. Si te estuvieras burlando de mí, sería un poco divertido. está bien, simplemente no estaba seguro y quería saberlo. en mi opinión, las personas deberían poder reírse de sí mismas. de todos modos, todavía no lo entiendo!
mikeserv

Jaja, claro, es un pensamiento muy positivo. De todos modos, es muy divertido conversar contigo y, a veces, estresas mi cerebro.
Cuonglm

Es interesante que en esta respuesta , si lo reemplazo s/hello/world/2con s//world/2, GNU funciona bien. Con dos sedde reliquia, /usr/5bin/posix/sedeleva segfault, /usr/5bin/sedentra en bucle infinitivo.
Cuonglm

@mikeserv, en referencia a nuestra discusión anterior sobre sedyawk (en comentarios): me gusta esta respuesta y la voté, pero observe que la traducción de la awkrespuesta aceptada es: "Imprimir líneas con 11 campos" y la traducción de esta sedrespuesta es: " Intenta eliminar la undécima coma; salta a la siguiente línea si fallas. Intenta reemplazar la décima coma por sí misma; imprime la línea si tienes éxito ". La awkrespuesta da las instrucciones a la computadora tal como las expresarías en inglés. ( awkes bueno para datos basados ​​en campo).
Comodín el

4

Lanzando algo corto python:

#!/usr/bin/env python2
with open('file.csv') as f:
    print '\n'.join(line for line in f if line.count(',') == 10)

Esto leerá cada línea y verificará si el número de comas en la línea es igual a 10 line.count(',') == 10, si es así, imprima la línea.


2

Y aquí hay una manera de Perl:

perl -F, -ane 'print if $#F==10'

Las -ncausas perlpara leer su archivo de entrada línea por línea y ejecutar el script dado por -een cada línea. Se -aactiva la división automática: cada línea de entrada se dividirá en el valor dado por -F(aquí, una coma) y se guardará como la matriz @F.

El $#F(o, más generalmente $#array), es el índice más alto de la matriz @F. Como las matrices comienzan en 0, una línea con 11 campos tendrá un @Fde 10. El script, por lo tanto, imprime la línea si tiene exactamente 11 campos.


También puede hacer print if @F==11que una matriz en un contexto escalar devuelva el número de elementos.
Sobrique

1

Si los campos pueden contener comas o líneas nuevas, su código debe comprender csv. Ejemplo (con tres columnas):

$ cat filter.csv
a,b,c
d,"e,f",g
1,2,3,4
one,two,"three
...continued"

$ cat filter.csv | python3 -c 'import sys, csv
> csv.writer(sys.stdout).writerows(
> row for row in csv.reader(sys.stdin) if len(row) == 3)
> '
a,b,c
d,"e,f",g
one,two,"three
...continued"

Supongo que la mayoría de las soluciones hasta ahora descartarían la segunda y cuarta fila.

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.