Awk - Intermedi

Variables en AWK

El llenguatge de programació AWK ens permet definir variables i utilitzar-les en les nostres accions. Les variables en AWK són dinàmiques i no necessiten ser declarades abans d'utilitzar-les. Això significa que podem utilitzar una variable sense haver-la declarat prèviament.

Per exemple, si volem comptar el nombre de línies que hi ha al fitxer pokemon.csv, podem fer servir una variable per a emmagatzemar el nombre de línies. A continuació, mostrem un exemple de com comptar el nombre de línies del fitxer pokemon.csv:

$ awk 'BEGIN { n=0 } { ++n } END { print n }' pokemon.csv

On n és la variable que utilitzem per emmagatzemar el nombre de línies. Per començar, inicialitzem la variable n a 0 amb la clàusula {BEGIN}. Aquesta clausula és opcional, ja que les variables en AWK són dinàmiques i no necessiten ser declarades prèviament. Després, incrementem la variable n per a cada línia amb la clàusula {++n}. Finalment, utilitzem la clàusula {END} per imprimir el valor de la variable n després de processar totes les línies.

Operacions Aritmètiques

OperadorAritatSignigicat
+BinariSuma
-BinariResta
*BinariMultiplicació
/BinariDivisió
%BinariMòdul
^BinariExponent
++UnariIncrement 1 unitat
--UnariDecrement 1 unitat
+=Binarix = x+y
-=Binarix = x-y
*=Binarix=x*y
/=Binarix=x/y
%=Binarix=x%y
^=Binarix=x^y

Implementeu un script que comprovi que el Total (columna 5) és la suma de tots els atributs (columnes 6,7,8,9,10 i 11). La sortida ha de ser semblant a:

Charmander->Total=309==309
Charmeleon->Total=405==405
Charizard->Total=534==534
  • En AWK:

    $ awk -F, '{ print $2"->Total="$5"=="($6+$7+$8+$9+$10+$11)}' pokemon.csv
    
  • En bash:

    #!/bin/bash
    while IFS=, read -r col1 col2 col3 col4 col5 col6 col7 col8 col9 col10 col11 \
    	col12 col13; do
    if [[ "$col5" == "$((col6+col7+col8+col9+col10+col11))" ]]; then
        echo "$col2->Total=$col5==$((col6+col7+col8+col9+col10+col11))"
    fi
    done < pokemon.csv
    

Variables Internes

AWK té variables internes que són molt útils per a la manipulació de dades. Aquestes variables són:

VariableContingut
$0Conté tot el registre actual
NFConté el valor del camp (columna) actual.
$1,$2...,$1 conté el valor del primer camp i així fins l'últim camp. noteu que $NF serà substituït al final pel valor de l'últim camp.
NRÍndex del registre actual. Per tant, quan es processa la primera línia aquesta variable té el valor 1 i quan acaba conté el nombre de línies processades.
FNRÍndex del fitxer actual que estem processant.
FILENAMENom del fitxer que estem processant.

Per exemple:

  • Utilitzeu la variable NR per simplificar la comanda per comptar el nombre de línies del fitxer pokemon.csv:

    $ awk 'END{ print NR }' pokemon.csv
    
  • Traduïu la capçalera del fitxer pokemon.csv al catala. La capçalera és la següent: # Name Type 1 Type 2 Total HP Attack Defense Sp. Atk Sp. Def Speed Generation Legendary. La traducció és la següent: # Nom Tipus 1 Tipus 2 Total HP Atac Defensa Atac Especial Defensa Especial Velocitat Generació Llegendari. I després imprimiu la resta de línies del fitxer.

 $ awk 'NR==1 { $1="#"; $2="Nom"; $3="Tipus 1"; $4="Tipus 2"; $5="Total"; $6="HP"; \
 $7="Atac"; $8="Defensa"; $9="Atac Especial"; $10="Defensa Especial"; \
 $11="Velocitat"; $12="Generació"; $13="Llegendari"; print $0 } NR>1 {print}' \
 pokemon.csv
  • Implementeu una comanda que permeti detectar entrades incorrectes al fitxer pokemon.csv. Una entrada incorrecta és aquella que no té 13 valors per línia. En cas de detectar una entrada incorrecta, la eliminarem de la sortida i comptarem el nombre de línies eliminades per mostrar-ho al final.
 $ awk 'NF != 13 { n++ } NF == 13 { print } END{ print "There are ", n, \
 "incorrect entries." }' pokemon.csv

Condicionals

Les sentències condicionals s'utilitzen en qualsevol llenguatge de programació per executar qualsevol sentència basada en una condició particular. Es basa en avaluar el valor true o false en les declaracions if-else i if-elseif. AWK admet tot tipus de sentències condicionals com altres llenguatges de programació.

