1.13. El llenguatge de processament d’arxius GAWK
1.13.3. Síntesis condensada de GAWK
GAWK no es limita a ser una eina de filtratge de text estructurat; és un llenguatge de programació complet que compta amb estructures de control de flux, bucles, diversos operadors i funcions integrades per treballar tant amb cadenes de caràcters com amb números. A més, et brinda la possibilitat de controlar el format de la sortida impresa, utilitzar estructures de dades i escriure funcions ad hoc. La combinació intel·ligent d’aquests elements et permetrà crear programes per realitzar transformacions complexes en arxius de text, com podràs veure en molts dels exemples que es presenten a continuació.
La següent llista presenta alguns dels elements i estructures sintàctiques essencials del llenguatge de programació gawk
. Tot i que pretenem cobrir tots aquests elements en profunditat, aquesta llista et donarà una idea del poder i la flexibilitat de gawk
com a llenguatge de programació.
- condicionals
if(condició1){code1}else if(condició2){code2}else {code3}
- bucles for
for (i in array) {code}; for (initialization;condition; increment|decrement)
- bucles while
while(true){code}
- operadors aritmètics
+, -, *, /, %, =, ++, –, +=, -=, …)
- operadors boleans
||, &&
- operadors relacionals
<, <=, ==, !=,>=, >
- funcions integrades
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])
- funcions escrites per l’usuario
function FUNNAME (arg1, arg1) {code}
- estructures de dades
(hashes o arranjaments associatius): array[string]=value
Avaluem el potencial de gawk
. Els conjunts de dades genòmiques sovint es distribueixen amb arxius separats per a cada cromosoma, i generalment necessitem realitzar la mateixa operació en cadascun. Per realitzar aquesta tasca, podem fer servir l’estructura de bucle for de la shell. Si tinguéssim arxius anomenats data_chr[chromosome].txt
, on [chromosome] varia d’1 a 22, i volguéssim executar ./command en ells, s’escriurien les següents ordres al terminal:
$ for i in {1..22}; do ./command data_chr$i.txt; done
El codi anterior recorre tots els valors entre 1 i 22, establint la variable de la shell
$i en cada valor, successivament. La sintaxi per especificar els rangs de números és que {A..B}
dona un rang entre A i B, on A i B són valors sencers.
També podem fer servir for
per recórrer les extensions d’arxiu. Suposem que teníem algunes dades en format PLINK anomenades data.bed
, data.bim
i data.fam
. Podríem llistar aquests arxius individualment executant:
$ for ext in bed bim fam; do ls data.$ext; done
Aquí, la variable $ext
(per a extensió) s’estableix successivament en bed, bim i fam. Aquest exemple en particular no és gaire útil, ja que podríem haver escrit ls data.*
i vist que existeixen aquests tres arxius. El que és útil és poder reanomenar el conjunt de dades base amb una sola línia de codi a la shell. Si es volguessin reanomenar aquests arxius human_data.bed, human_data.bim
i human_data.fam
, es podria escriure:
$ for ext in bed bim fam; do mv -i data.$ext human_data.$ext; done
Una altra construcció for
útil és recórrer grups d’arxius. Podem fer el següent per executar ./command
a cada arxiu amb extensió .txt en el nostre directori actual:
$ for file in *.txt; do ./command $file; done
Ara, suposem que tenim un arxiu anomenat data.txt i que volem separar la informació corresponent a cada cromosoma en arxius separats. Podríem fer servir un bucle for per recórrer cada número de cromosoma i després una expressió booleana en gawk
per extreure només les línies corresponents a aquest cromosoma. Comencem la nostra tasca amb un programa que no funciona del tot i després ho arreglarem.
Si la columna 2 de data.txt conté els números de cromosoma i volem arxius separats per a cada cromosoma, podríem pensar que el següent funciona (tingues en compte, novament, el comportament predeterminat de gawk
per imprimir les línies que coincideixen amb l’expressió booleana donada):
$ for chr in {1..22}; do awk '$2 == $chr' data.txt > data_chr$chr.txt; done
Això és gairebé correcte, però l’expressió donada en gawk és '$2 == $chr' gawk
no l’entén, perquè no coneix ni sap res sobre la variable de la shell $chr
que s’ha definit. En lloc de fer referència a una variable a la shell, podem assignar explícitament variables perquè gawk les usi amb l’opció -v:
$ for chr in {1..22}; do awk -v chr=$chr '$2 == chr' data.txt > data_chr$chr.txt; done
No està malament! Podem proporcionar a gawk
tantes opcions -v com volem per a totes les variables que haguem d’assignar:
$ for chr in {1..22}; do awk -v chr=$chr -v threshold=10 '$2 == chr && $4 > threshold' data.txt > data_chr$chr.txt; done
Això separa cada cromosoma en un arxiu individual amb la condició que els valors a la columna 4 siguin majors que 10.
Què passa si tenim un arxiu que conté dues columnes amb un recompte d’«èxits» i «intents» per a algun procés, cadascun recol·lectat d’una font diferent, i volem calcular la taxa mitjana d’èxit entre totes les fonts? Podem fer servir gawk
per sumar cada columna i després imprimir la mitjana al final fent servir una sintaxi especial. Si l’arxiu data.txt té els èxits i els intents a les columnes 2 i 3, respectivament, podríem fer això:
$ 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
L’ordre gawk
executa el codi a la secció BEGIN abans de processar qualsevol línia a l’entrada i el codi a la secció END després de llegir l’entrada. La secció BEGIN inicialitza dues variables a 0, la secció de codi principal suma les columnes a cada línia, i la secció END imprimeix els totals i la taxa.
Suposem ara que volem buscar mitjançant condicionals aquells assemblatges que conté l’organisme a 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 següent exemple, es volen filtrar les dades de Xenopus tropicalis que tinguin una data de llançament inferior al 2015. L’exemple utilitzarà un condicional en bash i es crearà script que es pugui executar. Salva les pròximes ordres en un fitxer del teu terminal.
#!/bin/bash
# Llegir la taula i guardar les línies que compleixen les condicions en un nou arxiu
awk -F'\t' 'NR==1 || ($2 == "Xenopus tropicalis" && $3 > "2015-01-01")' $1 > filtered_table.txt
# Comptar el nombre de línies a l’arxiu filtrat
num_lines=$(wc -l < filtered_table.txt)
# Si el nombre de línies és més gran que 1 (és a dir, si hi ha entrades que compleixen les condicions),
# imprimir un missatge i el contingut de l’arxiu filtrat
if [ $num_lines -gt 1 ]; then echo "Es van trobar les següents entrades per a Xenopus tropicalis publicades després de 2015-01-01:" cat filtered_table.txt # Si el nombre de línies és igual a 1, imprimir un missatge amb el nom de l’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 nombre de línies és 0, imprimir un missatge que digui que no es van trobar entrades que compleixin les condicions
else echo "No es van trobar entrades per a Xenopus tropicalis publicades després de 2015-01-01." fi
Salva el fitxer i executa les següents ordres en el terminal:
$ chmod +x script.sh
$ ./script.sh ensamble.txt
Aquest script utilitza gawk
per llegir la taula i guardar les línies que compleixen les condicions en un nou arxiu anomenat filtered_table.txt. Després, compta el nombre de línies en aquest arxiu i utilitza dues condicions if per imprimir missatges diferents depenent de si hi ha entrades que compleixin les condicions o no. En el primer cas, quan hi ha més d’una entrada que compleix les condicions, l’script imprimeix un missatge que indica que es van trobar entrades i mostra el contingut de la taula filtrada. En el segon cas, imprimeix un missatge si només hi ha una entrada que compleix la condició i l’última acció ocorrerà si no hi ha cap entrada en el fitxer de partida que compleixi les condicions demandades.
La taula 12 és un llistat de les operacions més comunes i utilitzades amb GAWK.
Taula 12. Operacions utilitzades a GAWK.
Variable | Descripció |
i=0 |
Assignar un valor |
a[i]=0; |
Guardar un valor en una taula |
i++; |
Incrementar un comptador |
print i; |
Mostrar una variable per pantalla |
print NR |
Mostrar el nombre de línies processades |
print $1,$4 |
Mostrar la primera i quarta columnes |
I a la taula 13 es mostren exemples d’instruccions condicionals amb GAWK.
Taula 13. Instruccions condicionals amb GAWK.
Variable | Descripció |
if ($1 > 0) print $0; |
Si la primera és positiva |
if ($1 < 0) print $0; |
Si la primera és negativa |
if ($1 >= 0) print $0; |
Si la primera columna és major o igual a zero |
if ($1 <= 0) print $0; |
Si la primera columna és menor o igual a zero |
if ($1 == 0) print $0; |
Si la primera columna és igual a zero |
if (CONDICIÓ1) && (CONDICIÓ2) |
Si es compleixen les dues condicions |
if (CONDICIÓ1) || (CONDICIÓ2 |
Si es compleix alguna de les dues condicions |
if ¡(CONDICIÓ1) |
Si no es compleix la condició |