Remover linhas do arquivo delimitado por tabulação com valores ausentes

5

Eu tenho um arquivo de texto grande (~ 900MB) delimitado por tabulação que eu processarei em um programa downstream. Eu preciso excluir qualquer linha com um valor ausente. O número correto de colunas está em cada linha (portanto, um valor ausente corresponderia a duas guias).

Observação: meus dados reais têm ~ 2 milhões de linhas e 80-300 colunas. Os caracteres possíveis são a-z A-Z 0-9 - (hífen) _ (sublinhado) e tabulação (delimitado). Nenhum espaço ou caractere especial está no arquivo.

Eu sou novo nesse tipo de script, então uma explicação de qualquer código fornecido seria apreciada. Eu normalmente uso R, mas meus filesizes superaram a funcionalidade de manipulação de dados do R.

Como posso no terminal (ou dentro de um shell script) excluir linhas com valores ausentes de um arquivo (por exemplo, usando sed )?

Exemplo de arquivo de entrada:

Col1    Col2    Col3
A        B        C
D                 F
G        H        I
J        K        

Exemplo de arquivo de saída:

Col1    Col2    Col3
A        B        C
G        H        I 
    
por Gaius Augustus 22.12.2015 / 07:07

7 respostas

4

Se os campos nunca puderem conter espaço em branco, um campo vazio significa uma guia como primeiro caractere ( ^\t ), uma guia como o último caractere ( \t$ ) ou duas guias consecutivas ( \t\t ). Você poderia, portanto, filtrar as linhas contendo qualquer uma delas:

grep -Ev $'^\t|\t\t|\t$' file

Se você pode ter espaço em branco, as coisas ficam mais complexas. Se seus campos puderem começar com espaços, use isso (considera um campo com apenas espaços vazios):

grep -Pv '\t\s*(\t|$)|\t$|^\t' file

A alteração filtra as linhas que correspondem a uma guia, seguida de 0 ou mais espaços e, em seguida, outra guia ou o final da linha.

Isso também falhará se o último campo contiver apenas espaços. Para evitar isso também, use perl com as opções -F e -a para dividir a entrada na matriz @F , informando para imprimir a menos que um dos campos esteja vazio ( /^$/ ):

perl -F'\t' -lane 'print unless grep{/^$/} @F' file
    
por 22.12.2015 / 11:53
8

com awk :

awk -F"\t" '$1!=""&&$2!=""&&$3!=""' file

Na verdade, é tão simples.

  • awk divide a entrada na guia do separador de campos \t especificado com o sinalizador -F . Isso também pode ser omitido quando o conteúdo não tiver espaços nos campos.
  • $1!=""&&... é uma condição. Quando essa condição é verdadeira, awk simplesmente imprime a linha. Você também pode escrever '$1!=""&&$2!=""&&$3!=""{print}' , mas isso não é necessário. O comportamento padrão dos awks é imprimir a linha quando nenhuma ação é dada. Aqui, essa condição é verdadeira quando os campos $1 , $2 e $3 all não estão vazios, portanto, quando os 3 primeiros campos tiverem um valor.

Para escrever em outro arquivo use isto:

awk -F"\t" '$1!=""&&$2!=""&&$3!=""' input_file >output_file

Editar : com um número indefinido de colunas, você pode usar este awk , verificar todos os campos na linha:

awk -F"\t" '{for(i=1;i<=NF;i++){if($i==""){next}}}1' file
    
por 22.12.2015 / 09:47
5

... para qualquer um dos itens abaixo, você deve primeiro fazer ...

t=$(printf \t)          ### because it's hard to demo CTRL+V TAB 

... agora, com um POSIX grep ...

grep -Ev "^$t+|$t($t|$)"     <in >out

grep selecionará as linhas que não corresponderem ao padrão - que usa o metacaractere | ou para indicar uma guia ^ de cabeçalho ou duas guias consecutivas ou uma aba de fim de linha $ - que são os únicos casos de falha possíveis o mais próximo que eu posso dizer.