Implementeu una comanda que us indiqui quines figures de tipus Fire són ordinàries o llegendàries. Busquem una sortida semblant a:

Charmander is a common pokemon.
Charizard is a legendary pokemon.
 ...

La condició per ser una figura llegendària és que la columna 13 sigui True.

$ awk -F, '/Fire/ { if ($13 == "True") { print $2, "is a legendary pokemon." }\ 
else { print $2, "is a common pokemon." } }' pokemon.csv

Es pot simplificar la comanda anterior amb l'ús de l'operador ?:

$ awk -F, '/Fire/ { print $2, "is a", ($13 == "True" ? "legendary" : "common"), \
"pokemon." }'  pokemon.csv

Formatant la sortida

AWK ens permet també utilitzar una funció semblant al printf de C. Permet imprimir la cadena amb diferents formats: printf("cadena",expr1,,expr2,...)

%20sEs mostraran 20 caràcters de la cadena alineats a la dreta per defecte.
%-20sEs mostraran 20 caràcters de la cadena alineats a l'esquerra per defecte.
%3dEs mostrarà un enter de 3 posicions alineat a la dreta
%03dEs mostrarà un enter de 3 posicions completat amb un 0 a l'esquerra i tot alineat a la dreta
%-3dEs mostrarà un enter de 3 posicions alineat a la esquerra.
&+3dIdem amb signe i alineat a la dreta
%10.2fEs mostrarà un nombre amb coma flotant amb 10 posicions, 2 de les quals seràn decimals.

Per exemple:

 awk -F, \
' BEGIN{ 
    max=0  
    min=100  
}  
{  
    if ($3 =="Fire") {  
        n++  
        attack+=$7
        
        if ($7 > max){  
            pmax=$2  
            max=$7  
        }  
        if ($7 < min){  
            pmin=$2
            min=$7  
        }  
    }  
}  
END{ printf("Avg(attack):%4.2f \nWeakest:%s \nStrongest:%s\n",attack/n,pmin,pmax)
}' pokemon.csv 

