informe-diari.sh ja desa els seus camins en constants i els seus recomptes en variables, però continua sent cec: té un LLINDAR_ERRORS=50 que ningú no compara amb res, i dona per fet que enviaments.csv existeix. Perquè l'script comenci a decidir li cal abans un vocabulari: els operadors. En aquesta lliçó aprendràs a encadenar ordres segons el seu resultat, a comprovar si un fitxer existeix, a comparar números i cadenes —i a no confondre totes dues coses, que és l'error més freqüent de l'scripting a Bash—. Encara no escriurem cap if: això és la lliçó següent. Aquí construïm les peces que hi aniran a dins.

Contingut

  1. Panorama: quatre famílies d'operadors
  2. Operadors de control de flux: ;, &&, ||, !
  3. Agrupació: { ...; } davant de ( ... )
  4. L'ordre test i les seves tres formes
  5. Comparació numèrica davant de comparació de cadenes
  6. Cadenes: igualtat, ordre i buidor
  7. Operadors de fitxer
  8. Operadors lògics dins de [[ ]]
  9. Aritmètica com a operador: (( )) i $(( ))
  10. Precedència i parèntesis

  1. Panorama: quatre famílies d'operadors

A Bash conviuen diversos sistemes d'operadors que s'assemblen però no són intercanviables. Distingir-los des del principi t'estalviarà hores de desconcert:

Família Símbols On s'escriuen Què operen
Control de flux ; && || ! & Entre ordres Codis de sortida
Comparació (test) -eq -lt = -f -z Dins de [ ] o [[ ]] Cadenes, números i fitxers
Aritmètics + - * / % > < Dins de (( )) o $(( )) Només enters
Expansió ${var:-x} $(cmd) En qualsevol paraula Text

Els tres primers grups són el contingut d'aquesta lliçó; la família d'expansió és la de 03-06. La confusió típica és entre les files 2 i 3: > dins de [[ ]] compara text alfabèticament, mentre que > dins de (( )) compara números. Mateix símbol, semàntica diferent segons el context.

  1. Operadors de control de flux: ;, &&, ||, !

Tota ordre de Linux retorna un codi de sortida: 0 si ha tingut èxit, diferent de 0 si ha fallat (lliçó 03-01). Els operadors de control de flux encadenen ordres fent servir aquest codi com a criteri.

Operador Nom Comportament
; Seqüència Executa la segona sempre, passi el que passi amb la primera
&& I lògic Executa la segona només si la primera ha tingut èxit (codi 0)
|| O lògic Executa la segona només si la primera ha fallat (codi ≠ 0)
! Negació Inverteix el codi de sortida de l'ordre
& Segon pla Llança l'ordre sense esperar-la (02-06)
mkdir -p ~/veloz-ops/logs ; echo "continuem passi el que passi"
mkdir -p ~/veloz-ops/logs && echo "directori a punt"   # només si el mkdir ha anat bé
grep -q ERROR /var/log/veloz/app.log || echo "avui no hi ha errors"

Se'ls anomena d'avaluació mandrosa (o de curtcircuit) perquè Bash només executa la segona ordre si cal per conèixer el resultat: amb &&, si la primera falla ja sap que el conjunt falla; amb ||, si la primera funciona ja sap que el conjunt funciona.

L'idioma més valuós de l'apartat és ordre_critica || exit 1, que es llegeix «fes això o, si falla, avorta». És la manera més compacta d'escriure una validació:

cd /srv/veloz/dades || exit 1     # si el cd falla, no continuïs treballant a cegues
grep -q ERROR "$LOG" && echo "hi ha errors" || echo "tot net"
! grep -q ERROR "$LOG" && echo "el log està net"      # ! inverteix el resultat

La primera línia és important: un cd que falla i un script que continua és una recepta per al desastre, perquè les ordres següents s'executaran al directori equivocat.

Compte amb el patró de la segona línia: no és un if/else de veritat. Si echo "hi ha errors" fallés (per exemple, amb el disc ple), s'executaria també la branca del ||. Serveix per a casos trivials; per a lògica real fes servir if, que és 03-04.

  1. Agrupació: { ...; } davant de ( ... )

