A les tres lliçons anteriors has escrit if, case i [[ ]] guiant-te per la semblança amb altres llenguatges. Funcionava, però no sabies per què. Toca arreglar-ho començant per la idea que en Bash ho governa tot i que gairebé cap tutorial no enuncia amb claredat: aquí no es ramifica segons un valor booleà, es ramifica segons el codi de sortida d'una ordre. if no espera una expressió: espera una ordre, l'executa i mira si ha retornat 0; tota la resta —[ ], [[ ]], (( ))— són ordres que existeixen per produir aquell 0 o 1. Amb aquella peça al seu lloc, la resta encaixa sola. Al final, revisio_salut.sh sabrà dir OK, AVÍS o CRÍTIC, i naixerà purgar_releases.sh.

Contingut

  1. La condició és un codi de sortida
  2. if, elif, else
  3. Els tres tipus de test i quin fer servir
  4. Taula completa d'operadors
  5. Expressions regulars amb =~ i BASH_REMATCH
  6. case i els seus tres terminadors
  7. Bucles: for, while, until
  8. break, continue i select
  9. Curtcircuits && i ||, i el seu parany
  10. Bucles i rendiment: quan vols awk
  11. Aplicació: nivells d'alerta i purgar_releases.sh

  1. La condició és un codi de sortida

operador@srv-tramontana:~$ if grep -q 'db_timeout' /var/log/tramontana/errors.log
> then echo "hi ha timeouts de base de dades"; fi
hi ha timeouts de base de dades

No hi ha cap == true enlloc. if va executar grep -q, grep va retornar 0 perquè va trobar coincidències, i 0 significa èxit, així que es va entrar al then. Si no hagués trobat res hauria retornat 1 i s'hauria saltat el bloc.