Exercicis Intermedis

  • Implementeu un script que compti totes les figures que tenim al fitxer pokemon.csv i que tingui la sortida següent:

    Counting pokemons...
    There are 800 pokemons.
    

    Recordeu que la primera línia és la capçalera i no la volem comptar.

    • En bash:
    !/bin/bash
    echo "Counting pokemons..." 
    n=`wc -l pokemon.csv` 
    n=`expr $n - 1`
    echo "There are $n pokemons."
    
    • En AWK:
    $ awk 'BEGIN { print "Counting pokemons..." } { ++n } END{ print "There are ",\
     n-1, "pokemons." }' pokemon.csv
    
  • Implementeu un comptador per saber totes les figures de tipus Fire de la primera generació descartant les figures Mega i que tingui la sortida següent:

    Counting pokemons...
    There are 12 fire type pokemons in the first generation without Mega evolutions.
    
    • Per fer-ho en bash, podeu combinar les comandes grep, cut, wc i expr. Nota, l'argument -v de grep exclou les línies que contenen el patró i la generació s'indica a la columna 12 amb el valor 1:
    !/bin/bash
    echo "Counting pokemons..."
    n=`grep Fire pokemon.csv | grep -v "Mega" | cut -d, -f12 | grep 1 | wc -l`
    echo "There are $n pokemons in the first generation without Mega evolutions."
    
    • Per fer-ho en AWK, teniu el negador ! per negar el patró:
    $ awk -F, 'BEGIN { print "Counting pokemons..." } /Fire/ && !/Mega/ && \
    $12 == 1 { ++n } END{ print "There are ", n, "fire type pokemons in the first\
     generation without Mega evolutions." }'  pokemon.csv
    

    En aquest exemple, hem utilitzat l'operador lògic && per combinar dos patrons. Això significa que la línia ha de contenir el patró Fire i no ha de contenir el patró Mega. Això ens permet filtrar les Mega figures del nostre comptador. A més, hem utilitzat l'operador ! per negar el patró Mega. Això significa que la línia no ha de contenir el patró Mega. Finalment, hem utilitzat la cláusula {END} per imprimir el resultat final.

  • Indiqueu a quina línia es troba cada figura del tipus Fire. Volem imprimir la línia i el nom de la figura. La sortida ha de ser semblant a:

    Line:  6    Charmander
    Line:  7    Charmeleon
    Line:  8    Charizard
    Line:  9    CharizardMega Charizard X
    Line:  10   CharizardMega Charizard Y
    ...
    Line:  737  Litleo
    Line:  738  Pyroar
    Line:  801  Volcanion
    

    on el format de cada línia és Line: n Nom de la figura.

    • En AWK podem fer servir la variable NR per obtenir el número de línia actual. A més a més, podeu formatar la sortida amb print cadena,variable,cadena,variable,...:
     $ awk -F, '/Fire/ {print "Line: ", NR, "\t" $2}' pokemon.csv
    
    • En bash:

      #!/bin/bash
      while IFS=, read -r col1 col2 col3 rest; do
      ((line_number++))
      # Check if the line contains the word "Fire"
      if [[ "$col3" == "Fire"  || "$col4" == "Fire" ]]; then
          echo "Line: $line_number    $col2"
      fi
      done < pokemon.csv
      
  • Implementeu un script que permeti comptar el nombre de figures de tipus Fire i Dragon. La sortida ha de ser semblant a:

    Fire:64
    Dragon:50
    Others:689
    
    • En AWK:

      $ awk -F, 'BEGIN{ fire=0; dragon=0; others=0 } /Fire/ { fire++ } /Dragon/ \
       { dragon++ } !/Fire|Dragon/ { others++ } END{ print "Fire:" fire \ 
       "\nDragon:" dragon "\nOthers:" others }' pokemon.csv
      
    • En bash:

      #!/bin/bash
      fire=0
      dragon=0
      others=0
      
      while IFS=, read -r col1 col2 col3 col4 col5 col6 col7 col8 col9 col10 col11 \
      	col12 col13; do
      if [[ "$col3" == "Fire"  || "$col4" == "Fire" ]]; then
       		((fire++))
      fi
      
      if [[ "$col3" == "Dragon"  || "$col4" == "Dragon" ]]; then
       		((dragon++))
      fi
      
      if [[ "$col3" != "Fire"  && "$col4" != "Fire" && "$col3" != "Dragon"  \
      && "$col4" != "Dragon" ]]; then
       		((others++))
      fi
      done < pokemon.csv
      
      echo "Fire:$fire"
      echo "Dragon:$dragon"
      echo "Others:$others"
      
  • Imprimiu el fitxer pokemon.csv amb una nova columna que indiqui la mitjana aritmètica dels atributs de cada figura. La sortida ha de ser semblant a:

    #,Name,Type 1,Type 2,Total,HP,Attack,Defense,Sp. Atk,Sp. Def,Speed,
    Generation,Legendary,Avg
    1,Bulbasaur,Grass,Poison,318,45,49,49,65,65,45,1,False,53
    2,Ivysaur,Grass,Poison,405,60,62,63,80,80,60,1,False,67.5
    3,Venusaur,Grass,Poison,525,80,82,83,100,100,80,1,False,87.5
    
    • En AWK:
    $ awk -F, '{ if (NR==1) print $0",Avg"; else print $0","($6+$7+$8+$9+$10+\
    	$11)/6 }' pokemon.csv
    
    • En bash:
    #!/bin/bash
    while IFS=, read -r col1 col2 col3 col4 col5 col6 col7 col8 col9 col10 col11 \
    	col12 col13; do
    if [[ "$col1" == "#" ]]; then
    	echo "$col1,$col2,$col3,$col4,$col5,$col6,$col7,$col8,$col9,$col10,\
    		$col11,$col12,$col13,Avg"
    else
    	avg=$((($col6+$col7+$col8+$col9+$col10+$col11)/6))
    	echo "$col1,$col2,$col3,$col4,$col5,$col6,$col7,$col8,$col9,$col10,\
    		$col11,$col12,$col13,$avg"
    fi
    done < pokemon.csv
    

    Nota: Bash de forma nativa no permet operacions aritmètiques amb nombres decimals. Per fer-ho, cal utilitzar una eina com bc. En aquest cas, podeu adaptar el codi per utilitzar bc quan calculeu la mitjana i fer servir printf per formatar la sortida amb el nombre de decimals que vulgueu.

  • Cerca la figura més més forta i més feble tenint en compte el valor de la columna 7 de pokemon.csv de tipus Fire de la primera generació.

    • En AWK, assumiu que el valors de la columna 7 van de 0 a 100:
    $ awk -F, 'BEGIN{ max=0; min=100 } /Fire/ && $12 == 1 { if ($7 > max) \
    	{ max=$7; pmax=$2 } \
    if ($7 < min) { min=$7; pmin=$2 } } END{ print "Weakest: "pmin " \
    	\nStrongest: "pmax }' pokemon.csv
    
    • En bash:
    #!/bin/bash
    max=0
    min=100
    while IFS=, read -r col1 col2 col3 col4 col5 col6 col7 col8 col9 col10 col11 \
    	 col12 col13; do
    if [[ "$col3" == "Fire" ]] && [[ "$col12" == "1" ]]; then
    	if [[ "$col7" -gt "$max" ]]; then
    		max=$col7
    		pmax=$col2
    	fi
    	if [[ "$col7" -lt "$min" ]]; then
    		min=$col7
    		pmin=$col2
    	fi
    fi
    done < pokemon.csv
    echo "Weakest: $pmin"
    echo "Strongest: $pmax"