informe-diari.sh ja funciona, però és un script rígid: /var/log/veloz/app.log i /srv/veloz/dades/enviaments.csv apareixen escrits literalment enmig del cos, i el llindar d'errors que vols vigilar no és enlloc. El dia que operacions mogui el CSV a un altre directori hauràs de buscar i reemplaçar per tot el fitxer, i cada aparició que se t'escapi serà una fallada silenciosa. Aquesta lliçó resol aquest problema i obre la porta a tota la resta: les variables són el mecanisme amb què un script deixa de ser una llista d'ordres i comença a manipular informació.

Contingut

  1. Assignar variables: la regla d'or de l'= sense espais
  2. Noms vàlids i convencions de majúscules
  3. Fer servir variables: $var davant de ${var}
  4. Bash no té tipus: tot és una cadena
  5. Substitució d'ordres amb $(...)
  6. Constants amb readonly i declare -r
  7. declare i les seves opcions útils
  8. Àmbit: variables de shell davant de variables d'entorn
  9. Variables especials del shell
  10. unset i la refactorització d'informe-diari.sh

  1. Assignar variables: la regla d'or de l'= sense espais

Una variable és un nom associat a un valor. Es crea assignant-li alguna cosa, i a Bash la sintaxi és implacable: ciutat=Valencia, repartidor=alopez, llindar=50.

No hi pot haver espais al voltant de l'=. Aquesta és la primera regla que tothom trenca, i val la pena que n'entenguis el perquè en lloc de memoritzar-la. Bash separa la línia en paraules pels espais: si escrius ciutat = Valencia, hi veu tres paraules i dedueix que vols executar l'ordre ciutat amb els arguments = i Valencia, d'aquí el missatge bash: ciutat: no s'ha trobat l'ordre. La variant ciutat =Valencia produeix el mateix error, mentre que ciutat= Valencia intenta executar Valencia amb la variable ciutat buida. Tres formes d'equivocar-se, tres símptomes diferents, una sola regla: enganxat a l'=, sense excepcions.

Si el valor conté espais, cal posar-lo entre cometes, perquè si no la segona paraula s'interpretaria com una ordre:

missatge="Informe generat correctament"
cami_llarg="/srv/veloz/dades/historic 2026/enviaments.csv"

Les cometes dobles són l'opció per defecte i les estudiaràs a fons a 03-06; de moment queda't amb això: posar cometes no fa mal mai.

  1. Noms vàlids i convencions de majúscules

Un nom de variable pot contenir lletres, dígits i guions baixos, i no pot començar per un dígit. No admet guions, punts ni espais: la-meva-variable no és vàlid (Bash ho llegeix com una resta), però la_meva_variable sí.

A més de les regles del llenguatge, hi ha una convenció universal que has de seguir:

Estil Es fa servir per a Exemples
MAJÚSCULES Constants de l'script i variables d'entorn CAMI_LOG, LLINDAR_ERRORS, PATH, HOME
minúscules Variables internes de l'script, temporals, comptadors data, total_errors, ciutat

La raó és pràctica: l'entorn del sistema fa servir majúscules (PATH, HOME, USER, LANG), de manera que utilitzar-les per a variables qualssevol t'exposa a trepitjar-ne una del sistema —un PATH=/tmp descuidat deixa l'script sense trobar cap ordre—. Reserva les majúscules per al que de debò és constant o d'entorn.

  1. Fer servir variables: $var davant de ${var}

Per llegir el valor s'hi anteposa $: si ciutat=Valencia, aleshores echo "Ciutat: $ciutat" imprimeix Ciutat: Valencia. La forma amb claus ${ciutat} és equivalent... fins que deixa de ser-ho. Les claus són obligatòries quan el que segueix el nom es podria confondre amb part del nom mateix:

data=2026-08-03
echo "enviaments_$data.csv"     # enviaments_2026-08-03.csv  (el punt talla el nom)
echo "informe_$data_v2.txt"     # informe_               ← MALAMENT!
echo "informe_${data}_v2.txt"   # informe_2026-08-03_v2.txt

La segona línia surt mutilada perquè el guió baix sí que és un caràcter vàlid en un nom de variable: Bash ha buscat una variable anomenada data_v2, que no existeix, i l'ha substituïda per res. Les claus marquen on acaba el nom i eliminen l'ambigüitat.

