A la lliçó anterior vas escriure les validacions que informe-diari.sh necessitava, però encadenades amb || i claus: un estil que aguanta bé dues comprovacions i es torna il·legible amb cinc. Ara els donem la forma definitiva amb if. I pel camí descobriràs la idea que més vegades s'explica malament als cursos de Bash: if no avalua cap booleà, avalua un codi de sortida. Entendre això canvia del tot la manera d'escriure les condicions, i explica per què if grep -q ERROR fitxer —sense cap claudàtor— és la construcció més idiomàtica del llenguatge.

Contingut

  1. Sintaxi d'if / then / fi
  2. La idea clau: if avalua un codi de sortida
  3. else i elif
  4. Condicionals d'una línia
  5. if sobre ordres reals
  6. Condicions compostes, negació i validació d'entrada
  7. Imbricació, clàusules de guarda i sortida primerenca
  8. Codis de sortida diferents segons l'error
  9. informe-diari.sh valida el seu entorn

  1. Sintaxi d'if / then / fi

L'estructura bàsica és if condició, després then amb les ordres i fi per tancar. Habitualment s'escriu amb el then a la mateixa línia, separat per un punt i coma:

if [[ -f "$CAMI_CSV" ]]; then
    echo "El fitxer d'enviaments està disponible"
fi

