1. Introducció als entorns de treball UNIX

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.bimhuman_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

Font: elaboració pròpia.

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ó

Font: elaboració pròpia.