1.13. El lenguaje de procesado de archivos GAWK
1.13.3. Síntesis condensada de GAWK
GAWK no se limita a ser una herramienta de filtrado de texto estructurado; es un lenguaje de programación completo que cuenta con estructuras de control de flujo, bucles, diversos operadores y funciones integradas para trabajar tanto con cadenas de caracteres como con números. Además, te brinda la posibilidad de controlar el formato de la salida impresa, utilizar estructuras de datos y escribir funciones ad hoc. La combinación inteligente de estos elementos te permitirá crear programas para realizar transformaciones complejas en archivos de texto, como podrás ver en muchos de los ejemplos que se presentan a continuación.
La siguiente lista presenta algunos de los elementos y estructuras sintácticas esenciales del lenguaje de programación gawk
. Aunque no vamos a cubrir todos estos elementos en profundidad, esta lista te dará una idea del poder y la flexibilidad de gawk
como lenguaje de programación.
- condicionales
if(condicion1){code1}else if(condición2){code2}else {code3}
- bucles for
for (i in array) {code}; for (initialization;condition; increment|decrement)
- bucles while
while(true){code}
- operadores aritméticos
+, -, *, /, %, =, ++, –, +=, -=, …)
- operadores boleanos
||, &&
- operadores relacionales
<, <=, ==, !=,>=, >
- funciones integradas
length(str); int(num); index (str1, str2); split(str,arr,del); substr(str,pos,len); printf(fmt,args); tolower(str); toupper(str); gsub(regexp, replacement [, target])
- funciones escritas por el usuario
function FUNNAME (arg1, arg1) {code}
- estructuras de datos
(hashes o arreglos asociativos): array[string]=value
Evaluemos el potencial de gawk
. Los conjuntos de datos genómicos a menudo se distribuyen con archivos separados para cada cromosoma, y generalmente necesitamos efectuar la misma operación en cada uno. Para realizar esta tarea, podemos usar la estructura de bucle for del shell. Si tuviéramos archivos llamados data_chr[chromosome].txt
, donde [chromosome] varía de 1 a 22, y quisiéramos ejecutar ./command en ellos, se escribirían los siguientes comandos en el terminal:
$ for i in {1..22}; do ./command data_chr$i.txt; done
El código anterior recorre todos los valores entre 1 y 22, estableciendo la variable de shell
$i en cada valor, sucesivamente. La sintaxis para especificar los rangos de números es que {A..B}
da un rango entre A y B, donde A y B son valores enteros.
También podemos utilizar for
para recorrer las extensiones de archivo. Supongamos que teníamos algunos datos en formato PLINK llamados data.bed, data.bim
y data.fam
. Podríamos listar estos archivos individualmente ejecutando:
$ for ext in bed bim fam; do ls data.$ext; done
Aquí, la variable $ext
(para extensión) se establece sucesivamente en bed, bim y fam. Este ejemplo en particular no es muy útil, ya que podríamos haber escrito ls data.*
y visto que existen estos tres archivos. Lo útil es poder renombrar el conjunto de datos base con una sola línea de código en el shell. Si quisiera renombrar estos archivos human_data.bed, human_data.bim
y human_data.fam
, se podría escribir:
$ for ext in bed bim fam; do mv -i data.$ext human_data.$ext; done
Otra construcción for
útil es recorrer grupos de archivos. Podemos hacer lo siguiente para ejecutar ./command
en cada archivo con extensión .txt en nuestro directorio actual:
$ for file in *.txt; do ./command $file; done
Ahora, supongamos que tenemos un archivo llamado data.txt y que queremos separar la información correspondiente a cada cromosoma en archivos separados. Podríamos usar un bucle for para recorrer cada número de cromosoma y luego una expresión booleana en gawk
para extraer solo las líneas correspondientes a ese cromosoma. Comencemos nuestra tarea con un programa que no funciona del todo y luego lo arreglaremos.
Si la columna 2 de data.txt contiene los números de cromosoma y queremos archivos separados para cada cromosoma, podríamos pensar que lo siguiente funciona (ten en cuenta, nuevamente, el comportamiento predeterminado de gawk
para imprimir las líneas que coinciden con la expresión booleana dada):
$ for chr in {1..22}; do awk '$2 == $chr' data.txt > data_chr$chr.txt; done
Esto es casi correcto, pero la expresión dada en gawk
es '$2 == $chr' gawk
no la entiende, porque no conoce ni sabe nada sobre la variable de shell $chr
que se ha definido. En lugar de hacer referencia a una variable en la shell, podemos asignar explícitamente variables para que gawk
las use con la opción -v:
$ for chr in {1..22}; do awk -v chr=$chr '$2 == chr' data.txt > data_chr$chr.txt; done
¡No está mal! Podemos proporcionar a gawk
tantas opciones -v como queramos para todas las variables que nos importen asignar:
$ for chr in {1..22}; do awk -v chr=$chr -v threshold=10 '$2 == chr && $4 > threshold' data.txt > data_chr$chr.txt; done
Esto separa cada cromosoma en un archivo individual con la condición de que los valores en la columna 4 sean mayores que 10.
¿Qué ocurre si tenemos un archivo que contiene dos columnas con un recuento de «éxitos» y «intentos» para algún proceso, cada uno recolectado de una fuente diferente, y queremos calcular la tasa promedio de éxito entre todas las fuentes? Podemos usar gawk
para sumar cada columna y luego imprimir el promedio al final usando una sintaxis especial. Si el archivo data.txt tiene los éxitos y los intentos en las columnas 2 y 3, respectivamente, podríamos hacer esto:
$ gawk 'BEGIN {total_success = total_attempts = 0;} {total_success += $2; total_attempts += $3} END {print "Éxitos:", total_success, "Intentos:", total_attempts, "Tasa:", total_success / total_attempts}' data.txt
El comando gawk
ejecuta el código en la sección BEGIN antes de procesar cualquier línea en la entrada y el código en la sección END después de leer la entrada. La sección BEGIN inicializa dos variables a 0, la sección de código principal suma las columnas en cada línea, y la sección END imprime los totales y la tasa.
Supongamos ahora que queremos buscar mediante condicionales aquellos ensambles que contiene el organismo en Xenopus tropicalis:
$ gawk ' BEGIN { FS="\t"; print "assembly_accession\torganism_name\tseq_rel_date\tasm_name\tsubmitter" } { if ($2 == "Xenopus tropicalis") { print $1 "\t" $2 "\t" $3 "\t" $4 "\t" $5 } } ' ensamble.txt
GCA_017527675.1 Phaeodactylum tricornutum 2022-03-24 Phatr3.0 NCBI GCA_011586775.1 Xenopus tropicalis 2022-04-11 Xenbase_v9.2 NCBI
En el siguiente ejemplo, se quieren filtrar los datos de Xenopus tropicalis que tengan una fecha de lanzamiento inferior a 2015. El ejemplo utilizará un condicional en bash y se creará script que se pueda ejecutar. Salva las próximas órdenes en un fichero de tu terminal.
#!/bin/bash
# Leer la tabla y guardar las líneas que cumplen las condiciones en un nuevo archivo
awk -F'\t' 'NR==1 || ($2 == "Xenopus tropicalis" && $3 > "2015-01-01")' $1 > filtered_table.txt
# Contar el número de líneas en el archivo filtrado
num_lines=$(wc -l < filtered_table.txt)
# Si el número de líneas es mayor que 1 (es decir, si hay entradas que cumplen las condiciones),
# imprimir un mensaje y el contenido del archivo filtrado
if [ $num_lines -gt 1 ]; then echo "Se encontraron las siguientes entradas para Xenopus tropicalis publicadas después de 2015-01-01:" cat filtered_table.txt # Si el número de líneas es igual a 1, imprimir un mensaje con el nombre de la entrada elif [ $num_lines -eq 1 ]; then echo "Se encontró la siguiente entrada para Xenopus tropicalis publicada después de 2015-01-01:" awk -F'\t' '{print $4}' filtered_table.txt
# Si el número de líneas es 0, imprimir un mensaje de que no se encontraron entradas que cumplan las condiciones
else echo "No se encontraron entradas para Xenopus tropicalis publicadas después de 2015-01-01." fi
Salva el fichero y ejecuta las siguientes órdenes en el terminal:
$ chmod +x script.sh
$ ./script.sh ensamble.txt
Este script emplea gawk
para leer la tabla y guardar las líneas que cumplen las condiciones en un nuevo archivo llamado filtered_table.txt. Luego, cuenta el número de líneas en ese archivo y utiliza dos condiciones if para imprimir mensajes diferentes dependiendo de si hay entradas que cumplan las condiciones o no. En el primer caso, cuando hay más de una entrada que cumple las condiciones, el script imprime un mensaje que indica que se encontraron entradas y muestra el contenido de la tabla filtrada. En el segundo caso, imprime un mensaje si solo hay una entrada que cumple la condición y la última acción ocurrirá si no hay ninguna entrada en el fichero de partida que cumpla las condiciones demandadas.
La tabla 12 es un listado de las operaciones más comunes y usadas con GAWK.
Tabla 12. Operaciones utilizadas en GAWK.
Variable | Descripción |
i=0 |
Asignar un valor |
a[i]=0; |
Guardar un valor en una tabla |
i++; |
Incrementar un contador |
print i; |
Mostrar una variable por pantalla |
print NR |
Mostrar el número de líneas procesadas |
print $1,$4 |
Mostrar la primera y cuarta columnas |
Y en la tabla 13 se muestran ejemplos de instrucciones condicionales con GAWK.
Tabla 13. Instrucciones condicionales con GAWK.
Variable | Descripción |
if ($1 > 0) print $0; |
Si la primera es positiva |
if ($1 < 0) print $0; |
Si la primera es negativa |
if ($1 >= 0) print $0; |
Si la primera columna es mayor o igual a cero |
if ($1 <= 0) print $0; |
Si la primera columna es menor o igual a cero |
if ($1 == 0) print $0; |
Si la primera columna es igual a cero |
if (CONDICION1) && (CONDICION2) |
Si se cumplen las dos condiciones |
if (CONDICION1) || (CONDICION2 |
Si se cumple alguna de las dos condiciones |
if ¡(CONDICION1) |
Si no se cumple la condición |