Hi ha un tercer motiu: les claus són la sintaxi de l'expansió de paràmetres, la família que inclou valors per defecte com ${var:-valor} (03-06) i manipulació de cadenes com ${var^^} (04-04). Per això molts equips adopten la norma d'escriure sempre ${var}.

  1. Bash no té tipus: tot és una cadena

Aquesta és una idea que cal assimilar bé, perquè explica molts comportaments desconcertants. A Bash tots els valors són cadenes de text. No existeixen ni els enters, ni els booleans, ni els decimals.

A llindar=50, aquest 50 és el text «cinc, zero», no pas el número cinquanta. Conseqüències directes:

  • Per comparar com a números cal fer servir operadors específics (-eq, -gt, …) o (( )); comparar amb = compara text, i "10" = "9" és fals mentre que 10 -gt 9 és cert. Aquest contrast és la lliçó 03-03.
  • Per sumar no n'hi ha prou amb a=$b+$c. Cal l'expansió aritmètica $(( )), que veuràs a 03-03 i a fons a 04-06.
  • No hi ha decimals. 50.5 es desa sense problema com a text, però qualsevol operació aritmètica amb ell donarà error. Per als decimals es recorre a bc o awk (04-06 i 06-01).
a=5; b=3
echo "$a+$b"        # 5+3   (concatenació de text)
echo $(( a + b ))   # 8     (expansió aritmètica)

Que tot sigui text també vol dir que una variable no declarada i una variable buida es comporten gairebé igual: totes dues s'expandeixen a res. És còmode i alhora perillós, i d'aquí que existeixi ${var:?missatge} per exigir que una variable tingui valor (03-06).

  1. Substitució d'ordres amb $(...)

Aquí és on les variables es tornen realment útils en operacions. La substitució d'ordres executa una ordre i substitueix l'expressió sencera per la seva sortida:

data=$(date +%F)
echo "Informe del $data"      # → Informe del 2026-08-03

Bash executa date +%F, recull el que escriu a stdout i ho desa a data. Els salts de línia finals s'eliminen automàticament, cosa molt convenient. Això converteix qualsevol canonada del Mòdul 2 en un valor manipulable:

total_errors=$(grep -c ERROR /var/log/veloz/app.log)
total_enviaments=$(tail -n +2 /srv/veloz/dades/enviaments.csv | wc -l)
incidencies=$(grep -c ',incidencia,' /srv/veloz/dades/enviaments.csv)

echo "Errors: $total_errors | Enviaments: $total_enviaments | Incidències: $incidencies"
# → Errors: 37 | Enviaments: 1246 | Incidències: 118

Hi ha una forma antiga amb cometes invertides, `ordre`, que encara veuràs en scripts heretats. Està desaconsellada:

Aspecte $(ordre) `ordre`
Imbricació Directa: $(dirname $(which bash)) Cal escapar cada nivell
Llegibilitat Els parèntesis s'aparellen visualment Les dues cometes són idèntiques
Confusió visual Cap Es confon amb ' en moltes tipografies
Escapaments interns Naturals Regles pròpies i sorprenents

Fes servir sempre $( ). Un avís de rendiment: cada $(...) llança un subshell, és a dir, un procés nou (lliçó 01-04). Amb tres o quatre no es nota, però dins d'un bucle de mil voltes sí; aquest és el tipus d'optimització que tracta 08-02.

  1. Constants amb readonly i declare -r

Algunes variables no han de canviar mai: els camins base de l'script, els llindars acordats amb negoci, el nom del servei. Marcar-les com a constants documenta aquesta intenció i, a més, fa que Bash la faci complir.

readonly CAMI_APP_LOG="/var/log/veloz/app.log"
readonly LLINDAR_ERRORS=50
declare -r CAMI_CSV="/srv/veloz/dades/enviaments.csv"   # forma equivalent

Si després alguna cosa les intenta modificar, Bash s'hi nega amb bash: LLINDAR_ERRORS: variable de només lectura. Dos matisos. Aquest error no atura l'script per si sol (llevat que hi hagi set -e, lliçó 05-03), però deixa rastre i retorna un codi diferent de zero. I readonly és irreversible: no es desfà amb unset a la sessió actual. En un script això és el que vols; al teu terminal interactiu, ves amb compte abans de declarar constants a la lleugera.

