Grep un file en un campo específico

Tengo dos files, digamos

Archivo1:

Locus_1 Locus_2 Locus_3 

File2:

 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 

Quiero hacer un grep -F para el primer file solo en la tercera columna del segundo file (en los campos originales del File2 están separados por tabs), tal como el resultado debe ser:

Salida:

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

¿Cómo puedo hacerlo?

Edit To Chaos: no, la coma no es un error. Puedo tener más de un Locus_ * en una columna, y en caso de que el segundo Locus_ * (el que está detrás de la coma) coincida con una de las líneas de File1 ¡también quiero que se recupere!

Si grep no es necesario, una solución simple sería usar join para eso:

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

Explicación

  • join -1 1 -2 3 : une los dos files donde en el primer file se usa el primer (y único) campo y en el segundo file el tercer campo. Se imprimen cuando son iguales.
  • <(sort file1) : join input orderada
  • <(sort -k3 file2) : la input debe orderarse en el campo de combinación (tercer campo aquí)

Al adaptar una solución desde https://stackoverflow.com/a/9937241/1673337 , puede usar (g) awk para get:

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

que proporciona la Salida dada.

Si bien se puede crear un RegEx para alimentar grep para satisfacer solo las coincidencias en la tercera columna, siento que usar awk en este punto es más comprensible.

la parte if($3~i){print;break} se encarga de imprimir solo si la tercera columna coincide con una línea de File1 (que se almacena en la matriz a). Vea la publicación vinculada para una explicación del rest.

Tenga en count que esto lee todo el contenido de File1 en la memory, sin embargo, esto solo debería ser una preocupación si es grande, en cuyo caso desea optimizar de todos modos, debido a la naturaleza multiplicativa de la comparación.

El uso de la opción grep -F busca cadenas literales en cualquier lugar de la línea actual. Por definición, literal significa que no puede usar expresiones regulares para limitar su búsqueda para que esté dentro del campo 3 (delimitado por TAB) .

Sin embargo, puede usar grep -f para leer su file de input de patrón1 – pero necesita modificarlo en una list de expresiones regulares. Aquí hay una forma de usar la sustitución de processs bash y sed para generar una list de expresiones regulares regulares que grep -f puede manejar.

Usando grep con Basic Regular Expressions :

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

Para la expresión regular básica de grep, el file file1 se convierte dinámicamente en:

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

O : El uso de grep -E con Extended Regular Expressions simplifica visualmente el código al evitar la necesidad de la mayoría de las barras invertidas en grep y sed

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

Para la expresión regular extendida de grep, el file file1 se convierte dinámicamente en:

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

La salida (en ambos casos) :

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

Tenga en count que -f y -F pueden networkingucir drásticamente las cosas mientras que el file1 es grande

Una solución grep -P :

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

Salida:

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

Si su File1 puede contener caracteres especiales de expresiones regulares, deberá escaping de ellos:

 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 

Explicación:

La segunda línea crea cadenas como: (Locus_1|Locus_2|Locus_3) . y

"^[^\s]+\s+[^s]+\s+([^\s]*,)*"

medio:

  • [word] [whitespace(s)] [word] [whitespace(s)] [(optional word followed by comma) zero or arbitrarily many times]
 ( 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 * 

Eso obtendrá una coincidencia para una línea en el Archivo 1 en la tercera columna de File2 independientemente de cuántos grupos ($ntc,)* preceden o (,$ntc)* siguen. Sin embargo, depende de que no haya metacaracteres en las cadenas de búsqueda en File1. Si puede haber metacars en File1, entonces tenemos que limpiarlo, primero:

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

Para "grep" columnas awk es la herramienta de elección

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

para que pueda recorrer el file 1

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

.