Tres detalls que causen la majoria dels errors inicials: fi tanca el bloc (és if a l'inrevés, convenció heretada del shell de Bourne, igual que case/esac); el ; abans de then és obligatori si van a la mateixa línia, perquè si no Bash llegeix then com un argument més de la condició; i la indentació és purament estètica per a Bash, però imprescindible per a qui llegeix —quatre espais en aquest curs—.

  1. La idea clau: if avalua un codi de sortida

Aquí hi ha el concepte que cal interioritzar. A la majoria de llenguatges, if rep una expressió booleana. A Bash, if rep una ordre, l'executa i en mira el codi de sortida. Si és 0, executa el bloc then; si és diferent de 0, no. Això té una conseqüència que desconcerta quan véns d'altres llenguatges: 0 significa cert, a l'inrevés del que és habitual. La raó és que a Unix 0 és «sense errors» i hi ha moltes maneres de fallar, cadascuna amb el seu número (lliçó 03-01).

I en té una altra de molt més important: si if executa una ordre qualsevol, aleshores [[ ... ]] no és sintaxi especial d'if, sinó simplement l'ordre que hi sols posar. Comprova-ho:

Executa [[ -f /srv/veloz/dades/enviaments.csv ]] seguit d'echo $? i obtindràs 0 si el fitxer existeix i 1 si no. Aquest [[ ]] funciona perfectament fora de qualsevol if, perquè és una ordre de ple dret. Per això if [[ -f "$CAMI_CSV" ]] i if test -f "$CAMI_CSV" són idèntics per a Bash.

Retén aquesta frase: qualsevol ordre pot anar dins d'un if. És la porta d'entrada a l'apartat 5, que és on if desplega tota la seva potència.

  1. else i elif

else cobreix el cas contrari, i elif (contracció d'else if) encadena condicions alternatives:

if (( total_errors == 0 )); then
    echo "Jornada sense errors a veloz-api"
elif (( total_errors < LLINDAR_ERRORS )); then
    echo "Errors dins del tolerable: $total_errors"
elif (( total_errors < LLINDAR_ERRORS * 2 )); then
    echo "AVÍS: $total_errors errors, per sobre del llindar"
else
    echo "CRÍTIC: $total_errors errors, més del doble del llindar"
fi                    # amb total_errors=73 → AVÍS: 73 errors...

Bash avalua les condicions per ordre i s'atura a la primera que és certa. Per això l'ordre importa: si posessis primer total_errors < LLINDAR_ERRORS * 2, aquesta branca capturaria també els casos normals. Ordena sempre del més específic al més general. Pots encadenar tants elif com vulguis, però quan en passes de quatre o cinc comparant el mateix valor contra valors fixos, l'eina adequada és case (04-05).

  1. Condicionals d'una línia

Quan el cos és una sola ordre curta, hi cap tot en una línia fent servir punts i coma. L'alternativa, ja coneguda de 03-03, són && i ||:

if [[ -z $ciutat ]]; then ciutat="Valencia"; fi   # un ; abans de then, un altre abans de fi
[[ -z $ciutat ]] && ciutat="Valencia"             # equivalent, més compacte

A la forma llarga, cada ; substitueix un salt de línia. Quina fer servir? La taula resol el dubte:

Situació Estil recomanat
Una acció curta, sense alternativa condició && acció
Una acció només si alguna cosa falla condició || acció
Validació que avorta condició || { missatge; exit N; }
Dues branques, o més d'una ordre al cos if ... else ... fi
Tres casos o més if/elif o case (04-05)

La regla de fons és de llegibilitat: && i || per a reflexos d'una línia, if per a lògica. I no facis servir mai a && b || c com a substitut d'if/else, pel motiu que ja vas veure a 03-03: si b falla, c s'executa igualment.

  1. if sobre ordres reals

Aquest és l'apartat més important de la lliçó. Com que if avalua codis de sortida, pots posar-hi directament l'ordre que t'interessa sense claudàtors, cosa que resulta més curta, més ràpida i més expressiva:

if grep -q ERROR "$CAMI_APP_LOG"; then          # idiomàtic
    echo "Hi ha errors registrats avui"
fi
n=$(grep -c ERROR "$CAMI_APP_LOG")              # alternativa maldestra
if [[ $n -gt 0 ]]; then echo "Hi ha errors"; fi

La primera versió és millor per tres motius: grep -q deixa de llegir així que troba la primera coincidència (en un log d'un gigabyte, la diferència és abismal), no crea cap subshell per capturar la sortida, i expressa la intenció directament. Fes servir la segona només quan necessitis de debò el número.

Els usos idiomàtics que escriuràs més vegades són aquests:

if command -v jq >/dev/null 2>&1; then        # està instal·lada l'eina?
    echo "jq disponible, s'utilitzarà per a la sortida JSON"
else
    echo "AVÍS: jq no instal·lat, sortida en text pla" >&2
fi
ping -c1 -W2 srv-veloz-01 >/dev/null 2>&1 && echo "servidor accessible"
systemctl is-active --quiet veloz-api && echo "veloz-api en marxa"

Para atenció a command -v jq >/dev/null 2>&1: és la manera correcta i portable de comprovar si un programa existeix. Per aquí veuràs which jq, però which és un programa extern que no és a tots els sistemes i el codi de sortida del qual no és fiable; command -v és un builtin de Bash (lliçó 01-04) i funciona sempre. La redirecció a /dev/null —el patró de 02-04 aplicat a les condicions— silencia la sortida en els tres casos, perquè aquí només ens interessa el codi de sortida; sense ella veuries les estadístiques del ping enmig de l'informe.

  1. Condicions compostes, negació i validació d'entrada

Per combinar condicions, el que és natural a Bash és fer servir && i || dins de [[ ]], com vas veure a 03-03: if [[ -f "$CAMI_CSV" && -r "$CAMI_CSV" && -s "$CAMI_CSV" ]] pregunta d'un sol cop si el CSV existeix, és llegible i té contingut.

També pots combinar ordres completes amb && a la condició de l'if, cosa menys coneguda però perfectament vàlida, com a if command -v jq >/dev/null && [[ -f "$CAMI_JSON" ]]; then.

La negació s'escriu amb !, i es pot aplicar tant dins com fora dels claudàtors:

if ! grep -q ERROR "$CAMI_APP_LOG"; then   # negant una ordre externa
    echo "Jornada neta: cap error registrat"
fi
[[ ! -f "$CAMI_CSV" ]] && echo "Falta el fitxer d'enviaments" >&2   # dins del test

Compte amb un detall d'estil: if ! [[ -f "$f" ]] i if [[ ! -f "$f" ]] són equivalents, però la segona es llegeix millor perquè manté la negació enganxada a allò que nega. I if ! ordre és l'única forma quan el que negues és una ordre externa.

Els operadors de fitxer de 03-03 troben aquí la seva llar natural. Un script seriós no suposa mai que les seves dades d'entrada són on haurien de ser:

if   [[ ! -e "$CAMI_CSV" ]]; then echo "ERROR: no existeix $CAMI_CSV" >&2; exit 4
elif [[ ! -r "$CAMI_CSV" ]]; then echo "ERROR: no el puc llegir" >&2;      exit 4
elif [[ ! -s "$CAMI_CSV" ]]; then echo "ERROR: és buit" >&2;               exit 4
fi

Aquest bloc és didàctic però exagerat: distingir cada causa amb el seu missatge és excel·lent per diagnosticar i excessiu per a un script petit. La versió condensada que faràs servir a la pràctica agrupa les tres a if [[ ! -r "$CAMI_CSV" || ! -s "$CAMI_CSV" ]]. El criteri per triar entre totes dues: com més lluny sigui qui llegeixi l'error, més específic ha de ser el missatge. Un script que executes tu es pot permetre missatges genèrics; un que corre per cron a les set del matí i només deixa rastre en un log necessita dir exactament què ha fallat.

No oblidis validar també les variables, no només els fitxers: [[ -z $ciutat ]] && { echo "ERROR: no s'ha indicat cap ciutat" >&2; exit 2; }. Aquesta comprovació prendrà tot el seu sentit a 03-05, quan la ciutat arribi com a argument de la línia d'ordres i pugui perfectament no arribar.

  1. Imbricació, clàusules de guarda i sortida primerenca

Un if pot contenir un altre if, però la imbricació es torna il·legible molt de pressa:

if [[ -f "$CAMI_CSV" ]]; then            # la lògica útil queda a tres nivells
    if [[ -r "$CAMI_CSV" ]]; then
        if [[ -s "$CAMI_CSV" ]]; then
            tail -n +2 "$CAMI_CSV" | cut -d, -f5 | sort | uniq -c
        fi
    fi
fi     # ...i si el fitxer no existeix, ningú no se n'assabenta

El problema no és només estètic. Amb aquesta forma, si el fitxer no existeix l'script no diu res i acaba com si tot hagués anat bé, perquè no hi ha cap branca que tracti la fallada. És una fallada silenciosa de manual. La solució professional s'anomena clàusula de guarda: inverteix cada condició, tracta el cas dolent primer i surt immediatament, i deixa el codi útil al final i sense indentar.

[[ -f "$CAMI_CSV" ]] || { echo "ERROR: no existeix $CAMI_CSV" >&2; exit 4; }
[[ -r "$CAMI_CSV" ]] || { echo "ERROR: no puc llegir $CAMI_CSV" >&2; exit 4; }
[[ -s "$CAMI_CSV" ]] || { echo "ERROR: $CAMI_CSV buit" >&2; exit 4; }
tail -n +2 "$CAMI_CSV" | cut -d, -f5 | sort | uniq -c

La segona versió guanya en quatre fronts: passa de tres nivells d'indentació a cap, diu què ha fallat amb un missatge específic, retorna un codi de sortida diferent de zero en lloc de fingir èxit, i admet una comprovació nova afegint-hi una línia en comptes d'un altre nivell.

La regla, aplicable a qualsevol llenguatge: valida i surt al principi; deixa la feina real al final. Si et trobes amb més de dos nivells d'if, gairebé sempre hi ha una guarda esperant que l'extreguin.

  1. Codis de sortida diferents segons l'error

Ja saps que exit N és el contracte amb qui et crida (03-01), i les condicionals són el mecanisme que permet complir-lo amb precisió: cada motiu de fallada, el seu propi número. Per a informe-diari.sh fixem aquesta taula, que documentarem a la capçalera del fitxer:

Codi Significat Codi Significat
0 Informe correcte 3 No es pot llegir app.log
1 Error inesperat 4 enviaments.csv absent, il·legible o buit
2 Ús incorrecte (03-05) 5 Destí d'informes no escrivible

L'avantatge és concret: qui invoqui l'script podrà reaccionar de manera diferent a cada fallada sense analitzar missatges de text —per exemple, amb un case $? que avisi l'equip de dades només quan el codi sigui 4—. Aquest case és un avançament de 04-05. A més, hi ha una eina que avorta l'script automàticament davant de qualsevol ordre fallida, set -e, juntament amb set -u i trap: són l'arsenal complet de la gestió d'errors i s'estudien a 05-03. De moment, les guardes explícites et donen més control i t'obliguen a pensar què ha de passar en cada cas.

  1. informe-diari.sh valida el seu entorn

Ho reunim tot; aquesta versió ja es pot posar en producció sense por:

#!/usr/bin/env bash
# informe-diari.sh - Resum diari d'activitat de Veloz Envíos
# Autor : Joan Costa <[email protected]>   ·   Ús: informe-diari.sh
# Codis : 0 correcte | 3 log il·legible | 4 CSV invàlid | 5 destí no escrivible

# --- Constants ---------------------------------------------------------
readonly CAMI_APP_LOG="/var/log/veloz/app.log"
readonly CAMI_CSV="/srv/veloz/dades/enviaments.csv"
readonly DIR_INFORMES="$HOME/veloz-ops/logs"
readonly LLINDAR_ERRORS=50
readonly SERVIDOR="srv-veloz-01"

# --- Validacions prèvies (clàusules de guarda) ------------------------
[[ -r "$CAMI_APP_LOG" ]] || { echo "ERROR: no llegeixo $CAMI_APP_LOG" >&2; exit 3; }
[[ -f "$CAMI_CSV" && -s "$CAMI_CSV" ]] \
    || { echo "ERROR: $CAMI_CSV no existeix o és buit" >&2; exit 4; }
mkdir -p "$DIR_INFORMES" || { echo "ERROR: no creo $DIR_INFORMES" >&2; exit 5; }
[[ -w "$DIR_INFORMES" ]] || { echo "ERROR: $DIR_INFORMES no escrivible" >&2; exit 5; }

# --- Dades i informe ---------------------------------------------------
total_errors=$(grep -c ERROR "$CAMI_APP_LOG")
total_enviaments=$(tail -n +2 "$CAMI_CSV" | wc -l)

echo "  INFORME DIARI - VELOZ ENVIOS ($SERVIDOR)  ·  $(date '+%F %T')"
echo "-- Errors a app.log --"
if (( total_errors == 0 )); then
    echo "Sense errors registrats. Jornada neta."
elif (( total_errors < LLINDAR_ERRORS )); then
    echo "$total_errors errors (llindar: $LLINDAR_ERRORS). Dins del normal."
else
    echo "AVÍS: $total_errors errors, per sobre del llindar de $LLINDAR_ERRORS."
    echo "Últims 3 errors registrats:"
    grep ERROR "$CAMI_APP_LOG" | tail -3
fi

echo "-- Enviaments per estat ($total_enviaments en total) --"
tail -n +2 "$CAMI_CSV" | cut -d, -f5 | sort | uniq -c | sort -rn

# --- Qualitat de la dada ----------------------------------------------
grep -q ',lliurat,' "$CAMI_CSV" \
    || echo "ATENCIÓ: cap enviament lliurat avui. Dades incompletes?" >&2
exit 0

Executat en un dia amb incidències:

  INFORME DIARI - VELOZ ENVIOS (srv-veloz-01)   ·   2026-08-03 07:00:04
-- Errors a app.log --
AVÍS: 73 errors, per sobre del llindar de 50.
Últims 3 errors registrats:
2026-08-03 06:41:02 [ERROR] timeout consultando ruta de reparto id=88213
2026-08-03 06:44:57 [ERROR] fallo al geocodificar dirección de Bilbao
2026-08-03 06:52:11 [ERROR] respuesta 500 del proveedor de rutas
-- Enviaments per estat (1247 en total) --
    981 lliurat
    148 en_repartiment
    118 incidencia

El salt qualitatiu és real. L'script ara comprova el seu entorn abans de treballar i avorta amb un codi específic si alguna cosa falla; interpreta el recompte d'errors en lloc de limitar-se a imprimir-lo; hi afegeix context útil (els tres últims errors) només quan cal, per no omplir de soroll els dies tranquils; i detecta una anomalia de dades que cap operador no hauria notat llegint xifres. Això ja no és una canonada desada en un fitxer: és una eina d'operacions.

Errors Habituals i Consells

  • Oblidar el fi. Bash dona error de sintaxi: final de fitxer inesperat, i assenyala l'última línia de l'script en lloc de l'if culpable.
  • Oblidar el ; abans de then. Produeix error de sintaxi prop de l'element inesperat 'then'.
  • Escriure elseif o else if. A Bash és elif. (else if funciona, però obre un if nou que necessita el seu propi fi.)
  • Creure que if necessita claudàtors. if grep -q ... és vàlid i idiomàtic: els claudàtors són una ordre més.
  • Confondre 0 amb fals. A Bash 0 és èxit, és a dir, cert per a un if.
  • Comparar números amb > dins de [[ ]]. És comparació textual (03-03). Fes servir -gt o (( )).
  • Imbricar tres nivells d'if. Inverteix les condicions i fes servir clàusules de guarda.
  • Validar sense avisar. Un if que detecta el problema i no imprimeix res ni surt amb codi d'error deixa l'script fallant en silenci: tot error va a stderr amb >&2 i amb exit N.

Exercicis

Exercici 1 — D'imbricat a guardes. Reescriu aquest bloc amb clàusules de guarda, missatges a stderr i codis de sortida diferents (3 per al directori, 4 per al fitxer, 5 per al permís).

if [[ -d /srv/veloz/dades ]]; then
    if [[ -f /srv/veloz/dades/enviaments.csv ]]; then
        if [[ -r /srv/veloz/dades/enviaments.csv ]]; then
            wc -l /srv/veloz/dades/enviaments.csv
        fi
    fi
fi

Exercici 2 — Comprovació prèvia d'eines. Escriu un fragment per a informe-diari.sh que verifiqui que existeixen grep, cut, sort i uniq abans de començar, avisi per stderr de quina falta i surti amb codi 6. A més, si existeix column, ha d'activar una variable format_taula="si" per presentar l'informe alineat; si no, deixar-la en "no" i continuar sense avortar.

Exercici 3 — Classificar la jornada. A partir de la variable incidencies, imprimeix «jornada excel·lent» si és 0, «jornada normal» entre 1 i 50, «revisar rutes» entre 51 i 150, i «escalar a direcció» a partir de 151. Explica per què no cal escriure els límits inferiors de cada rang.

Solucions

Solució a l'Exercici 1

readonly DIR_DADES="/srv/veloz/dades"
readonly CAMI_CSV="$DIR_DADES/enviaments.csv"
[[ -d "$DIR_DADES" ]] || { echo "ERROR: no existeix $DIR_DADES" >&2; exit 3; }
[[ -f "$CAMI_CSV" ]]  || { echo "ERROR: no existeix $CAMI_CSV" >&2; exit 4; }
[[ -r "$CAMI_CSV" ]]  || { echo "ERROR: no puc llegir $CAMI_CSV" >&2; exit 5; }
wc -l "$CAMI_CSV"

A més d'eliminar els tres nivells d'indentació, aquesta versió corregeix una errada greu de l'original: el bloc imbricat acabava amb codi 0 encara que no hagués fet res. Un cron que l'executés hauria donat per bo un dia en què el fitxer de dades ni tan sols existia. Fixa't també que els camins s'han extret a constants, aplicant 03-02.

Solució a l'Exercici 2

for eina in grep cut sort uniq; do
    command -v "$eina" >/dev/null 2>&1 \
        || { echo "ERROR: falta l'eina '$eina'" >&2; exit 6; }
done
if command -v column >/dev/null 2>&1; then
    format_taula="si"
else
    format_taula="no"; echo "AVÍS: 'column' no disponible" >&2
fi

El for és un avançament de 04-01, però encaixa de manera natural aquí; sense ell hauries de repetir el mateix if quatre vegades. L'important és el criteri de disseny: grep, cut, sort i uniq són dependències dures —sense elles l'script no pot treballar, així que avorta—, mentre que column és una millora opcional —la seva absència degrada la presentació però no impedeix generar l'informe, així que només avisa—. Distingir-les és una de les decisions que separen un script robust d'un de fràgil.

Solució a l'Exercici 3

if   (( incidencies == 0 ));   then echo "Jornada excel·lent: cap incidència"
elif (( incidencies <= 50 ));  then echo "Jornada normal: $incidencies"
elif (( incidencies <= 150 )); then echo "Revisar rutes: $incidencies"
else                                echo "Escalar a direcció: $incidencies"
fi

Com que s'avaluen de dalt a baix i s'aturen a la primera condició certa, els límits inferiors són implícits: la branca <= 150 només s'assoleix si les dues anteriors han fallat, així que ja se sap que el valor és més gran que 50. Escriure (( incidencies > 50 && incidencies <= 150 )) seria correcte però redundant, i afegiria una oportunitat més d'equivocar-se en ajustar els llindars.

Conclusió

El teu script ja pensa. Domines la sintaxi d'if/then/elif/else/fi amb els seus paranys de puntuació; i sobretot entens la idea central del capítol, que if avalua un codi de sortida i no cap booleà, cosa que converteix [[ ]] en una ordre més i obre la porta a if grep -q ... o if command -v ..., que és com escriu Bash la gent que fa anys que s'hi dedica. Saps quan un && d'una línia és més llegible que un if de tres; negues amb ! sense ambigüitat; valides fitxers i variables abans de fer-los servir; i has canviat la imbricació per clàusules de guarda que deixen la lògica útil sense indentar, amb un codi de sortida propi per a cada fallada.

informe-diari.sh valida el seu entorn, interpreta el llindar, hi afegeix context només quan cal i detecta anomalies a les dades. Però li queda una limitació de fons: sempre fa exactament el mateix. Analitza el dia actual i totes les ciutats, i no hi ha manera de demanar-li l'informe del dilluns passat o només el de Bilbao sense editar el fitxer. Continua sent, en aquest sentit, tan rígid com l'àlies que vam deixar enrere al Mòdul 2.

A la lliçó 03-05 això s'acaba. Aprendràs a rebre informació des de fora: paràmetres posicionals $1 i $@, la diferència crítica entre "$@" i "$*", arguments amb nom com --data i --ciutat, getopts, valors per defecte i entrada interactiva amb read. En acabar, informe-diari.sh --data 2026-07-28 --ciutat Bilbao serà una ordre real, amb el seu --help i tot.

Curs de Programació en Bash

Mòdul 1: Introducció a Bash

Mòdul 2: Ordres Bàsiques de Bash

Mòdul 3: Fonaments de Scripting

Mòdul 4: Scripting Intermedi

Mòdul 5: Tècniques Avançades de Scripting

Mòdul 6: Treballar amb Eines Externes

Mòdul 7: Automatització i Programació

Mòdul 8: Bones Pràctiques i Optimització

Mòdul 9: Projectes del Món Real

© Copyright 2026. Tots els drets reservats