Regla d'or a partir d'ara: totes les constants van juntes al principi del fitxer, sota la capçalera. Qui obri l'script veu en deu segons quins camins fa servir i quins llindars aplica, sense llegir-ne el cos.

  1. declare i les seves opcions útils

declare crea variables amb atributs. Les opcions que importen ara:

Opció Efecte Exemple
-r Només lectura (constant) declare -r MAX=100
-i Tracta la variable com un enter declare -i comptador=0
-x L'exporta a l'entorn (com export) declare -x VELOZ_ENV=produccio
-a / -A Array indexat / associatiu S'estudien a 04-03
-p Mostra la declaració d'una variable declare -p LLINDAR_ERRORS

L'atribut -i és curiós: fa que les assignacions s'avaluïn aritmèticament sense necessitat de $(( )). I declare -p és una eina de depuració excel·lent, perquè mostra el valor i els atributs:

declare -i comptador=0;  comptador=comptador+5;  echo "$comptador"  # → 5
sense_atribut=0;  sense_atribut=sense_atribut+5;  echo "$sense_atribut"  # → sense_atribut+5
declare -p CAMI_CSV   # → declare -r CAMI_CSV="/srv/veloz/dades/enviaments.csv"

  1. Àmbit: variables de shell davant de variables d'entorn

Reprenem la distinció de la lliçó 01-04, ara amb conseqüències pràctiques. Una variable pot viure en dos llocs:

  • Variable de shell: només existeix al shell que la va crear. És el que obtens amb una assignació normal.
  • Variable d'entorn: es copia a tots els processos fills. S'aconsegueix amb export.
ciutat=Valencia                  # variable de shell
export VELOZ_ENTORN=produccio    # variable d'entorn

bash -c 'echo "ciutat=[$ciutat] entorn=[$VELOZ_ENTORN]"'
# → ciutat=[] entorn=[produccio]

El shell fill no veu ciutat perquè no s'ha exportat. Aquesta és la causa número u de la pregunta «per què el meu script no veu la variable que vaig definir abans?»: un script és un procés fill, i només hereta el que s'ha exportat.

L'herència, a més, va en una sola direcció: si el fill modifica VELOZ_ENTORN, el pare no se n'assabenta. En rep una còpia, no pas un enllaç. Per això un script no pot canviar el directori ni les variables del teu terminal, i per això existeix source (03-01).

Consulta l'entorn amb env o printenv, i totes les variables amb set. Per fixar una variable d'entorn només durant una invocació concreta, s'anteposa a l'ordre: VELOZ_ENTORN=proves informe-diari.sh. A la pràctica s'exporta poc: només allò que altres programes necessiten llegir.

  1. Variables especials del shell

Bash defineix automàticament variables que aporten informació del context. Les més útils ara mateix:

Variable Conté
$? Codi de sortida de l'última ordre (01-04)
$$ PID del shell o script actual — ideal per a fitxers temporals únics
$0 Nom amb què s'ha invocat l'script
$HOME, $USER, $HOSTNAME Directori personal, usuari i nom de l'equip
$PWD / $OLDPWD Directori actual / anterior
$RANDOM Enter pseudoaleatori entre 0 i 32767, diferent a cada lectura
$SECONDS Segons transcorreguts des que va arrencar l'script
$LINENO Número de línia actual — molt útil en depurar (05-03)
temporal="/tmp/informe-$$-$RANDOM.tmp"
echo "Executant $0 com a $USER a $HOSTNAME"
echo "Temporal: $temporal · Durada: $SECONDS s"
Executant /home/joan/veloz-ops/bin/informe-diari.sh com a joan a srv-veloz-01
Temporal: /tmp/informe-48213-9174.tmp · Durada: 2 s

Combinar $$ i $RANDOM és el truc clàssic per a noms temporals que no col·lideixin si l'script s'executa dues vegades alhora; a 05-01 el substituirem per mktemp, que a més neteja darrere seu.

Falten les variables d'arguments —$1, $@, $#—, que són el tema complet de la lliçó 03-05.

  1. unset i la refactorització d'informe-diari.sh

unset ciutat elimina la variable del tot, i la deixa indefinida: echo "[$ciutat]" imprimirà []. Compte: unset no funciona sobre constants declarades amb readonly, i no és el mateix que assignar-hi la cadena buida. Per a gairebé tot donen el mateix resultat, però ${var:?} i ${var+x} (03-06) sí que distingeixen tots dos casos.