sem o interruptor% negação -v , pode ser:

grep -E "([^$t]+$t){2}[^$t]" <in >out

... que especifica uma contagem de ocorrências { } para o grupo de padrões ( ) de + um ou mais caracteres na [ classe ] de caracteres que são ^ de separadores não seguidos por um separador.

... ou com um POSIX sed ...

sed -ne"s/[^$t][^$t]*/&/3p"  <in >out

... ou ...

sed -ne"s/[^$t]\{1,\}/&/3p"  <in >out

... ou w / GNU ou BSD sed s ...

sed -Ene"s/[^$t]+/&/3p"      <in >out

... onde sed faz -n ot imprimir por padrão qualquer linha, a menos que possa s/// ubstitute para & a terceira ocorrência em uma linha da maior sequência possível de pelo menos um [^ não tab ] character.

(as guias literais devem ser preferidas para portabilidade. a versão original dessa resposta usou \ backslash e não foi não útil. definitivamente usando \ escapes invertidos em um [ classe de caractere ] limitará a aplicabilidade do seu código.)

    
por 22.12.2015 / 09:50
1
awk 'NF==3' file

Imprime uma linha se o número de campos for igual a 3. É bastante simples alterar o número de colunas de acordo com seus dados.

No entanto, como apontado, isso não funciona com o requisito de número variável de campos do OP.

    
por 23.12.2015 / 07:37
0

Você pode tentar algo assim:

grep "^[a-zA-Z0-9]\+[[:space:]][a-zA-Z0-9]\+[[:space:]][a-zA-Z0-9]\+$" input_file > output_file

O propósito de grep é (ou não) encontrar strings em um ou mais arquivos que correspondam a um determinado padrão. Aqui, o padrão [a-zA-Z0-9]\+ corresponde a um ou mais caracteres alfanuméricos, que é seguido por um espaço em branco ou tabulação. O início de uma linha é correspondido por ^ , enquanto $ indica o fim da linha. Se outros caracteres forem usados nas colunas, eles devem ser adicionados à classe de caracteres acima. Finalmente, > redireciona a saída correspondente para o arquivo de saída.

Por favor olhe também para o comentário do @Terdon abaixo para potenciais armadilhas e uma solução alternativa. Observe que, se você estiver trabalhando em ambientes Linux / Unix, a utilidade de grep vai muito além dessa solução específica.

    
por 22.12.2015 / 08:58
0

Eu tenho uma maneira mais genérica de fazer essa tarefa

<$your_file perl -CASD -ne 'print if not grep { /^$/ } split "\t"'

@terdon: Você está certo, agora funciona como esperado.

    
por 22.12.2015 / 09:34
-1

Esse é um perl que pode ser feito de maneira bem clara:

perl -lane 'print if @F==3';

autosplits na matriz @F e se o número de campos for 3, será impresso.

Editar: você pode configurar automaticamente o número do campo:

perl -lane '$cols //= @F; print if @F == $cols'

Isso definirá a variável $cols como o número de colunas na primeira linha (que, no seu exemplo, é a linha de cabeçalho).

Observação - ele não funciona com campos espaçados em branco (outros exemplos acima são mais apropriados para isso. Algo parecido com usar -F"\t" para definir o delimitador e grep para filtrar os campos vazios)

Nota //= é uma atribuição condicional - atribui se nenhum valor está atualmente definido. É também um recurso perl mais recente, e estou ciente de que há algumas versões perl bem antigas em circulação. (5.8.8 aparece com bastante frequência).

Se isso não funcionar na sua versão, ||= deve fazer o truque bem - também é uma atribuição condicional, que testa a "verdade". Qual deve funcionar bem com seu conjunto de dados. (Ele lida com '0' e '' diferentemente de // , mas esses não devem aparecer).

    
por 22.12.2015 / 20:11