Quan el && o el || han d'afectar diverses ordres, cal agrupar-les. Bash n'ofereix dues formes amb una diferència crucial:

Forma On s'executa Sintaxi
{ cmd1; cmd2; } Al shell actual Necessita espais després de { i ; (o salt) abans de }
( cmd1; cmd2 ) En un subshell (procés fill) Sense requisits especials
[[ -f "$CAMI_CSV" ]] || { echo "Falta el CSV d'enviaments" >&2; exit 3; }

Aquesta línia és el patró de validació que escriuràs més vegades: si el fitxer no existeix, imprimeix un error a stderr (>&2, lliçó 02-04) i surt amb el codi 3. Les claus són imprescindibles aquí: sense elles, l'exit 3 s'executaria sempre.

Els espais de les claus no són opcionals: {echo hola;} dona error de sintaxi perquè Bash llegeix {echo com un sol nom d'ordre, i cal el ; (o un salt de línia) abans de la clau de tancament.

Els parèntesis creen un subshell, amb tot el que això implica segons 01-04: els canvis de directori i les variables definides a dins no en surten. Per això ( cd /srv/veloz/dades && wc -l enviaments.csv ) no altera el teu directori actual. Regla pràctica: claus per defecte (no costen cap procés) i parèntesis només quan vulguis aïllament deliberat.

  1. L'ordre test i les seves tres formes

Aquí arriba la peça central. Per preguntar «existeix aquest fitxer?» o «és aquest número més gran que aquell?», Bash fa servir test. I test és una ordre, no cap sintaxi especial: rep arguments i retorna 0 (cert) o 1 (fals).

Hi ha tres formes d'invocar-lo, històricament diferents: test -f "$csv" (forma original), [ -f "$csv" ] (sinònim POSIX) i [[ -f "$csv" ]] (paraula clau de Bash).

Que [ sigui una ordre i no un símbol explica l'error més desconcertant del principiant: [-f fitxer] falla amb no s'ha trobat l'ordre, perquè Bash busca un programa anomenat [-f. Els espais interiors són obligatoris, i el ] és literalment l'últim argument que [ exigeix rebre.

Aspecte [ ] (ordre test) [[ ]] (paraula clau de Bash)
Portabilitat POSIX: funciona a sh, dash, tots Només Bash, Zsh, Ksh
Variables sense cometes Perillós: si és buida, error de sintaxi Segur: no hi ha divisió en paraules
Comodins del valor S'expandeix, amb sorpreses No s'expandeix
&& i || interns No els admet; cal fer servir -a / -o Els admet amb precedència correcta
Comparació de patrons No Sí: [[ $ciutat == Val* ]]
Expressions regulars No Sí: [[ $l =~ ^ERROR ]] (05-04)
Comparació numèrica amb < No (compara text) Tampoc: fes servir -lt o (( ))

La diferència pràctica més important és la segona fila. Amb ciutat="", l'expressió [ $ciutat = "Valencia" ] produeix bash: [: =: s'esperava una expressió unària, mentre que [[ $ciutat = "Valencia" ]] funciona i simplement resulta falsa.

Amb [ ], la variable buida desapareix abans que test la vegi, i aquest rep [ = Valencia ], que no té cap sentit. Amb [[ ]], Bash no expandeix paraules a dins, així que no hi ha problema. La solució amb [ ] és posar sempre cometes ([ "$ciutat" = "Valencia" ]), però és una precaució que cal recordar cada vegada.

Recomanació d'aquest curs: fes servir [[ ]] sempre que escriguis per a Bash. Recorre a [ ] només quan l'script hagi de funcionar amb sh pur (08-07).

  1. Comparació numèrica davant de comparació de cadenes

Aquest és l'apartat que preveu més errors. Com que a Bash tot és text (03-02), el shell no pot endevinar si vols comparar números o paraules: l'hi has de dir amb l'operador.

Comparació Numèrica De cadenes
Igual / diferent -eq / -ne = o == / !=
Menor / menor o igual -lt / -le < / (no existeix)
Major / major o igual -gt / -ge > / (no existeix)

Els noms vénen de l'anglès i són fàcils de recordar: equal, not equal, less than, less or equal, greater than, greater or equal.

I ara l'error clàssic, que convé veure amb números concrets:

[[ $total_errors -gt $LLINDAR_ERRORS ]]   # més errors dels tolerables?
[[ 100 -gt 9 ]]   ; echo $?    # 0 → cert: 100 és més gran que 9
[[ "100" > "9" ]] ; echo $?    # 1 → FALS: "100" va abans que "9" alfabèticament

L'última comparació és alfabètica: caràcter a caràcter, l'1 va abans que el 9, així que "100" és «menor» que "9". És exactament el mateix criteri amb què un diccionari col·loca «casa» abans que «sabata». Si compares números amb > o <, el teu script funcionarà per casualitat amb xifres de la mateixa llargada i fallarà el dia que els errors passin de 9 a 100.

Regla: números amb -eq -ne -lt -le -gt -ge; text amb = == != < >. Sense excepcions. Dos avisos finals. Dins de [ ], els símbols < i > s'han d'escriure escapats (\<, \>) perquè si no Bash els interpreta com a redireccions i crea un fitxer anomenat 9. Dins de [[ ]] no cal. I els operadors numèrics exigeixen que tots dos costats siguin enters: si la variable és buida o conté text, obtindràs s'esperava una expressió entera.

  1. Cadenes: igualtat, ordre i buidor

A més de comparar, sovint necessites saber si una variable té contingut:

Operador Cert si…
-z "$var" / -n "$var" La cadena té longitud zero / no és buida
$a = $b / $a != $b Són idèntiques (a [[ ]], == és sinònim) / són diferents
$a < $b $a va abans alfabèticament (segons la configuració regional)
[[ -n $ciutat ]] || { echo "Falta indicar la ciutat" >&2; exit 2; }
[[ $ciutat == Val* ]] && echo "comença per Val"
[[ ${ciutat,,} == "valencia" ]] && echo "coincideix sense distingir majúscules"

La segona línia mostra una cosa que només [[ ]] permet: el costat dret sense cometes es tracta com un patró de comodins (02-05), de manera que Val* casa amb qualsevol ciutat que comenci per «Val». Si hi poses cometes ("Val*"), passa a ser text literal i no hi casarà.

Un detall sobre l'ordre alfabètic: depèn de LC_COLLATE. En català, [[ "àvia" < "banc" ]] pot donar un resultat diferent que amb la configuració C. Per a comparacions deterministes, la pràctica habitual és fixar LC_ALL=C.

  1. Operadors de fitxer

Aquesta és la família que converteix informe-diari.sh en un script robust: comprovar abans de treballar.

Operador Cert si el camí…
-e cami Existeix (sigui el que sigui)
-f cami Existeix i és un fitxer regular
-d cami Existeix i és un directori
-s cami Existeix i no és buit (mida > 0)
-r cami És llegible per l'usuari actual
-w cami És escrivible
-x cami És executable (o directori travessable)
-L cami És un enllaç simbòlic (05-01)
f1 -nt f2 / f1 -ot f2 f1 és més recent / més antic que f2
[[ -r "$CAMI_APP_LOG" ]] || echo "sense permís de lectura sobre el log" >&2
[[ -s "$CAMI_CSV" ]]     || echo "atenció: el CSV és buit" >&2
[[ -d "$DIR_INFORMES" ]] || mkdir -p "$DIR_INFORMES"     # "assegura que existeix"

La distinció entre -e, -f i -s importa més del que sembla. -e només diu que el camí existeix: un directori anomenat enviaments.csv també la satisfà. -f garanteix que és un fitxer de debò. I -s hi afegeix que té contingut, que és el que vols saber realment: un CSV buit existeix, és llegible i produeix un informe amb tot a zero sense donar cap error. Aquesta és la fallada silenciosa que -s preveu.

-nt i -ot tenen un ús molt concret en operacions: saber si un informe està al dia respecte a les seves dades d'origen, amb [[ "$CAMI_CSV" -nt "$informe" ]].

  1. Operadors lògics dins de [[ ]]

Per combinar condicions, [[ ]] admet && i || dins dels claudàtors:

[[ -f "$CAMI_CSV" && -r "$CAMI_CSV" ]] && echo "existeix i el puc llegir"
[[ -z $ciutat || $ciutat == "totes" ]] && echo "analitzar totes les ciutats"

L'ordre [ ] no els admet; fa servir -a (and) i -o (or), com a [ -f "$CAMI_CSV" -a -r "$CAMI_CSV" ]. Evita -a i -o. Estan marcats com a obsolets en el mateix estàndard POSIX perquè la seva anàlisi sintàctica és ambigua: test rep una llista plana d'arguments i ha d'endevinar on comença i acaba cada expressió, cosa que produeix resultats incorrectes amb certs valors. A més no fan curtcircuit: [ -f "$f" -a -r "$f" ] avalua sempre les dues parts.

Hi ha una altra diferència pràctica notable: dins de [[ ]], && sí que curtcircuita, cosa que permet escriure comprovacions dependents amb seguretat. A [[ -f "$f" && $(wc -l < "$f") -gt 0 ]], si el fitxer no existeix el wc no arriba a executar-se i no apareix cap missatge d'error espuri. Amb -a no en tindries la garantia.

  1. Aritmètica com a operador: (( )) i $(( ))

Bash ofereix un context aritmètic on els operadors recuperen el seu significat matemàtic habitual. N'hi ha dues variants:

Sintaxi Retorna Es fa servir per a
(( expr )) Un codi de sortida Comparar i avaluar com a condició
$(( expr )) Un valor Calcular i substituir el resultat
total_errors=37; LLINDAR_ERRORS=50

(( total_errors > LLINDAR_ERRORS )) && echo "AVÍS: massa errors"
percentatge=$(( total_errors * 100 / LLINDAR_ERRORS ))
echo "Ets al ${percentatge}% del llindar"     # → Ets al 74% del llindar

Dos avantatges notables. Primer: dins de (( )) el $ és opcional als noms de variable, perquè tot el que no sigui un número s'interpreta com a tal. Segon: pots fer servir <, >, <=, >=, == i != amb el seu significat numèric natural, cosa que es llegeix molt millor que -lt i companyia.

Hi ha un detall que sorprèn i produeix fallades silencioses: (( )) retorna 1 (fallada) quan el resultat de l'expressió és 0, seguint la convenció de C, invertida respecte als codis de sortida del shell.

(( 0 ))  ; echo $?    # 1 → "fals"
(( 5 ))  ; echo $?    # 0 → "cert"
comptador=0; (( comptador++ )) ; echo $?    # 1, tot i que l'operació ha funcionat

L'última línia és el parany real: si tens set -e actiu (05-03), un (( comptador++ )) amb el comptador a zero avortarà l'script. La solució idiomàtica és (( comptador++ )) || true.

El repertori aritmètic complet —%, **, increments, bases numèriques, decimals amb bc— és la lliçó 04-06. Aquí ens interessa (( )) només com a operador de comparació.

  1. Precedència i parèntesis

Quan barreges operadors, Bash aplica una precedència establerta: && s'avalua abans que || dins de [[ ]], igual que a la majoria de llenguatges. Però entre ordres, && i || tenen la mateixa precedència i s'avaluen d'esquerra a dreta, cosa que és una font clàssica de sorpreses.

[[ -f "$CAMI_CSV" || -f "$CAMI_ALT" ]] && [[ -r "$CAMI_CSV" ]]   # explícit
[[ ( -f "$f" || -d "$f" ) && -r "$f" ]]                          # parèntesis interns

El consell professional és senzill: no confiïs en la precedència, escriu parèntesis. Dins de [[ ]] s'escriuen tal qual, amb espais al voltant; dins de [ ] cal escapar-los (\\( i \\)), una raó més per preferir [[ ]].

Amb tot això, les validacions que informe-diari.sh necessita ja es poden expressar del tot:

[[ -f "$CAMI_CSV" && -s "$CAMI_CSV" ]] || { echo "CSV absent o buit" >&2; exit 3; }
[[ -r "$CAMI_APP_LOG" ]] || { echo "No puc llegir el log" >&2; exit 4; }
(( total_errors > LLINDAR_ERRORS )) && echo "AVÍS: $total_errors errors avui"

Tres línies que converteixen un script confiat en un de defensiu. A 03-04 els donarem la forma d'if que els correspon i les integrarem al fitxer.

Errors Habituals i Consells

  • Oblidar els espais interiors dels claudàtors. [[-f $f]] no és sintaxi: [ i [[ són paraules que necessiten espais a banda i banda.
  • Comparar números amb > o <. [[ "100" > "9" ]] és fals. Fes servir -gt o, encara millor, (( )).
  • Fer servir -eq amb text. [[ "Valencia" -eq "Sevilla" ]] dona s'esperava una expressió entera.
  • Fer servir [ ] amb variables sense cometes. Si la variable és buida, el test rep menys arguments dels que espera i falla amb un missatge críptic.
  • Escriure < o > dins de [ ] sense escapar-los. Bash ho entén com una redirecció i crea un fitxer amb aquest nom.
  • Encadenar a && b || c creient que és un if/else. Si b falla, c també s'executa.
  • Confiar en (( )) amb resultat zero. Retorna codi 1 i pot avortar l'script si hi ha set -e.
  • Fer servir -a i -o dins de [ ]. Obsolets i ambigus: fes servir && i || dins de [[ ]].

Exercicis

Exercici 1 — Tradueix a operadors. Escriu, sense fer servir if, la línia que correspon a cada frase: (a) si enviaments.csv no existeix, imprimeix un error a stderr i surt amb codi 3; (b) crea ~/veloz-ops/logs només si no existeix; (c) si el nombre d'incidències supera 100, imprimeix una alerta; (d) si la variable ciutat és buida, assigna-li Valencia (fent servir operadors, no expansió de paràmetres).

Exercici 2 — Troba les cinc errades. Aquest bloc té cinc errors d'operadors. Identifica'ls i reescriu-lo amb les bones pràctiques de la lliçó.

errors=`grep -c ERROR /var/log/veloz/app.log`
if [-f /srv/veloz/dades/enviaments.csv]
[ $errors > 50 ] && echo "molts errors"
[ -f $csv -a -r $csv ] && echo "llegible"
[[ $ciutat -eq "Valencia" ]] && echo "és Valencia"

Exercici 3 — Validació completa. Escriu el bloc de validacions prèvies d'informe-diari.sh fent servir només operadors. Ha de comprovar, amb un codi de sortida diferent per a cada fallada: que el log existeix i és llegible (codi 3), que el CSV existeix i no és buit (codi 4), que el directori d'informes existeix o es pot crear (codi 5), i que hi ha espai lliure a /srv (codi 6). Cada error ha d'anar a stderr.

Solucions

Solució a l'Exercici 1

[[ -f "$CAMI_CSV" ]] || { echo "No trobo $CAMI_CSV" >&2; exit 3; }        # (a)
[[ -d "$DIR_INFORMES" ]] || mkdir -p "$DIR_INFORMES"                      # (b)
(( incidencies > 100 )) && echo "ALERTA: $incidencies incidències avui"   # (c)
[[ -z $ciutat ]] && ciutat="Valencia"                                     # (d)

A (a) les claus són imprescindibles: agrupen el missatge i la sortida en una sola unitat per al ||. A (c) es fa servir (( )) en lloc de [[ $incidencies -gt 100 ]]; totes dues són correctes, però l'aritmètica es llegeix millor i no admet confusió amb la comparació textual. A (d), la forma idiomàtica real de Bash és ciutat=${ciutat:-Valencia}, que veuràs a 03-06: fa el mateix en una expressió.

Solució a l'Exercici 2

Les cinc errades: (1) cometes invertides en lloc de $( ); (2) [-f ...] sense espais interiors i sense tancar l'if —a més hi falta el ; then—; (3) > compara text i, a sobre, crea un fitxer anomenat 50, cal fer servir -gt o (( )); (4) -a obsolet i variables sense cometes dins de [ ]; (5) -eq aplicat a text, que donarà s'esperava una expressió entera.

errors=$(grep -c ERROR /var/log/veloz/app.log)
[[ -f /srv/veloz/dades/enviaments.csv ]] && echo "el CSV existeix"
(( errors > 50 )) && echo "molts errors"
[[ -f $csv && -r $csv ]] && echo "llegible"
[[ $ciutat == "Valencia" ]] && echo "és Valencia"

L'errada (3) mereix una mirada: amb [ $errors > 50 ], Bash interpreta > com una redirecció, avalua [ $errors ] (cert si la variable no és buida) i crea un fitxer buit anomenat 50 al directori actual. L'script sembla que funciona —sempre diu «molts errors»— i va deixant brossa pel sistema de fitxers. És l'exemple perfecte de per què els operadors importen.

Solució a l'Exercici 3

[[ -f "$CAMI_APP_LOG" && -r "$CAMI_APP_LOG" ]] \
  || { echo "ERROR: no puc llegir $CAMI_APP_LOG" >&2; exit 3; }

[[ -f "$CAMI_CSV" && -s "$CAMI_CSV" ]] \
  || { echo "ERROR: $CAMI_CSV no existeix o és buit" >&2; exit 4; }

[[ -d "$DIR_INFORMES" ]] || mkdir -p "$DIR_INFORMES" \
  || { echo "ERROR: no puc crear $DIR_INFORMES" >&2; exit 5; }

lliure=$(df --output=avail -m /srv | tail -1)
(( lliure > 100 )) || { echo "ERROR: menys de 100 MB lliures a /srv" >&2; exit 6; }

Tres detalls a destacar. L'ús de -s a més de -f a la segona comprovació és el que evita l'informe silenciós amb tot a zero. La tercera línia encadena || dues vegades: si el directori no existeix intenta crear-lo, i si això també falla, avorta; és correcte perquè el segon || recull la fallada del mkdir. I els codis de sortida diferents (3, 4, 5, 6) permeten que qui invoqui l'script —cron, un altre script, un tauler de monitoratge— sàpiga què ha fallat sense llegir els missatges, que és justament el contracte del qual parlàvem a 03-01.

Conclusió

Ja tens el vocabulari amb què un script raona. Distingeixes les quatre famílies d'operadors i saps que > significa coses diferents segons el context; encadenes ordres amb ;, && i || aprofitant el curtcircuit, i domines l'idioma ordre || exit 1; agrupes amb { ...; } i amb ( ... ) quan vols aïllament; coneixes les tres formes de test i per què [[ ]] és l'elecció correcta a Bash; separes la comparació numèrica de la textual, que és l'error més car dels principiants; verifiques fitxers amb -f, -r, -s, -d i -nt, entenent per què -s preveu fallades silencioses; combines condicions dins de [[ ]] en lloc dels obsolets -a i -o; i fas servir (( )) amb la precaució del seu resultat zero.

I, sobretot, les tres validacions que informe-diari.sh necessitava ja estan escrites. Però escrites com a línies soltes encadenades amb ||, un estil que resisteix bé dues condicions i es torna il·legible amb cinc.

A la lliçó 03-04 els donem la forma definitiva. Descobriràs que if a Bash no avalua cap booleà sinó un codi de sortida —cosa que explica per què if grep -q ERROR ... funciona sense claudàtors—, aprendràs a estructurar validacions amb clàusules de guarda en lloc d'imbricacions, i informe-diari.sh passarà a comprovar el seu entorn abans de treballar i a avisar quan els errors superin el llindar.

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