Amb totes les peces sobre la taula, així queda informe-diari.sh:

#!/usr/bin/env bash
#
# informe-diari.sh - Resum diari d'activitat de Veloz Envíos
#
# Propòsit : Comptar els ERROR d'app.log i agrupar els enviaments per estat.
# Autor    : Joan Costa <[email protected]>   ·   Ús: informe-diari.sh
# Codis    : 0 correcte
#

# --- 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"

# --- Dades calculades --------------------------------------------------
data=$(date +%F)
hora=$(date +%T)
total_errors=$(grep -c ERROR "$CAMI_APP_LOG")
total_enviaments=$(tail -n +2 "$CAMI_CSV" | wc -l)

# --- Informe -----------------------------------------------------------
echo "==================================================="
echo "  INFORME DIARI - VELOZ ENVIOS ($SERVIDOR)"
echo "  Generat: ${data} ${hora}"
echo "==================================================="
echo
echo "-- Errors a app.log --"
echo "Total: ${total_errors} (llindar d'avís: ${LLINDAR_ERRORS})"
echo
echo "-- Enviaments per estat (${total_enviaments} en total) --"
tail -n +2 "$CAMI_CSV" | cut -d, -f5 | sort | uniq -c | sort -rn
exit 0

Compara'l amb la versió de 03-01 i mesura la millora. Els camins apareixen una sola vegada, agrupats i visibles a les primeres línies: moure enviaments.csv és canviar un caràcter. El llindar acordat amb negoci existeix ara com a dada del programa, amb nom propi, a punt perquè la lliçó 03-04 el converteixi en un avís automàtic. Els recomptes es calculen un cop i es reutilitzen. I l'informe mostra data, hora i servidor, informació imprescindible quan algú el llegeixi tres setmanes després.

Fixa't en dos detalls que anticipen 03-06: les variables es fan servir sempre entre cometes dobles ("$CAMI_CSV") i s'hi posen claus quan el valor va enganxat a un altre text. Adopta l'hàbit des d'ara.

Errors Habituals i Consells

  • Espais al voltant de l'=. ciutat = Valencia intenta executar l'ordre ciutat. És l'error número u del principiant.
  • Oblidar el $ en llegir, o posar-lo en assignar. echo ciutat imprimeix la paraula; $ciutat=Valencia és un error de sintaxi. El $ només es fa servir per llegir.
  • Noms enganxats a text sense claus. $data_v2 busca la variable data_v2. Fes servir ${data}_v2.
  • Valors amb espais sense cometes. msg=Informe diari intenta executar diari amb msg=Informe a l'entorn.
  • Fer servir MAJÚSCULES per a tot. Tard o d'hora trepitjaràs PATH, HOME o IFS, amb conseqüències difícils de diagnosticar.
  • Esperar que un script vegi les variables del pare. Només hereta el que s'ha exportat; per a això hi ha export o source.
  • Confiar en l'aritmètica amb text. total=$a+$b produeix la cadena 5+3. Necessites $(( )).

Exercicis

Exercici 1 — Capçalera de constants. Escriu el bloc de constants d'un script resum-ciutat.sh que hagi de treballar amb enviaments.csv, escrigui a ~/veloz-ops/logs, consideri «moltes incidències» a partir de 30 i analitzi València per defecte. Fes servir la convenció correcta de majúscules i readonly, i afegeix-hi una variable no constant amb la data del dia en format AAAA-MM-DD.

Exercici 2 — Depurar un script trencat. Un company ha escrit això i no funciona. Identifica'n els cinc errors i reescriu-lo.

#!/usr/bin/env bash
CAMI = "/srv/veloz/dades/enviaments.csv"
total_enviaments = `wc -l < $CAMI`
fitxer_sortida="$HOME/veloz-ops/logs/resum_$data_final.txt"
echo "Enviaments: total_enviaments" > $fitxer_sortida

Exercici 3 — Mètriques de l'informe. Amplia el bloc de dades calculades d'informe-diari.sh perquè desi en variables: el nombre de lliuraments completats, el nombre d'incidències, el repartidor amb més enviaments del dia i el nom del fitxer de sortida (que ha d'incloure la data). Imprimeix les quatre amb etiquetes.