Tres conseqüències que convé tenir presents des d'ara:

  • Qualsevol ordre pot ser una condició: if ping -c1 -W2 10.0.2.15, if [[ -f fitxer ]], if la_meva_funcio. Totes són el mateix. I el [ clàssic és una ordre de debò, amb el seu executable a /usr/bin/[; per això [ $a = $b ] necessita espais: són arguments separats.
  • La lògica està invertida respecte als llenguatges amb booleans: 0 és cert. Si escrius una funció que comprova alguna cosa, ha de retornar 0 quan la resposta és «sí».

  1. if, elif, else

us_disc=$(df --output=pcent / | tail -1 | tr -dc '0-9')
if (( us_disc >= 90 )); then
    nivell="CRITIC"
elif (( us_disc >= 80 )); then
    nivell="AVIS"
else
    nivell="OK"
fi

La sintaxi exigeix then i fi; el ; abans de then permet posar-lo a la mateixa línia. Els elif s'avaluen en ordre i el primer que encerta guanya, així que les condicions van de la més restrictiva a la més laxa: si intercanviessis els dos primers blocs, un disc al 95 % diria AVÍS. Per negar, ! al davant: if ! systemctl is-active --quiet tramontana; then .... I compte amb if [ $? -eq 0 ], gairebé sempre redundant: posa l'ordre directament al if.

  1. Els tres tipus de test i quin fer servir

Forma Què és Quan fer-la servir
[ ... ] o test ... Ordre POSIX Només si l'script ha de córrer en dash
[[ ... ]] Paraula reservada de Bash Sempre, per defecte
(( ... )) Avaluació aritmètica Comparacions i comptes numèrics

La diferència entre [ ] i [[ ]] no és cosmètica. [[ ]] és sintaxi del mateix shell, no una ordre, així que Bash no divideix en paraules ni expandeix globs a dins:

operador@srv-tramontana:~$ f="informe agost.txt"
operador@srv-tramontana:~$ [ -f $f ] && echo existeix
-bash: [: informe: binary operator expected
operador@srv-tramontana:~$ [[ -f $f ]] && echo existeix || echo "no existeix, però sense error"
no existeix, però sense error

Amb [ ] la variable sense cometes es va partir en dos arguments i l'ordre ni tan sols va poder avaluar-se. Amb [[ ]] no cal entrecometar, i a més admet &&, ||, patrons i =~, que [ ] no té. Per a números, (( )) és molt més llegible: compara if (( errors > 20 && connexions >= 200 )) amb [ "$errors" -gt 20 -a "$connexions" -ge 200 ]. Recorda de 04-02 que dins de (( )) les variables van sense $.

  1. Taula completa d'operadors

Grup Operador Cert si…
Fitxers -e / -f / -d / -L Existeix / és fitxer regular / és directori / és enllaç
Fitxers -s / -r / -w / -x No és buit / permís de lectura, escriptura, execució
Fitxers a -nt b / a -ot b a és més nou / més vell que b
Cadenes -z "$s" / -n "$s" És buida / no és buida
Cadenes $s == patró Coincideix amb el patró de globbing (sense cometes a la dreta)
Cadenes == / != / < / > Iguals / diferents / ordre lexicogràfic segons la locale
Cadenes $s =~ regex Coincideix amb l'expressió regular (apartat 5)
Números -eq -ne -lt -le -gt -ge Igual, diferent, menor, menor o igual, major, major o igual
Números Dins de (( )) == != < <= > >=, i && || !

La confusió clàssica mereix un avís en negreta: -eq és per a números i == per a cadenes. [[ "01" -eq "1" ]] és cert (numèricament iguals) però [[ "01" == "1" ]] és fals (cadenes diferents). Fer servir l'operador equivocat dona resultats correctes durant mesos i després falla amb un zero a l'esquerra o amb una versió com ara 3.10.

Un altre detall que sorprèn: dins de [[ ]], el costat dret de == no s'entrecometa si vols que sigui un patró:

operador@srv-tramontana:~$ v="3.2.1"; [[ $v == 3.2.* ]] && echo "branca 3.2"
branca 3.2
operador@srv-tramontana:~$ [[ $v == "3.2.*" ]] && echo "branca 3.2" || echo "sense coincidència"
sense coincidència

Les cometes converteixen el patró en text literal: és la font número u de «la meva comparació no funciona i no sé per què».

  1. Expressions regulars amb =~ i BASH_REMATCH

=~ compara amb una expressió regular estesa (l'ERE de 03-02) i, a més de dir sí o no, deixa les captures a la matriu BASH_REMATCH: la posició 0 és la coincidència completa i les següents, els grups entre parèntesis.

validar_data() {
    local data="$1"
    if [[ $data =~ ^([0-9]{4})-([0-9]{2})-([0-9]{2})$ ]]; then
        printf 'vàlida: any %s, mes %s, dia %s\n' \
            "${BASH_REMATCH[1]}" "${BASH_REMATCH[2]}" "${BASH_REMATCH[3]}"
        return 0
    fi
    printf 'format invàlid: %s\n' "$data" >&2
    return 1
}
operador@srv-tramontana:~$ validar_data "2026-08-18"
vàlida: any 2026, mes 08, dia 18
operador@srv-tramontana:~$ validar_data "18/08/2026"; echo "codi: $?"
format invàlid: 18/08/2026
codi: 1

Amb això valides la columna data de reserves.csv abans de fiar-te'n. Dues regles d'or: la regex no va entre cometes —si l'entrecometes es torna text literal, igual que amb ==— i si la guardes en una variable, fes-la servir també sense cometes: [[ $f =~ $PATRO ]].

  1. case i els seus tres terminadors

case compara una cadena contra patrons de globbing —no expressions regulars— i executa la primera branca que hi encaixi.

classificar_codi() {
    case "$1" in
        2*)      echo "èxit" ;;               3*) echo "redirecció" ;;
        404)     echo "no trobat" ;;          4*) echo "error del client" ;;
        500|503) echo "error del servidor" ;;  *) echo "desconegut" ;;
    esac
}
operador@srv-tramontana:~$ for c in 200 404 418 503 999; do
>     printf '%s->%s ' "$c" "$(classificar_codi "$c")"; done; echo
200->èxit 404->no trobat 418->error del client 503->error del servidor
999->desconegut

L'ordre importa: 404 va abans que 4* perquè guanya el primer que hi encaixa. | separa alternatives i *) és el cas per defecte, que va l'últim i convé posar sempre. Els tres terminadors:

