Grep um arquivo no campo específico

6

Eu tenho dois arquivos, digamos

Arquivo1:

Locus_1
Locus_2
Locus_3

Arquivo2:

3  3  Locus_1  Locus_40  etc_849    
3  2  Locus_2  Locus_94  *    
2  2  Locus_6  Locus_1  *    
2  3  Locus_3,Locus_4  Locus_50  *    
3  3  Locus_9  Locus_3  etc_667

Eu quero fazer um grep -F para o primeiro arquivo somente na terceira coluna do segundo arquivo (no original File2 campos são separados por tabulações), como a saída deve seja:

Saída:

3  3  Locus_1  Locus_40  etc_849    
3  2  Locus_2  Locus_94  *    
2  3  Locus_3,Locus_4  Locus_50  *

Como posso fazer isso?

Editar Para o Caos: não, a vírgula não é um erro. Eu posso ter mais de um Locus_ * em uma coluna - e no caso do segundo Locus_ * (aquele após a vírgula) coincide com uma das linhas de File1 eu quero que ele seja recuperado também!

    
por LinuxBlanket 08.07.2015 / 12:33

6 respostas

5

Se grep não for necessário, uma solução simples seria usar join para isso:

$ join -1 1 -2 3 <(sort file1) <(sort -k3 file2)
Locus_1 3 3 Locus_40 etc_849
Locus_2 3 2 Locus_94 *
Locus_3 2 3 Locus_4 Locus_50 *

Explicação :

  • join -1 1 -2 3 : junte os dois arquivos onde no primeiro arquivo o primeiro (e único) campo é usado e no segundo arquivo o terceiro campo. Eles são impressos quando são iguais.
  • <(sort file1) : join precisa de entrada classificada
  • <(sort -k3 file2) : a entrada deve ser classificada no campo de junção (terceiro campo aqui)
por 08.07.2015 / 12:49
5

Adaptando uma solução do link você pode usar o (g) awk para obter:

awk 'NR==FNR{a[$0]=1;next} {for(i in a){if($3~i){print;break}}}' File1 File2

que fornece a saída dada.

Embora você possa criar um RegEx para alimentar o grep para satisfazer apenas a correspondência na terceira coluna, sinto que usar o awk nesse ponto é mais compreensível.

a parte if($3~i){print;break} cuida da impressão apenas se a terceira coluna corresponder a uma linha do Arquivo1 (que é armazenada na matriz a). Veja o post relacionado para uma explicação do resto.

Esteja ciente de que lê todo o conteúdo de File1 na memória, no entanto, isso só deve ser uma preocupação se for grande, caso em que você desejaria otimizar de qualquer maneira, devido à natureza multiplicativa da comparação.

    
por 08.07.2015 / 13:01
3

A opção grep -F pesquisa literal strings em qualquer lugar na linha atual. Por definição, literal significa que você não pode usar expressões regulares para restringir sua busca para estar dentro do campo 3 (delimitado por TAB) .

Você pode, no entanto, usar o grep -f para ler a entrada do padrão arquivo1 - mas é necessário modificá-lo em uma lista de expressões regulares. Aqui está uma maneira de usar o bash process substitution e sed para gerar uma lista de expressões regulares padrão que grep -f pode manipular.

Usando o grep com Expressões regulares básicas :

  grep -f <(sed 's/.*/^\([^\t]\+\t\)\{2\}\([^\t]\+,\)*&[,\t]/'  file1) file2

Para o regex básico do grep, file1 é dinamicamente convertido para:

^\([^   ]\+ \)\{2\}\([^ ]\+,\)*Locus_1[,    ]
^\([^   ]\+ \)\{2\}\([^ ]\+,\)*Locus_2[,    ]
^\([^   ]\+ \)\{2\}\([^ ]\+,\)*Locus_3[,    ]

OR : Usar grep -E com Expressões regulares estendidas simplifica visualmente o código, evitando a necessidade da maioria das contrabarras em grep e sed

grep -Ef <(sed 's/.*/^([^\t]+\t){2}([^\t]+,)*&[,\t]/' file1) file2

Para o regex estendido do grep, file1 é dinamicamente convertido para:

^([^    ]+  ){2}([^ ]+,)*Locus_1[,  ]
^([^    ]+  ){2}([^ ]+,)*Locus_2[,  ]
^([^    ]+  ){2}([^ ]+,)*Locus_3[,  ]

A saída (em ambos os casos) :

3   3   Locus_1 Locus_40    etc_849     
3   2   Locus_2 Locus_94    *       
2   3   Locus_3,Locus_4 Locus_50    *       

Tenha em atenção que -f e -F podem abrandar drasticamente as coisas enquanto file1 é grande

    
por 08.07.2015 / 16:07
2

A grep -P solution:

regexp=$( echo -n '('; < File1 tr '\n' '|' | sed 's/|$//'; echo ')' )
grep -P "^[^\s]+\s+[^s]+\s+([^\s]*,)*$regexp" File2

Saída:

3  3  Locus_1  Locus_40  etc_849    
3  2  Locus_2  Locus_94  *    
2  3  Locus_3,Locus_4  Locus_50  *    

Se o seu File1 puder conter caracteres especiais de expressão regular, você precisará escapar deles:

regexp_escape() { ruby -pe '$_ = Regexp.escape($_.chomp("\n")) + "\n"'; }

regexp=$( echo -n '('; < File1 regexp_escape  |  tr '\n' '|' | sed 's/|$//'; echo ')' )
grep -P "^[^\s]+\s+[^s]+\s+([^\s]*,)*$regexp" File2

Explicação:

A segunda linha cria strings como: %código%. e

(Locus_1|Locus_2|Locus_3)

significa:

  • "^[^\s]+\s+[^s]+\s+([^\s]*,)*"
por 08.07.2015 / 13:45
2
(   t=$(printf \t) ntt=[^$t]*$t ntc=[^$t,]*
### ^just makes it easy regardless of your sed version.
    sed  -ne"s/..*/^($ntt){2}($ntc,)*&(,$ntc)*$t/p" |
    grep -Ef- ./File2
)   <File1
3   3   Locus_1 Locus_40    etc_849
3   2   Locus_2 Locus_94    *
2   3   Locus_3,Locus_4 Locus_50    *

Isso obterá uma correspondência para uma linha no Arquivo1 na terceira coluna do Arquivo2, independentemente de quantos grupos ($ntc,)* precedem ou grupos (,$ntc)* o sigam. Isso depende, no entanto, em não haver metacaracteres nas seqüências de pesquisa no arquivo1. Se pode haver metachars em File1, então temos que limpá-lo primeiro:

(   t=$(printf \t) ntt=[^$t]*$t ntc=[^$t,]*
    sed  -ne's/[]?{(^$|*.+)}\[]/\&/g' \
          -e"s/..*/^($ntt){2}($ntc,)*&(,$ntc)*$t/p" |
    grep -Ef- ./File2
)   <File1
    
por 08.07.2015 / 17:17
1

Para colunas "grep" awk é a ferramenta de escolha

BEGIN { f="Locus_2" }
$3==f { print $0; }

para que você possa percorrer o Arquivo1

for x in 'cat File1'
do awk -v X="$x" '$3~X { print $0 }' <File2
done

.

    
por 08.07.2015 / 12:50