1.11: Patrones (Expresiones Regulares)
- Page ID
- 55196
\( \newcommand{\vecs}[1]{\overset { \scriptstyle \rightharpoonup} {\mathbf{#1}} } \)
\( \newcommand{\vecd}[1]{\overset{-\!-\!\rightharpoonup}{\vphantom{a}\smash {#1}}} \)
\( \newcommand{\id}{\mathrm{id}}\) \( \newcommand{\Span}{\mathrm{span}}\)
( \newcommand{\kernel}{\mathrm{null}\,}\) \( \newcommand{\range}{\mathrm{range}\,}\)
\( \newcommand{\RealPart}{\mathrm{Re}}\) \( \newcommand{\ImaginaryPart}{\mathrm{Im}}\)
\( \newcommand{\Argument}{\mathrm{Arg}}\) \( \newcommand{\norm}[1]{\| #1 \|}\)
\( \newcommand{\inner}[2]{\langle #1, #2 \rangle}\)
\( \newcommand{\Span}{\mathrm{span}}\)
\( \newcommand{\id}{\mathrm{id}}\)
\( \newcommand{\Span}{\mathrm{span}}\)
\( \newcommand{\kernel}{\mathrm{null}\,}\)
\( \newcommand{\range}{\mathrm{range}\,}\)
\( \newcommand{\RealPart}{\mathrm{Re}}\)
\( \newcommand{\ImaginaryPart}{\mathrm{Im}}\)
\( \newcommand{\Argument}{\mathrm{Arg}}\)
\( \newcommand{\norm}[1]{\| #1 \|}\)
\( \newcommand{\inner}[2]{\langle #1, #2 \rangle}\)
\( \newcommand{\Span}{\mathrm{span}}\) \( \newcommand{\AA}{\unicode[.8,0]{x212B}}\)
\( \newcommand{\vectorA}[1]{\vec{#1}} % arrow\)
\( \newcommand{\vectorAt}[1]{\vec{\text{#1}}} % arrow\)
\( \newcommand{\vectorB}[1]{\overset { \scriptstyle \rightharpoonup} {\mathbf{#1}} } \)
\( \newcommand{\vectorC}[1]{\textbf{#1}} \)
\( \newcommand{\vectorD}[1]{\overrightarrow{#1}} \)
\( \newcommand{\vectorDt}[1]{\overrightarrow{\text{#1}}} \)
\( \newcommand{\vectE}[1]{\overset{-\!-\!\rightharpoonup}{\vphantom{a}\smash{\mathbf {#1}}}} \)
\( \newcommand{\vecs}[1]{\overset { \scriptstyle \rightharpoonup} {\mathbf{#1}} } \)
\( \newcommand{\vecd}[1]{\overset{-\!-\!\rightharpoonup}{\vphantom{a}\smash {#1}}} \)
En capítulos anteriores, utilizamos un sencillo programa fasta_stats
para realizar análisis básicos en un archivo FASTA llamado Pz_CDNAS.FASTA
, principalmente como excusa para aprender sobre los flujos estándar y herramientas como grep
y
ordenar. Resulta que la información en el archivo Pz_CDNAS.fasta
nos proporciona muchas preguntas potenciales para reflexionar.
Las secuencias en este archivo son en realidad un subconjunto de transcripciones putativas, producidas a partir de un ensamblaje de transcriptoma de novo para la mariposa Papilio zelicaon. Cada línea de cabecera de secuencia codifica una variedad de información: la segunda y tercera columnas de las líneas de cabecera revelan el número de lecturas que contribuyen a cada secuencia ensamblada y la cobertura promedio de la secuencia (definida como el número total de bases aportadas por lecturas, dividido por la secuencia ensamblada longitud). Incluso los ID de secuencia codifican cierta información: todos comienzan con un identificador pseudoaleatorios, pero algunos tienen un sufijo como _TY
.
Los grupos de secuencias que comparten el mismo sufijo _
se identificaron previamente como que tenían coincidencias compartidas usando un Self-blast. Los ID de secuencia sin dicho sufijo no tenían coincidencias. Podríamos preguntarnos: ¿cuántas secuencias hay en tal grupo? Esto podría responderse fácilmente usando primero grep
para extraer líneas que coincidan con >
(las líneas de encabezado), y luego usando otro grep
para extraer aquellas con el patrón _
(las de un grupo) antes de enviar el resultado a wc
.
Una pregunta más compleja sería preguntar cuántos grupos diferentes están representados en el archivo. Si la información del grupo se almacenaba en una columna separada (digamos, la segunda columna), esta pregunta podría responderse con el mismo proceso anterior, seguida de un sort -k2,2d -u
para eliminar identificadores de grupo duplicados. Pero, ¿cómo podemos coaccionar la información del grupo en su propia columna? Esto podríamos hacer sustituyendo instancias de _
por espacios. La herramienta sed
(Stream Editor) nos puede ayudar. Aquí está el pipeline general que usaremos:
Y aquí está parte de la salida, donde solo se representan las secuencias en grupos y cada grupo se representa solo una vez (reemplazando los menos -S
con wc
contaría así el número de grupos):
La herramienta sed
es un programa sofisticado para modificar la entrada (ya sea desde un archivo o entrada estándar) e imprimir los resultados a la salida estándar: sed ''
<program><file>o... | sed ''
<program>.
Al igual que awk
, sed
proviene de la década de 1970 y proporciona una gran variedad de potentes características y sintaxis, solo una pequeña fracción de las cuales cubriremos aquí. En particular, nos centraremos en la operación s
, o sustitución.
La opción -r
que hemos usado le permite saber a sed
que queremos que nuestro patrón sea especificado por la sintaxis “expresión regular extendida POSIX”. [1] El patrón general del programa de sustitución es s///g
<pattern><replacement>, donde la g
especifica que, para cada línea, cada instancia del patrón debe ser reemplazada. Alternativamente, podemos usar 1
en este spot para indicar que solo se debe reemplazar la primera instancia, 2
para indicar solo la segunda, y así sucesivamente. A menudo, <pattern><replacement>se usa s///
, ya que tiene el mismo significado que s///1
<pattern><replacement>. [2]
Expresiones Regulares
El verdadero poder de sed
no proviene de su capacidad de sustituir texto, sino de su utilidad para sustituir texto basado en “patrones” o, más formalmente, expresiones regulares. Una expresión regular es una sintaxis para describir la coincidencia de patrones en cadenas. Las expresiones regulares son descritas por los caracteres individuales que componen el patrón a buscar, y los “meta-operadores” que modifican partes del patrón para mayor flexibilidad. En [ch] at
, por ejemplo, los corchetes funcionan como un meta-operador que significa “uno de estos personajes”, y este patrón coincide con gato
y sombrero
, pero no chat
. Las expresiones regulares a menudo se construyen encadenando expresiones más pequeñas, como en [ch] en el [mh] at
, gato a juego en el sombrero
, gato en el tapete
, sombrero en el sombrero
y sombrero en el tapete
.
En el ejemplo anterior, todo el patrón fue especificado por _
, que no es un meta-operador de ningún tipo, por lo que cada instancia de _
fue reemplazada por el reemplazo (un carácter de espacio). Los meta-operadores que son compatibles con expresiones regulares son muchos y variados, pero aquí hay una lista básica junto con algunos ejemplos biológicamente inspirados:
- caracteres o cadenas que no son meta-operadores
- La mayoría de los personajes que no operan de una metamoda son simplemente emparejados. Por ejemplo,
_
coincide con_
,A
coincide conA
yATG
coincide con un codón de inicio. (De hecho,ATG
son tres patrones individuales especificados en una fila). En caso de duda, suele ser seguro escapar de un personaje (prefijándolo con una diagonal inversa) para asegurarse de que se interprete literalmente. Por ejemplo,\[_\]
coincide con la cadena literal[_]
, en lugar de hacer uso de los corchetes como meta-operadores.
- La mayoría de los personajes que no operan de una metamoda son simplemente emparejados. Por ejemplo,
-
.
- Un periodo coincide con cualquier carácter individual. Por ejemplo,
CC.
coincide con cualquier codón P (CCA
,CCT
,CCG
,CCC
), pero también cadenas comoCCX
yCC%
.
- Un periodo coincide con cualquier carácter individual. Por ejemplo,
-
[]
<charset>- Coincide con cualquier carácter individual especificado en
<charset>. Por ejemplo,
TA [CT]
coincide con un codón Y (TAC o
).TAT
- Coincide con cualquier carácter individual especificado en
-
[^]
<charset>- Colocar un
^
como el primer carácter dentro de los corchetes del juego de caracteres niega el significado, de tal manera que cualquier carácter único que no esté nombrado entre corchetes coincide.TA [^CT]
coincide conTAT
,TAG
,TA%
, y así sucesivamente, pero noTAC o
.TAT
- Colocar un
^
(fuera de[]
)- Colocar un
^
fuera de los corchetes del juego de caracteres coincide con el inicio de la cadena o línea de entrada. Usandosed -r 's/^atg/xxx/G'
, por ejemplo, reemplaza todas las instancias de codones de inicio porXXX
, pero solo si existen al inicio de la línea.
- Colocar un
-
$
- Similar a
^
, pero$
coincide con el final de la cadena o línea. Entonces,sed -r 's/ATG$/xxx/G'
reemplaza a todos los codones de inicio que existen al final de sus respectivas líneas.
- Similar a
Hasta ahora nuestros patrones no son realmente tan flexibles, porque la mayoría de las piezas cubiertas hasta este punto coinciden con un solo personaje. Los siguientes cinco metaoperadores resuelven esa limitación.
-
{x, y}
- Modifica el patrón anterior para que coincida si ocurre entre
x
ey
veces seguidas, inclusive. Por ejemplo,[GC] {4,8}
coincide con cualquier cadena de C y/o G que tenga una longitud de cuatro a ocho caracteres (disparando para ocho personajes, si es posible). Entonces,sed -r/[GC] {4,8} /_x_/g'
resultaría en las siguientes sustituciones:ATCCGTCT
aATCCGTCT
(sin reemplazo)
ATCCGCGGCTC
aAT_X_TC
ATCGCGGCCCGTTCGGGCCT
aAT_X_CCGTT_X_T
- Usar
{0,1}
tiene el efecto de hacer que lo que sigue sea opcional en el patrón, y{x,}
tiene el efecto de permitir que el patrón coincidax
o más veces sin límite superior.
- Modifica el patrón anterior para que coincida si ocurre entre
-
*
- Un asterisco modifica el patrón anterior para que coincida si ocurre cero o más veces; así es equivalente a
{0,}
.El uso de
*
merece un ejemplo detallado. Considere el patrónATG [ATGC] *TGA
, dondeATG
es el patrón para un codón de inicio,[ATGC] *
indica cero o más bases de ADN en una fila, yTGA
es uno de los codones de parada canónicos. Este patrón coincide conATGTACCTTGA
, y también coincide conATGTGA
(donde la parte media se ha emparejado cero veces).
- Un asterisco modifica el patrón anterior para que coincida si ocurre cero o más veces; así es equivalente a
-
+
- El modificador de repetición más prominente, un signo más modifica el patrón anterior para que coincida una o más veces; es equivalente a
{1,}
. En contraste con el ejemplo anterior,ATG [ATGC] +TGA
coincide conATGTACCTTGA
yATGCTGA
, pero noATGTGA
.
- El modificador de repetición más prominente, un signo más modifica el patrón anterior para que coincida una o más veces; es equivalente a
-
()
<pattern>- Los paréntesis se pueden utilizar para agrupar una expresión o serie de expresiones en una sola unidad para que puedan operarse juntas. Debido
a que AT
es el patrónA
seguido deT
, por ejemplo,AT+
coincide conAT
,
, ATTT, etc. Si quisiéramos hacer coincidir repeticionesATT
AT
, podríamos desear especificar un patrón como(AT) +
, que coincida conAT
,ATAT
,ATAT
, y así sucesivamente. Los paréntesis también “guardan” la cadena que coincidió dentro de ellos para su uso posterior. Esto se conoce como retroreferenciación, se discute a continuación.
- Los paréntesis se pueden utilizar para agrupar una expresión o serie de expresiones en una sola unidad para que puedan operarse juntas. Debido
-
|
<pattern x><pattern y>- Coincidir con el patrón
<pattern x>o el patrón
<pattern y>. Se pueden encadenar múltiples patrones u operaciones de este tipo; por ejemplo,
TAA|TAG|TGA
coincide con cualquiera de los tres codones de parada canónicos. Sin embargo, este ejemplo es un poco ambiguo: ¿este patrón lee “TA (A o T) A (G o T) GA” o “TAA o TAG o TGA”? Para hacerlo concreto, probablemente nos gustaría especificarlo como((TAA) | (TAG) | (TGA))
.
- Coincidir con el patrón
Usando estas piezas, podemos armar una expresión regular que sirve como un buscador simple (y no realmente útil en la práctica) de lectura abierta. Para las secuencias procariotas (donde los intrones no son una consideración), las definimos como un codón de inicio ATG
, seguido de uno o más codones, seguido de uno de los tres codones de parada canónicos TAA
, TAG
o TGA
. El patrón para el inicio es ATG
, y hemos visto cómo podemos codificar un stop arriba, con ((TAA) | (TAG) | (TGA))
. ¿Qué tal “uno o más codones”? Bueno, “uno o más” se encarna en el operador +
, y un codón es cualquiera de tres A, T, C o G. Entonces, “uno o más codones” se codifica como ([ACTG] {3,3}) +
. Por lo tanto, la expresión regular de nuestro sencillo buscador de marcos de lectura abierta es:
En realidad, las expresiones regulares no se utilizan a menudo en la búsqueda de regiones codificantes (aunque a veces se usan para identificar motivos más pequeños). Parte de la razón es que las expresiones regulares son, por defecto, codiciosas: coinciden con el primer patrón que ocurren que pueden, y buscan hacer coincidir tanto de la cadena como puedan. (La maquinaria celular que procesa los marcos de lectura abiertos no es codiciosa de esta manera). Considera la siguiente secuencia, la cual tiene tres marcos de lectura abiertos de acuerdo a nuestra definición simple y expresión regular anterior.
Observe que la cadena TAG
es tanto un tipo de codón en general ([ACTG] {3,3}
) como una parada, por lo que técnicamente ambas de las dos primeras opciones son válidas según la expresión regular. Por las reglas de la codicia, se emparejará el primero, lo que podemos verificar con un simple eco
y sed
.
La sintaxis de expresión regular utilizada por sed
es similar a la sintaxis utilizada en lenguajes como Perl, Python y R. De hecho, todos los ejemplos que hemos visto hasta ahora funcionarían igual en esos lenguajes (aunque son aplicados por sus propias funciones específicas en lugar de una llamada a sed
). Una característica útil proporcionada por los motores de expresión regular más modernos como estos es que los operadores como *
y +
pueden volverse no codiciosos (aunque prefiero el término más claro “reacio”) siguiéndolos con un signo de interrogación. En Python, la expresión regular ATG ([ACTG] {3,3}) +? ((TAA) | (TAG) | (TGA))
coincidiría con la segunda opción. (Cuando no sigue un *
, o +
, hace que el anterior sea opcional; así TG (T)?
CC es equivalente a TG (T) {0,1} CC
.) Las características más sofisticadas permiten al usuario acceder a todas las coincidencias de un patrón, aunque se superpongan, de manera que la más satisfactoria pueda ser sacada por algunos criterios secundarios. Desafortunadamente, sed
no admite coincidencias no codiciosas y varias otras características avanzadas de expresión regular.
Clases de caracteres y expresiones regulares en otras herramientas
A menudo deseamos usar corchetes para que coincidan con cualquiera de una “clase” de caracteres; por ejemplo, [0123456789]
coincide con cualquier dígito individual. La mayoría de las sintaxis de expresión regular (incluida la utilizada por sed
) permiten una versión abreviada [0-9]
(si quisiéramos hacer coincidir solo un 0, 9 o -, podríamos usar [09-]
). Del mismo modo, [a-z]
coincide con cualquier letra minúscula y [A-Z]
cualquier letra mayúscula. Estos incluso se pueden combinar: [A-za-Z0-9]
coincide con cualquier dígito o letra. En la sintaxis extendida POSIX utilizada por sed
, 0-9
también se puede especificar como [:digit:]
. Observe la falta de corchetes en el anterior, para que realmente coincida con cualquier dígito, la expresión regular es [[:digit:]]
(lo cual, sí, es molesto). Para que coincida con cualquier no dígito, podemos negar el conjunto entre corchetes como [^ [:digit:]]
.
Estas clases de caracteres POSIX son especialmente útiles cuando queremos hacer coincidir tipos de caracteres que son difíciles de escribir o enumerar. En particular, [[:space:]]
coincide con uno de cualquier carácter de espacio en blanco (espacios, tabulaciones, nuevas líneas), y [[:punct:]]
coincide con cualquier carácter de “puntuación”, de los cuales hay bastantes. La clase de caracteres [[:space:]]
es particularmente útil cuando está reformateando datos almacenados en filas y columnas pero no está seguro de si los separadores de columnas son espacios, tabulaciones o alguna combinación.
En muchas sintaxis de expresiones regulares (incluidas las utilizadas por Perl, Python, R y algunas versiones de sed
), hay disponibles atajos aún más cortos para clases de caracteres. En estos,\ d
equivale a [[:digit:]]
,\ D
es equivalente a [^ [:digit:]]
,\ s
para [[:space:]]
,\ S
para [^ [:space:]]
, entre otros.
Resulta que las expresiones regulares pueden ser utilizadas tanto por grep
como por awk
. Al usar grep
, podemos especificar que el patrón debe tratarse como una expresión regular extendida agregando la bandera -E
(a diferencia de la -r
utilizada para sed
.) Así grep -E '[[:digit:]] +'
extrae líneas que contienen un entero.
En awk
, podemos usar el comparador ~
en lugar del comparador ==
en una sentencia if, como en awk '{if ($1 ~ /PZ718 [[:digit:]] +/) {print $3}}'
, que imprime la tercera columna de cada línea donde la primera columna coincide con el patrón PZ718 [[:digit:]] +
.
Back-Referenciación
De acuerdo con la definición anterior para las líneas de cabecera en el archivo Pz_CDNAS.fasta
, los IDs deben caracterizarse como un identificador pseudoaleatoria seguido de, opcionalmente, un guion bajo y un conjunto de letras mayúsculas especificando el grupo. Usando grep '>'
para extraer solo las líneas de cabecera, podemos inspeccionar esto visualmente:
Si enviamos estos resultados a través de wc
, vemos que este archivo contiene 471 líneas de cabecera. ¿Cómo podemos verificar que cada uno de ellos siga este patrón? Mediante el uso de una expresión regular con grep
para el patrón, y comparando el conteo con 471. Debido a que los ID deben comenzar inmediatamente después del símbolo >
en un archivo FASTA, eso será parte de nuestro patrón. Para la sección pseudoaleatoria, que puede o no comenzar con PZ
pero al menos no debe incluir un guion bajo o un espacio, podemos usar el patrón [^_ [:space:]] +
para especificar uno o más caracteres no subrayados, no espacios en blanco. Para el identificador de grupo opcional, el patrón sería (_ [A-Z] +) {0,1}
(porque {0,1}
hace que el anterior sea opcional). Armando estos con grep -E
y contando los partidos debería producir 471.
Todos los encabezados coincidieron con el patrón que esperábamos. ¿Y si no lo hubieran hecho? Podríamos inspeccionar cuáles no usando un grep -v -E
para imprimir las líneas que no coincidieron con el patrón.
Ahora, hipotéticamente, supongamos que un colega (terco, y más senior) ha identificado una lista de identificaciones genéticas importantes, y las ha enviado en un simple archivo de texto.
Desafortunadamente, parece que nuestro colega ha decidido utilizar un esquema de nomenclatura ligeramente alterado, añadiendo -gen
al final de cada identificador pseudoaleatoria, antes del _
, si está presente. Para continuar con colaboraciones pacíficas, podría correspondernos modificar nuestro archivo secuencial para que corresponda a este esquema. Podemos hacer esto con sed
, pero será un reto, principalmente porque queremos realizar una inserción, más que una sustitución. En realidad, estaremos realizando una sustitución, ¡pero estaremos sustituyendo partidos con contenidos de ellos mismos!
En cuanto a las referencias posteriores, en una expresión regular, las coincidencias o subcoincidencias que se agrupan y se encierran entre paréntesis tienen sus cadenas coincidentes guardadas en variables\ 1
,\ 2
, y así sucesivamente. El contenido del primer par de paréntesis se guarda en\ 1
, el segundo en\ 2
(puede ser necesaria cierta experimentación para identificar dónde se guardan las coincidencias entre paréntesis anidados). Toda la coincidencia de expresión se guarda en\ 0
.
Para completar el ejemplo, modificaremos el patrón utilizado en el grep
para capturar ambas partes relevantes del patrón, reemplazándolas con\ 1-gen\ 2
.
Los contenidos de Pz_CDNAS.fasta
, luego de ejecutarlo por el sed
anterior, son los siguientes:
Las referencias posteriores también se pueden usar dentro del patrón en sí. Por ejemplo, un sed -r/([A-za-Z] +)\ 1/\ 1/g'
reemplazaría palabras “duplicadas” ([A-za-Z] +)\ 1
con una sola copia de la palabra\ 1
, como en Me gusta mucho sed
, resultando en que me gusta mucho sed
. Pero ten cuidado si estás pensando en usar la sustitución de este tipo como corrector gramatical, porque esta sintaxis no busca a través de los límites de línea (aunque los programas sed
más complejos pueden hacerlo). Este ejemplo no modificaría el siguiente par de líneas (donde la palabra the
aparece dos veces):
The quick sed regex modifies the the lazy awk output.
Algunas notas finales sobre sed
y expresiones regulares en general ayudarán a concluir este capítulo.
- Las expresiones regulares, aunque poderosas, se prestan a errores. Trabajar de manera incremental y verificar regularmente los resultados.
- A menudo es más fácil usar múltiples invocaciones de expresiones regulares (por ejemplo, con múltiples comandos
sed
) en lugar de intentar crear una sola expresión compleja. - Use expresiones regulares en su caso, pero sepa que no siempre son apropiadas. Muchos problemas que pueden parecer un ajuste natural para las expresiones regulares también se ajustan naturalmente a otras estrategias, que deben tomarse en consideración dada la complejidad que las expresiones regulares pueden agregar a un comando o fragmento de código dado.
Algunas personas, cuando se enfrentan a un problema, piensan: “Lo sé, usaré expresiones regulares”. Ahora tienen dos problemas.
~Jamie Zawinski
Ejercicios
- En el archivo de estadísticas de ensamblaje de novo
contig_stats.txt
, los ID de cóntigo se denominanNODE_1
, NODE_2
, etc. Preferiríamos que se llamarancontig1
,contig2
y similares. Producir uncontig_stats_renamed.txt
con estos cambios realizados. - ¿Cuántas secuencias en el archivo
Pz_CDNAS.fasta
están compuestas por una sola lectura? Es probable que necesite usar tantoawk
comosed
aquí, y asegúrese de verificar cuidadosamente los resultados de su tubería conmenos
. - Un colega particularmente odioso insiste en que en el archivo
Pz_CDNAS.fasta
, las secuencias que no forman parte de ningún grupo (es decir, las que no tienen_
sufijo) deben tener el sufijo_nogroup
. Apaciguar a este colega produciendo un archivo a esta especificación llamadoPz_CDNAS_FIXED.FASTA
. - Las líneas cabeceras en el conjunto de proteínas de levadura
orf_trans.fasta
se ven así cuando se ven conmenos -S
después de grepping para>:
Notablemente, las líneas de encabezado contienen información sobre las ubicaciones de los exones individuales; la secuencia YAL001C tiene dos exones en cromosoma I de las ubicaciones 151006 a 147594 y 151166 a 151097 (los números van de grandes a pequeños porque las ubicaciones se especifican en la cadena directa, pero el gen está en la cadena inversa del complemento). Por el contrario, la secuencia YAL002W tiene solo un exón en la cadena directa.¿Cuántas secuencias están compuestas por un solo exón? A continuación, produzca una lista de ID de secuencia en un archivo llamado
multi_exon_ids.txt
que contenga todos esos ID de secuencia con más de un exón como una sola columna. - Como continuación de la pregunta 4, ¿qué secuencia tiene más exones? ¿Qué secuencia de un solo exón es la más larga, en términos de la distancia desde la primera posición hasta la última posición señalada en la lista de exones?
- POSIX, abreviatura de Interfaz de sistema operativo portátil, define un conjunto básico de estándares para programas y sistemas operativos para que diferentes sistemas operativos similares a UNIX puedan interoperar.
- También debemos tener en cuenta que los
/
delimitadores son los más utilizados, pero la mayoría de los caracteres se pueden usar en su lugar; por ejemplo,s| | |g
<pattern><replacement>puede facilitar el reemplazo de/
caracteres.