Mantenha apenas as linhas que contêm o número exato de delimitadores

9

Eu tenho um enorme arquivo csv com 10 campos separados por vírgulas. Infelizmente, algumas linhas estão incorretas e não contêm exatamente 10 vírgulas (o que causa alguns problemas quando quero ler o arquivo em R). Como posso filtrar apenas as linhas que contêm exatamente 10 vírgulas?

    
por Miroslav Sabo 13.01.2016 / 09:53

7 respostas

19

Outro POSIX 1:

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

Se a linha tiver 10 vírgulas, haverá 11 campos nessa linha. Então, nós simplesmente criamos awk use , como o delimitador de campo. Se o número de campos for 11, a condição NF == 11 é verdadeira, awk executará a ação padrão print $0 .

    
por 13.01.2016 / 10:14
8

Usando egrep (ou grep -E no POSIX):

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

Isso filtra qualquer coisa que não contenha 10 vírgulas: corresponde a linhas completas ( ^ no início e $ no final), contendo exatamente dez repetições ( {10} ) da sequência "qualquer número de caracteres, exceto ',', seguido por um único ',' "( ([^,]*,) ), seguido novamente por qualquer número de caracteres, exceto ',' ( [^,]* ).

Você também pode usar o parâmetro -x para soltar as âncoras:

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

Isso é menos eficiente do que a solução cuonglm awk ; o último é tipicamente seis vezes mais rápido no meu sistema para linhas com cerca de 10 vírgulas. Linhas mais longas causam grandes lentidões.

    
por 13.01.2016 / 09:59
5

O código grep mais simples que funcionará:

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

Explicação:

-x garante que o padrão deve corresponder à linha inteira , em vez de apenas parte dela. Isso é importante para que você não corresponda a linhas com mais de 10 vírgulas.

-E significa "regex estendido", o que reduz o escape de contrabarra na sua regex.

Os parênteses são usados para agrupamento, e o {10} significa que deve haver exatamente dez correspondências em uma linha do padrão dentro das parênteses.

[^,] é uma classe de caractere - por exemplo, [c-f] corresponderia a qualquer caractere único que fosse c , d , e ou f e [^A-Z] correspondessem qualquer caractere único que NÃO seja uma letra maiúscula. Portanto, [^,] corresponde a qualquer caractere único, exceto uma vírgula.

O * após a classe de caracteres significa "zero ou mais desses".

Portanto, a parte regex ([^,]*,) significa "Qualquer caractere, exceto uma vírgula qualquer número de vezes (incluindo zero vezes), seguido por uma vírgula" e o {10} especifica 10 desses. Então, [^,]* para corresponder ao resto dos caracteres não-vírgula até o final da linha.

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

O primeiro ramifica qualquer linha com 11 ou mais vírgulas e, em seguida, imprime o que resta apenas aquelas que correspondem a 10 vírgulas.

Aparentemente, eu respondi a isso antes ... Aqui está um eu-plágio de uma pergunta que procura exatamente 4 ocorrências de algum padrão:

You can target [num]th occurrence of a pattern with a sed s///ubstitution command by just adding the [num] to the command. When you test for a successful substitution and don't specify a target :label, the test branches out of the script. This means all you have to do is test for s///5 or more commas, then print what remains.

Or, at least, that handles the lines which exceed your maximum of 4. Apparently you also have a minimum requirement. Luckily, that is just as simple:

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

...just replace the 4th occurrence of , on a line with itself and tack your print on to the s///ubstitution flags. Because any lines matching , 5 or more times have already been pruned, the lines containing 4 , matches contain only 4.

    
por 13.01.2016 / 10:37
4

Arremessando alguns python :

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

Isto irá ler cada linha e verificar se o número de vírgulas na linha é igual a 10 line.count(',') == 10 , se for o caso, imprima a linha.

    
por 13.01.2016 / 09:57
2

E aqui está uma maneira de Perl:

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

O -n faz com que perl leia seu arquivo de entrada linha por linha e execute o script fornecido por -e em cada linha. O -a ativa a divisão automática: cada linha de entrada será dividida no valor dado por -F (aqui, uma vírgula) e salva como a matriz @F .

O $#F (ou, mais geralmente $#array ), é o maior índice da matriz @F . Como os arrays começam em 0 , uma linha com 11 campos terá um @F de 10 . O script, portanto, imprime a linha se tiver exatamente 11 campos.

    
por 13.01.2016 / 18:32
1

Se os campos puderem conter vírgulas ou novas linhas, seu código precisará entender csv. Exemplo (com três colunas):

$ 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"

Suponho que a maioria das soluções até agora descartaria a segunda e a quarta linha.

    
por 14.01.2016 / 15:21

Tags