Terminador Efecte
;; Acaba el case. El que faràs servir el 99 % de les vegades
;& Cau a la branca següent sense comprovar-ne el patró (el fallthrough de C)
;;& Continua comprovant els patrons restants i executa els que hi encaixin

;;& és rar però útil per a etiquetes acumulatives; ;& és perillós perquè un lector distret no el veu. Fes-los servir només amb un comentari que ho justifiqui.

  1. Bucles: for, while, until

for sobre llistes i globs

for release in 3.1.0 3.2.0 3.2.1; do printf 'release %s\n' "$release"; done
for fitxer in /var/log/tramontana/*.log; do
    [[ -e $fitxer ]] || continue      # protegeix del glob sense coincidències
    printf '%-40s %s línies\n' "$fitxer" "$(wc -l < "$fitxer")"
done

La segona forma —iterar sobre un glob— és la correcta per recórrer fitxers, i porta dos avisos. El primer: si el glob no troba res, Bash deixa el patró literal i el bucle s'executa una vegada amb /var/log/tramontana/*.log com a valor; d'aquí el [[ -e ... ]] || continue, o bé shopt -s nullglob de 03-02. El segon, més important:

No escriguis mai for f in $(ls).

Ho veuràs a internet constantment i està malament per tres motius acumulatius: ls retorna text que el shell parteix per espais, així que «informe agost.txt» es converteix en dos elements; els caràcters especials del nom s'expandeixen com a globs; i ls pot acolorir o alinear segons opcions i àlies. El glob no té cap d'aquells problemes perquè Bash treballa amb noms de fitxer reals, no amb text. I existeix la forma aritmètica, for (( i = 0; i < ${#versions[@]}; i++ )), per a quan l'índex importa.

while i until

while repeteix mentre la condició retorni 0; until repeteix fins que retorni 0. Són la mateixa estructura amb la condició negada, i until guanya quan la frase natural és «espera fins que»:

intents=0
until curl -sf -o /dev/null --max-time 3 "http://10.0.2.15:8080/salut"; do
    (( ++intents ))
    (( intents >= 10 )) && { echo "l'aplicació no arrenca" >&2; exit 69; }
    sleep 2
done
printf 'ha respost després de %d intents\n' "$intents"

Aquell bucle és el que necessita un desplegament per esperar que el servei estigui a punt, i el reutilitzarem a 04-07. Ja coneixes a més el while read de 04-03, que és el tercer ús habitual.

  1. break, continue i select

break surt del bucle i continue salta a la iteració següent. Tots dos accepten un número que indica quants nivells afecten: break 2 surt de dos bucles imbricats de cop.

for dir in /opt/tramontana/releases/*/; do
    for fitxer in "$dir"*.conf; do
        [[ -e $fitxer ]] || continue
        grep -q 'db_password' "$fitxer" || continue
        printf 'credencial a %s\n' "$fitxer" >&2
        break 2                     # deixa de cercar a tots els releases
    done
done
PS3="Tria un release per desplegar: "       # select: menú numerat de franc
select v in 3.2.1 3.3.0 cancella; do
    [[ -n $v ]] || { echo "opció no vàlida" >&2; continue; }
    [[ $v == cancella ]] && break
    printf 'desplegant %s\n' "$v"; break
done

Si et trobes amb tres nivells d'imbricació, això no és un problema de bucles: és una funció que encara no has escrit, i ho veurem a 04-05. Quant a select, és un bucle i sense break no acaba mai; PS3 és el text de l'avís i $REPLY guarda el que va teclejar l'usuari.

  1. Curtcircuits && i ||, i el seu parany

Ja els fas servir des de 02-01: A && B executa B només si A ha tingut èxit; A || B executa B només si A ha fallat. Com a condicional d'una línia són insuperables: [[ -d $DIR ]] || mkdir -p "$DIR", o command -v shellcheck >/dev/null || { echo "falta shellcheck" >&2; exit 69; }. Però molta gent escriu condicio && accio || altra_cosa creient que és un if/else, i no ho és:

operador@srv-tramontana:~$ [[ 5 -gt 3 ]] && grep -q zzz /etc/hostname || echo "no!"
no!

