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
- Sintaxi d'
if/then/fi - La idea clau:
ifavalua un codi de sortida elseielif- Condicionals d'una línia
ifsobre ordres reals- Condicions compostes, negació i validació d'entrada
- Imbricació, clàusules de guarda i sortida primerenca
- Codis de sortida diferents segons l'error
informe-diari.shvalida el seu entorn
- Sintaxi d'
if / then / fi
if / then / fiL'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:
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—.
- La idea clau:
if avalua un codi de sortida
if avalua un codi de sortidaAquí 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.
else i elif
else i elifelse 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).
- 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 compacteA 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.
if sobre ordres reals
if sobre ordres realsAquest é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"; fiLa 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.
- 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 testCompte 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
fiAquest 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.
- 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'assabentaEl 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 -cLa 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.
- 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.
informe-diari.sh valida el seu entorn
informe-diari.sh valida el seu entornHo 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 0Executat 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 incidenciaEl 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 donaerror de sintaxi: final de fitxer inesperat, i assenyala l'última línia de l'script en lloc de l'ifculpable. - Oblidar el
;abans dethen. Produeixerror de sintaxi prop de l'element inesperat 'then'. - Escriure
elseifoelse if. A Bash éselif. (else iffunciona, però obre unifnou que necessita el seu propifi.) - Creure que
ifnecessita 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-gto(( )). - Imbricar tres nivells d'
if. Inverteix les condicions i fes servir clàusules de guarda. - Validar sense avisar. Un
ifque 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>&2i ambexit 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
fiExercici 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
fiEl 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"
fiCom 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
- Què és Bash?
- Configurar el teu Entorn
- Navegació Bàsica per la Línia d'Ordres
- Entendre el Shell
- Trobar Ajuda: man, help i --help
Mòdul 2: Ordres Bàsiques de Bash
- Operacions amb Fitxers i Directoris
- Ordres de Processament de Text
- Permisos i Propietat dels Fitxers
- Redirecció i Canonades
- Comodins i Expansió de Rutes
- Historial i Dreceres de Teclat
Mòdul 3: Fonaments de Scripting
- Crear i Executar un Script
- Variables i Constants
- Operadors Bàsics
- Sentències Condicionals
- Arguments i Entrada de l'Usuari
- Cometes, Expansió i Substitució
Mòdul 4: Scripting Intermedi
- Bucles en Bash
- Funcions en Bash
- Arrays i Arrays Associatius
- Manipulació de Cadenes
- La Sentència case i els Menús Interactius
- Aritmètica i Càlculs Numèrics
Mòdul 5: Tècniques Avançades de Scripting
- Operacions Avançades amb Fitxers
- Gestió de Processos
- Gestió d'Errors i Depuració
- Expressions Regulars
- Entrada/Sortida Avançada: Descriptors i Here-Documents
- Scripts Modulars i Llibreries Reutilitzables
Mòdul 6: Treballar amb Eines Externes
Mòdul 7: Automatització i Programació
- Tasques Cron
- Automatitzar Tasques
- Scripts de Còpia i Restauració
- Monitoratge i Registre
- Serveis i Temporitzadors amb systemd
- Automatització Remota amb SSH
Mòdul 8: Bones Pràctiques i Optimització
- Escriure Codi Llegible
- Optimitzar Scripts en Bash
- Consideracions de Seguretat
- Control de Versions amb Git
- Anàlisi Estàtica amb ShellCheck i shfmt
- Proves Automatitzades amb Bats
- Portabilitat: POSIX sh enfront de Bashismes