Solucions

Solució a l'Exercici 1

readonly CAMI_CSV="/srv/veloz/dades/enviaments.csv"   # --- Constants ---
readonly DIR_INFORMES="$HOME/veloz-ops/logs"
readonly LLINDAR_INCIDENCIES=30
readonly CIUTAT_DEFECTE="Valencia"
data=$(date +%F)                                      # --- Dades calculades ---

Les quatre primeres són constants, en majúscules i amb readonly; data va en minúscules i sense readonly perquè és un valor calculat a cada execució, no una decisió de disseny. Fixa't que $HOME dins de cometes dobles s'expandeix correctament, cosa que fa l'script vàlid per a qualsevol usuari: escriure /home/joan/... a mà el trencaria per a tots els altres.

Solució a l'Exercici 2

Els cinc errors: (1) CAMI = "..."espais al voltant de l'=; (2) total_enviaments = ... repeteix la mateixa errada; (3) fa servir cometes invertides en lloc de $( ); (4) $data_final busca una variable inexistent —hi falta ${data}_final— i, a més, data no es defineix mai; (5) echo "Enviaments: total_enviaments" imprimeix el text literal perquè hi falta el $, i > $fitxer_sortida va sense cometes.

#!/usr/bin/env bash
readonly CAMI="/srv/veloz/dades/enviaments.csv"
data=$(date +%F)
total_enviaments=$(tail -n +2 "$CAMI" | wc -l)
fitxer_sortida="$HOME/veloz-ops/logs/resum_${data}_final.txt"
echo "Enviaments: $total_enviaments" > "$fitxer_sortida"

S'hi ha afegit a més tail -n +2 per no comptar la capçalera del CSV, una errada de contingut que la versió original arrossegava sense que ningú se n'adonés.

Solució a l'Exercici 3

lliurats=$(grep -c ',lliurat,' "$CAMI_CSV")
incidencies=$(grep -c ',incidencia,' "$CAMI_CSV")
top_repartidor=$(tail -n +2 "$CAMI_CSV" | cut -d, -f4 | sort | uniq -c \
                 | sort -rn | head -1 | tr -s ' ' | cut -d' ' -f3)
fitxer_sortida="${DIR_INFORMES}/informe-$(date +%F).txt"

echo "Lliurats        : $lliurats"        # → 981
echo "Incidències     : $incidencies"     # → 118
echo "Top repartidor  : $top_repartidor"  # → mgarcia
echo "Fitxer destí    : $fitxer_sortida"  # → ~/veloz-ops/logs/informe-2026-08-03.txt

La línia de top_repartidor mereix explicació. uniq -c genera línies amb espais d'emplenament al principi ( 412 mgarcia), així que tr -s ' ' els comprimeix en un de sol i cut -d' ' -f3 pren el tercer camp —el primer queda buit per culpa de l'espai inicial—. És funcional, però també un avís: quan una canonada necessita tants ajustos, l'eina adequada és awk, i a 06-01 aquesta mateixa línia es reduirà a una expressió molt més neta.

Conclusió

informe-diari.sh ja no és una llista d'ordres: és un programa amb dades. Saps assignar sense espais al voltant de l'= i per què existeix aquesta regla; anomenes seguint la convenció de majúscules per a les constants i minúscules per a la resta; fas servir ${var} quan les claus són necessàries; assumeixes que a Bash tot és text; captures la sortida de qualsevol canonada amb $( ); blindes camins i llindars amb readonly; coneixes declare i els seus atributs; distingeixes les variables de shell de les d'entorn; i manejes les especials com $?, $$ o $SECONDS.

L'script té ara un LLINDAR_ERRORS=50 a la capçalera que no serveix de res: s'imprimeix, però ningú no el compara amb res. I continua donant per fet que app.log i enviaments.csv existeixen i són llegibles; si operacions reanomena el CSV, l'script no avisarà: escopirà un informe amb zeros.

A la lliçó 03-03 aprendràs a preguntar. Veuràs els operadors del shell: com encadenar ordres amb && i ||, com comprovar si un fitxer existeix amb [[ -f ... ]], i com comparar números —el clàssic -gt davant de >— per poder respondre la pregunta que aquest llindar porta esperant des del principi: hi ha avui més errors dels tolerables?

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