La clau: || no mira la condició, mira el resultat de l'acció anterior. Si l'acció falla per qualsevol motiu —un mkdir sense permís, un grep sense coincidències— la branca del || s'executa a més de la del &&. La regla: encadena && i || només quan l'acció no pugui fallar; tan bon punt hi hagi un else de debò, fes servir if.

  1. Bucles i rendiment: quan vols awk

Aquest apartat t'estalviarà hores d'espera. Comptem les 14 respostes 500 d'acces.log de dues maneres:

operador@srv-tramontana:~$ time (n=0; while read -r l; do
>     grep -q ' 500 ' <<<"$l" && (( ++n )); done < /var/log/tramontana/acces.log
> echo "$n")
14
real    0m1,873s
operador@srv-tramontana:~$ time awk '$5 == 500 { n++ } END { print n+0 }' \
>     /var/log/tramontana/acces.log
14
real    0m0,006s

Tres-centes vegades més ràpid, i només amb 412 línies. La causa no és que Bash interpreti a poc a poc: és que el bucle llança un procés grep per cada línia, i crear un procés costa uns mil·lisegons que es multipliquen. Amb les 10.000 línies que tindrà acces.log en un mes, el bucle trigaria gairebé un minut i awk continuaria en centèsimes. La regla:

  • Si el bucle processa text línia a línia, probablement vols awk (o grep, sed, sort), que fan una sola passada dins d'un únic procés.
  • Si el bucle ha de cridar una ordre externa a cada volta —curl, systemctl, tar— aleshores sí que és feina de Bash, perquè no hi ha alternativa. I si necessites Bash sobre text, almenys no llancis processos a dins: read amb diversos noms, case i expansió de paràmetres fan molt sense sortir del shell.

  1. Aplicació: nivells d'alerta i purgar_releases.sh

revisio_salut.sh per fi decideix. Substituïm el bloc d'impressió per comprovacions amb nivell:

# --- Avaluació -----------------------------------------------------------
nivell_global="OK"
pitjor() {   # eleva el nivell global si el nou és més greu
    case "$1" in
        CRITIC) nivell_global="CRITIC" ;;
        AVIS)   [[ $nivell_global == OK ]] && nivell_global="AVIS" ;;
    esac
}
case "$codi_http" in
    200) estat_http="OK" ;;
    000) estat_http="CRITIC" ;;    # curl no va arribar ni a connectar
    *)   estat_http="AVIS" ;;
esac
if   (( us_disc >= 90 ));           then estat_disc="CRITIC"
elif (( us_disc >= LLINDAR_DISC )); then estat_disc="AVIS"
else                                     estat_disc="OK"; fi
if   (( errors_avui >= 50 )); then estat_err="CRITIC"
elif (( errors_avui >= 10 )); then estat_err="AVIS"
else                               estat_err="OK"; fi
pitjor "$estat_http"; pitjor "$estat_disc"; pitjor "$estat_err"

LC_ALL=C printf '%-10s %-8s %s\n' \
    "HTTP"   "$estat_http" "codi $codi_http" \
    "Disc"   "$estat_disc" "${us_disc}% usat (llindar ${LLINDAR_DISC}%)" \
    "Errors" "$estat_err"  "$errors_avui avui"
printf '\nEstat global: %s\n' "$nivell_global"
case "$nivell_global" in OK) exit 0 ;; AVIS) exit 1 ;; CRITIC) exit 2 ;; esac

operador@srv-tramontana:~$ ~/scripts/revisio_salut.sh; echo "codi: $?"
HTTP       OK       codi 200
Disc       OK       30% usat (llindar 80%)
Errors     OK       6 avui

Estat global: OK
codi: 0
operador@srv-tramontana:~$ ~/scripts/revisio_salut.sh -u 25 | tail -1
Estat global: AVIS

Amb aquells codis —0 correcte, 1 avís, 2 crític— l'script ja serveix per a monitoració: Nagios, Zabbix i companyia fan servir exactament aquella convenció. I l'script nou:

#!/usr/bin/env bash
#
# purgar_releases.sh - Elimina releases antics conservant l'actiu,
#                      l'anterior i els més nous.
# Autor : Operador de sistemes <operador@srv-tramontana>  Data: 2026-08-18
# Ús    : purgar_releases.sh [-n] [-v]   Sortida: 0 ok | 2 ús | 66 sense releases
set -euo pipefail
readonly BASE="/opt/tramontana"
readonly DIR_RELEASES="$BASE/releases"
DRY_RUN=0; VERBOSE=0
# log(), error() i executar() són les de 04-03; getopts amb ":hnv" fixa -n i -v.
actiu=$(basename "$(readlink -f "$BASE/app")")   # cap on apunta l'enllaç
log "release actiu: $actiu"

# Iterem sobre el glob (mai sobre ls) i ordenem amb sort -V, que sap que
# 3.10.0 va després de 3.9.0, cosa que sort a seques ignora. mapfile aboca
# cada línia en un element de la matriu, sense partir per espais.
llista=()
for d in "$DIR_RELEASES"/*/; do [[ -d $d ]] && llista+=("$(basename "$d")"); done
(( ${#llista[@]} )) || { error "no hi ha releases a $DIR_RELEASES"; exit 66; }
mapfile -t versions < <(printf '%s\n' "${llista[@]}" | sort -V)
index_actiu=-1        # posició de l'actiu, per saber quin és "l'anterior"
for (( i = 0; i < ${#versions[@]}; i++ )); do
    [[ ${versions[i]} == "$actiu" ]] && index_actiu=$i
done
(( index_actiu >= 0 )) || { error "l'actiu $actiu no és a la llista"; exit 66; }
for (( i = 0; i < ${#versions[@]}; i++ )); do
    v="${versions[i]}"
    (( i >= index_actiu - 1 )) && { log "conservo $v"; continue; }
    printf 'purgant %s (%s)\n' "$v" "$(du -sh "$DIR_RELEASES/$v" | cut -f1)"
    executar rm -rf -- "${DIR_RELEASES:?}/$v"
done
exit 0
operador@srv-tramontana:~$ ~/scripts/purgar_releases.sh -n -v
[2026-08-18 11:04:12] release actiu: 3.2.1
purgant 3.1.0 (97M)
[simulat] rm -rf -- /opt/tramontana/releases/3.1.0
[2026-08-18 11:04:12] conservo 3.2.0
[2026-08-18 11:04:12] conservo 3.2.1
[2026-08-18 11:04:12] conservo 3.3.0

Just el que volíem: només 3.1.0 és purgable. Fixa't en dos detalls defensius: rm -rf -- "${DIR_RELEASES:?}/$v" fa servir ${VAR:?} de 04-02 perquè, si la variable quedés buida per un error, l'ordre avorti en comptes de convertir-se en rm -rf /; i -- evita que un nom de directori amb guionet inicial es llegeixi com a opció.

Errors Comuns i Consells

  • Oblidar els espais dins de [ ] o [[ ]]. [[$a == $b]] dona command not found: els claudàtors són ordres i paraules reservades, no sintaxi enganxada.
  • Barrejar -eq amb cadenes o == amb números. [[ "3.10" > "3.9" ]] és fals lexicogràficament: per a versions, sort -V; per a números, (( )). I no entrecometis el costat dret de == o =~, o el patró es torna text literal.
  • for f in $(ls). Es trenca amb espais i amb caràcters especials; fes servir sempre el glob. I protegeix el glob sense coincidències amb [[ -e $f ]] || continue o shopt -s nullglob, o el bucle correrà una vegada amb el patró literal.
  • Confiar en cond && accio || altra com a if/else. Si accio falla, s'executen les dues branques. I imbricar tres bucles és el senyal inequívoc que falta una funció.
  • Consell: compara números sempre amb (( )): és més llegible i no confondràs l'operador. Posa sempre la branca *) en un case, encara que només sigui per registrar «valor inesperat». I abans d'escriure un bucle que processi text, pregunta't si awk ho faria d'una passada: gairebé sempre la resposta és sí.

Exercicis

Exercici 1. Escriu es_versio_valida(), que retorni 0 si el seu argument té el format N.N.N (un o més dígits per tram) i 1 si no, fent servir =~, i que imprimeixi els tres números per separat. Prova-la amb 3.2.1, 3.10.0, 3.2 i v3.2.1.

Exercici 2. Escriu ~/scripts/resum_franges.sh, que sobre acces.log classifiqui cada petició en matinada (00-05), matí (06-13), tarda (14-21) o nit (22-23) amb case, i mostri peticions i errors per franja. Fes-ho sense llançar processos dins del bucle i compara el temps amb awk.

Exercici 3. En Luis ha escrit aquest fragment per decidir si cal purgar. Troba-hi els quatre defectes i reescriu-lo.

lliure=`df / | tail -1 | awk '{print $5}'`
if [ $lliure > 80 ]; then
  echo "purgant" && rm -rf /opt/tramontana/releases/* || echo "error"
fi

Solucions

Solució 1.

es_versio_valida() {
    local v="$1"
    # ^ i $ ancoren la coincidència a TOTA la cadena: sense ells, "v3.2.1x"
    # també hi encaixaria. Els parèntesis creen els grups de BASH_REMATCH.
    [[ $v =~ ^([0-9]+)\.([0-9]+)\.([0-9]+)$ ]] || return 1
    printf 'major=%s menor=%s pedaç=%s\n' \
        "${BASH_REMATCH[1]}" "${BASH_REMATCH[2]}" "${BASH_REMATCH[3]}"
}
for v in 3.2.1 3.10.0 3.2 v3.2.1; do
    if es_versio_valida "$v"; then printf '  %-8s vàlida\n' "$v"
    else printf '  %-8s NO vàlida\n' "$v"; fi
done

operador@srv-tramontana:~$ bash /tmp/versions.sh
major=3 menor=2 pedaç=1
  3.2.1    vàlida
major=3 menor=10 pedaç=0
  3.10.0   vàlida
  3.2      NO vàlida        # falta un tram
  v3.2.1   NO vàlida        # l'àncora ^ rebutja la 'v'

Els punts van escapats (\.) perquè en una regex . significa «qualsevol caràcter»; sense escapar, 3x2y1 també seria vàlida. I fixa't en com la funció es fa servir directament al if: és l'apartat 1 en acció. (Sense return 0 explícit, la funció retorna el codi del printf, que és 0.)

Solució 2.

#!/usr/bin/env bash
#
# resum_franges.sh - Peticions i errors per franja horària a acces.log.
# Autor : Operador de sistemes <operador@srv-tramontana>  Data: 2026-08-18
set -euo pipefail
readonly LOG="${1:-/var/log/tramontana/acces.log}"
declare -A peticions errors
# read trosseja la línia sense llançar processos: l'hora és el segon camp i
# el codi el cinquè. ${hora%%:*} deixa només HH, i el 10# força base
# decimal perquè "08" i "09" serien octal invàlid (04-02).
while read -r _data hora _metode _ruta codi _resta; do
    case "$(( 10#${hora%%:*} ))" in
        [0-5])        franja="matinada" ;;
        [6-9]|1[0-3]) franja="matí"     ;;
        1[4-9]|2[01]) franja="tarda"    ;;
        *)            franja="nit"      ;;
    esac
    peticions["$franja"]=$(( ${peticions["$franja"]:-0} + 1 ))
    [[ $codi == [45]* ]] && errors["$franja"]=$(( ${errors["$franja"]:-0} + 1 ))
done < "$LOG"

LC_ALL=C printf '%-12s %12s %8s\n' FRANJA PETICIONS ERRORS
for f in matinada matí tarda nit; do
    LC_ALL=C printf '%-12s %12d %8d\n' "$f" "${peticions[$f]:-0}" "${errors[$f]:-0}"
done
operador@srv-tramontana:~$ time ~/scripts/resum_franges.sh
FRANJA          PETICIONS   ERRORS
matinada               88       15
matí                  147        3
tarda                 141        4
nit                    36        1
real  0m0,048s

Els 15 errors de matinada són els de la franja de les 03:00 que ja coneixes, i els 23 totals quadren. La versió en awk —una línia amb substr($2,1,2)+0 i el ternari per a la franja— triga 0,007 s: set vegades menys tot i no haver-hi processos dins del bucle, només pel cost de l'intèrpret. Però fixa't en l'escala: centèsimes contra centèsimes. La diferència catastròfica apareix quan hi ha $(...) dins del bucle, no pel bucle en si. I nota el patró [45]* del case: globbing, no regex.

Solució 3. Els quatre defectes:

  1. ` ` en comptes de $( ). Les cometes invertides no s'imbriquen, tenen regles d'escapament diferents i són il·legibles. A més $5 de df inclou el %, així que la comparació numèrica rep 30%.
  2. [ $lliure > 80 ]. Dins de [ ], > no compara: és una redirecció, així que aquella ordre crea un fitxer anomenat 80 al directori actual i el test sempre és cert. Cal fer servir -gt o, millor, (( )).
  3. rm -rf /opt/tramontana/releases/* esborra tot, inclòs el release actiu al qual apunta l'enllaç app: l'aplicació cau a l'acte.
  4. echo ... && rm ... || echo "error". Si el rm falla parcialment s'imprimeixen tots dos missatges i l'script no retorna cap codi útil. A més no hi ha --dry-run ni registre de què s'ha esborrat.
us_disc=$(df --output=pcent / | tail -1 | tr -dc '0-9')
if (( us_disc > 80 )); then
    printf 'disc al %d%%, purgant releases antics\n' "$us_disc" >&2
    if ~/scripts/purgar_releases.sh -v; then printf 'purga completada\n' >&2
    else printf 'la purga ha fallat (codi %d)\n' "$?" >&2; exit 1; fi
fi

La lògica destructiva es delega a purgar_releases.sh, que ja sap què conservar, té --dry-run i registra el que fa. Reutilitzar en comptes de reescriure també és una estructura de control.

Conclusió

Ja no escrius condicionals per imitació: saps què passa per sota.

  • Tens clar que la condició és un codi de sortida, que 0 és èxit, i que qualsevol ordre —inclosa una funció teva— pot anar en un if.
  • Fas servir if/elif/else ordenant de la condició més restrictiva a la més laxa, i tries entre els tres tipus de test: [[ ]] per defecte perquè no divideix paraules i admet patrons, (( )) per al que és numèric, i [ ] només si necessites POSIX.
  • Maneges la taula completa d'operadors de fitxer, cadena i número; no confons -eq amb == ni entrecometes el costat dret d'un patró; valides amb =~ i BASH_REMATCH, amb la regex sense cometes i ancorada amb ^ i $; i escrius case amb patrons de globbing, el *) obligatori i els terminadors ;;, ;& i ;;&.
  • Recorres amb for sobre globs, mai sobre $(ls), amb la forma aritmètica quan l'índex importa, i amb while/until per a esperes i lectures; controles el flux amb break N, continue N i select. Coneixes el parany de cond && accio || altra i la regla de rendiment: si el bucle processa text, probablement vols awk, mesurat en un cas real.
  • I els teus scripts decideixen: revisio_salut.sh retorna OK, AVÍS o CRÍTIC amb codis aptes per a monitoració, i purgar_releases.sh recorre els releases conservant l'actiu, l'anterior i els més nous.

Mira ara els scripts junts: log() està copiada tres vegades, error() també, l'embolcall executar() va per la seva segona còpia i el bloc de getopts és gairebé idèntic a tots. Cada vegada que en milloris un t'hauràs de recordar de millorar els altres, i algun dia no te'n recordaràs. A la lliçó següent, Funcions i Biblioteques, resolem això: quan extreure una funció i per què el nom és documentació, l'àmbit dinàmic de Bash i per què tota variable ha de ser local, les tres formes reals de retornar dades quan return només admet un número de 0 a 255, i com muntar lib/comuns.sh amb càrrega robusta, guardes d'inclusió i fins i tot un petit test que comprovi que la biblioteca funciona. En acabar-la, aquelles funcions duplicades viuran en un sol lloc.

Curs de Linux: De Principiant a Administrador de Sistemes

Mòdul 1: Introducció a Linux

Mòdul 2: Comandes Bàsiques de Linux

Mòdul 3: Habilitats Avançades en la Línia de Comandes

Mòdul 4: Scripting en Shell

Mòdul 5: Administració del Sistema

Mòdul 6: Xarxes i Seguretat

Mòdul 7: Temes Avançats

Mòdul 8: Projectes Pràctics

© Copyright 2026. Tots els drets reservats