En tancar el Mòdul 3 va quedar un mur assenyalat amb el dit: la línia de crontab plena d'escapaments, la canonada de l'informe que ja no cabia a la pantalla, i aquella dependència insuportable que tu te'n recordessis de cada pas a les tres de la matinada. Aquest mòdul enderroca aquell mur amb la idea més simple del món: un script no és res més que un fitxer de text amb les ordres que ja saps escriure.

No hi ha cap llenguatge nou per aprendre: grep, awk, find, curl i df continuen sent els mateixos. El que canvia és que deixen de viure a la teva memòria per viure en un fitxer amb nom, permisos, autor i data; un fitxer que es pot executar igual mil vegades i córrer sol mentre tu dorms. Aquesta lliçó munta els fonaments i acaba amb revisio_salut.sh, el primer script del fil conductor, que les sis lliçons següents aniran millorant.

Contingut

  1. Val la pena que això sigui un script?
  2. Què és exactament un script
  3. El shebang: qui interpreta el teu fitxer
  4. Les tres formes d'executar un script
  5. Permisos d'execució
  6. On viuen els scripts i el PATH
  7. Anatomia d'un script decent
  8. Comentaris útils i comentaris inútils
  9. Codis de sortida
  10. Primer script real: revisio_salut.sh
  11. Estil i eines: shellcheck i shfmt

  1. Val la pena que això sigui un script?

Escriure un script té un cost: cal mantenir-lo, versionar-lo, documentar-lo i recordar que existeix. Abans d'obrir l'editor, situa el que vols fer en aquesta escala.

Forma Quan Avantatge Límit
One-liner / àlies Ho fas una vegada, o és una ordre fixa diària Cost zero, o una línia a .bashrc Es perd en tancar la sessió; l'àlies no accepta arguments al mig
Funció de shell Lògica curta amb arguments, només per a tu Viu al teu entorn, sense subprocés No la pot executar cron ni cap altre usuari
Script Procediment repetible, amb passos i decisions Versionable, compartible, programable Bash es torna incòmode per sobre de ~300 línies
Ansible Configurar màquines, i més d'una Idempotència i inventari de sèrie Requereix infraestructura i aprenentatge
Llenguatge de programació Estructures de dades, JSON, concurrència, API Tipus, tests, biblioteques Dependències per instal·lar i mantenir

La regla pràctica: si ho has fet tres vegades a mà, o si ho executarà algú que no ets tu, o si ho executarà cron, és un script. Si a més comença a manejar dades imbricades o a parlar amb API JSON, l'alarma està sonant i toca mirar cap a Python; hi tornarem a 04-07. La revisió de salut que la Marta demana cada matí compleix els tres criteris alhora: és un script.

  1. Què és exactament un script

Un fitxer de text pla amb ordres que l'intèrpret llegeix i executa en ordre. No es compila, no s'enllaça, no genera binaris.

operador@srv-tramontana:~$ printf 'echo "Hola"\ndate +%%F\n' > /tmp/primer.sh
operador@srv-tramontana:~$ bash /tmp/primer.sh
Hola
2026-08-18

bash ha obert el fitxer, ha llegit la primera línia, l'ha executada com si tu l'haguessis teclejada, i ha passat a la segona. Aquesta equivalència total entre «el que teclejes» i «el que va en un script» és la raó que aquest mòdul et resulti natural: ja saps escriure el contingut, només faltava el recipient.

  1. El shebang: qui interpreta el teu fitxer

Si el fitxer s'executa per si mateix, el nucli necessita saber quin programa l'ha d'interpretar: ho diuen els dos primers caràcters, #!, seguits de la ruta de l'intèrpret. En executar ./script.sh, el nucli llegeix aquells bytes màgics i arrenca en realitat /bin/bash ./script.sh. Sense shebang el resultat depèn d'on siguis, així que no ho deixis mai a l'atzar.

Shebang Què fa Quan fer-lo servir
#!/bin/bash Ruta fixa a Bash Servidors Linux, on Bash sempre hi és
#!/usr/bin/env bash Busca bash al PATH Portabilitat: macOS, BSD, Bash a /usr/local
#!/bin/sh El shell POSIX del sistema Scripts que han de córrer en qualsevol Unix, sense extensions

En aquest curs farem servir #!/usr/bin/env bash: costa el mateix i t'estalvia el dia que executis l'script en un macOS on el Bash modern és a /opt/homebrew/bin/bash.

Per què /bin/sh no és Bash a Ubuntu

A Ubuntu i Debian, ls -l /bin/sh revela un enllaç a dash, un shell minúscul que implementa POSIX i res més. Es va triar així perquè els scripts d'arrencada són milers i dash els executa molt més ràpid. La conseqüència és que tot el que és específic de Bash falla:

operador@srv-tramontana:~$ cat > /tmp/prova_sh.sh <<'EOF'
#!/bin/sh
if [[ "reserves" == reserv* ]]; then
    echo "coincideix"
fi
EOF
operador@srv-tramontana:~$ bash /tmp/prova_sh.sh
coincideix
operador@srv-tramontana:~$ sh /tmp/prova_sh.sh
/tmp/prova_sh.sh: 2: [[: not found

El mateix fitxer, dos intèrprets, dos resultats. [[ ]] és de Bash, igual que les matrius, ${VAR^^}, local -n o <<<. I fixa't en el detall cruel: l'script no va fallar en escriure'l, va fallar en executar-se amb l'intèrpret equivocat, possiblement setmanes després i dins de cron. Si escrius #!/bin/sh, no facis servir res de Bash.

  1. Les tres formes d'executar un script

N'hi ha tres —bash script.sh arrenca un Bash nou que llegeix el fitxer, ./script.sh deixa que el nucli llegeixi el shebang, i source script.sh (o . script.sh, la forma POSIX) l'executa el shell actual— i la diferència no és cosmètica: canvia en quin procés s'executen les teves ordres.

flowchart LR
    A["El teu shell interactiu<br/>PID 3120, cwd=/home/operador"] -->|"bash script.sh<br/>./script.sh"| B["Subprocés Bash<br/>PID 3455, còpia de l'entorn"]
    B -->|"en acabar, mor"| C["El shell original<br/>no ha canviat"]
    A -->|"source script.sh"| D["Mateix procés PID 3120<br/>cd, variables i funcions persisteixen"]

La demostració clàssica, un script que canvia de directori:

operador@srv-tramontana:~$ cat > /tmp/anar_releases.sh <<'EOF'
#!/usr/bin/env bash
cd /opt/tramontana/releases
echo "Dins de l'script soc a: $PWD"
EOF
operador@srv-tramontana:~$ chmod +x /tmp/anar_releases.sh
operador@srv-tramontana:~$ /tmp/anar_releases.sh
Dins de l'script soc a: /opt/tramontana/releases
operador@srv-tramontana:~$ pwd
/home/operador
operador@srv-tramontana:~$ source /tmp/anar_releases.sh
Dins de l'script soc a: /opt/tramontana/releases
operador@srv-tramontana:~$ pwd
/opt/tramontana/releases

L'script sí que va canviar de directori... en el seu propi procés, que ja no existeix. Un fill hereta una còpia de l'entorn del seu pare, però res del que el fill canviï no torna al pare: ni el directori, ni les variables, ni les funcions. És l'herència d'una sola direcció de 03-01; amb source no hi ha procés nou i per això el cd persisteix.

Forma Procés nou? Cal chmod +x? Respecta el shebang? Efectes que persisteixen
bash script.sh Sí No No (mana bash) Cap
./script.sh Sí Sí Sí Cap
source script.sh No No No cd, variables, funcions

Dues regles pràctiques. Els scripts s'executen amb ./ o pel seu nom, l'única forma que respecta el shebang i la que farà servir cron. I source es reserva per a configuració i biblioteques de funcions, que és per al que el farem servir a 04-05. Un avís: com que source no crea cap procés, un exit 1 dins d'un fitxer que fas source al teu terminal et tanca la sessió. Li ha passat a tothom una vegada.

  1. Permisos d'execució

Un fitxer de text no és executable fins que li ho dius al sistema. Reprenent 02-07:

operador@srv-tramontana:~/scripts$ ./revisio_salut.sh
-bash: ./revisio_salut.sh: Permission denied
operador@srv-tramontana:~/scripts$ chmod +x revisio_salut.sh && ls -l revisio*
-rwxr-x--- 1 operador operador 512 Aug 18 09:14 revisio_salut.sh

chmod +x respecta l'umask 027 del Mòdul 2: afegeix x a usuari i grup, no a altres; chmod 750 diu el mateix sense dependre de l'umask. Dos matisos: el bit x no significa «això és codi màquina» sinó «permet-me intentar executar-ho» —qui decideix com interpretar-ho és el shebang—, i bash script.sh funciona sense bit x perquè allà el fitxer només es llegeix, que és la manera còmoda de provar una cosa a mig escriure.

  1. On viuen els scripts i el PATH

Teclejar /home/operador/scripts/revisio_salut.sh cada matí garanteix que ningú no el faci servir. La solució és el PATH, que ja vas disseccionar a 03-01.

Ubicació Per a qui Notes
~/scripts Tu, amb ruta explícita On viu el codi font en aquest curs
~/bin o ~/.local/bin Només el teu usuari Ubuntu l'afegeix al PATH si existeix en iniciar la sessió
/usr/local/bin Tots els usuaris El lloc correcte per a scripts locals del sistema (/usr/bin és del gestor de paquets: no hi posis mai res teu)
operador@srv-tramontana:~$ mkdir -p ~/bin ~/scripts
operador@srv-tramontana:~$ ln -s ~/scripts/revisio_salut.sh ~/bin/revisio-salut
operador@srv-tramontana:~$ source ~/.profile && which revisio-salut
/home/operador/bin/revisio-salut

Fem servir un enllaç simbòlic en comptes de moure el fitxer: el codi continua a ~/scripts (versionat a git, ja a 04-07) i ~/bin només publica un nom còmode, sense extensió i amb guionets, com les ordres d'Unix. Continua vigent la regla del Mòdul 3: . mai al PATH, perquè n'hi hauria prou que algú deixés un fitxer anomenat ls en un directori compartit perquè executessis el seu codi sense saber-ho.

  1. Anatomia d'un script decent

Aquesta és la plantilla amb què començaràs tots els teus scripts a partir d'avui. Cada línia evita un problema concret que veuràs durant el mòdul.

#!/usr/bin/env bash
#
# nom.sh - Una línia dient què fa.
#
# Autor : Nom Cognom <correu>
# Data  : 2026-08-18
# Ús    : nom.sh [opcions] <argument>
#
# Descripció una mica més llarga si cal: què comprova, què modifica,
# què deixa escrit i on.

set -euo pipefail

# --- Constants -----------------------------------------------------------
DIRECTORI_LOG="/var/log/tramontana"
LLINDAR_DISC=80

# --- Cos -----------------------------------------------------------------
# ... aquí la feina real ...
exit 0
  • Capçalera. Nom, propòsit, autor, data i manera d'ús: quan d'aquí a vuit mesos trobis aquest fitxer a les quatre de la matinada, aquelles cinc línies valdran més que tota la resta.
  • set -euo pipefail. El mode estricte: -e avorta si una ordre falla, -u avorta si fas servir una variable no definida, i pipefail fa que una canonada falli si falla qualsevol dels seus trams, no només l'últim (ho vam anunciar a 03-04). Posa-ho des d'avui a tot el que escriguis; el perquè detallat i —sobretot— on no funciona és el cor de 04-06.
  • Constants en MAJÚSCULES, minúscules per a les variables de treball, i totes agrupades a dalt: canviar un llindar no ha d'obligar a llegir l'script sencer.
  • exit 0 explícit. Sense ell, l'script retorna el codi de l'última ordre, que pot ser qualsevol cosa. exit 0 diu «he arribat fins aquí i tot ha anat bé», i això és el que llegeixen cron i la monitoració.

  1. Comentaris útils i comentaris inútils

Tot el que segueix un # s'ignora fins al final de línia, tret del shebang. Però comentar molt no és comentar bé.

# INÚTIL: repeteix el que el codi ja diu
us=$(df --output=pcent / | tail -1)   # assigna l'ús de disc a us

# ÚTIL: documenta una decisió que sembla un error
# No fem servir 'grep -c' a seques perquè retorna 1 si no hi ha coincidències
# i amb 'set -e' això avortaria l'script un dia tranquil sense errors.
errors=$(grep -c "^$avui" "$LOG_ERRORS" || true)

La regla: el codi diu què fa; el comentari diu per què. Si necessites un comentari per entendre què fa una línia, la solució sol ser reescriure-la o extreure-la a una funció amb bon nom (04-05).

  1. Codis de sortida

Tot procés acaba retornant un enter de 0 a 255. Ja el coneixes com a $?; ara ets tu qui el produeix, i això canvia la responsabilitat: el codi de sortida és l'única part del teu script que un altre programa llegirà. La convenció és a l'inrevés del que és intuïtiu —0 és èxit— i té sentit: hi ha una sola manera d'encertar i moltes de fallar, cadascuna amb el seu número.

Codi Significat
0 Èxit
1 Error genèric
2 Ús incorrecte: falten arguments, opció desconeguda
126 / 127 Existeix però no s'ha pogut executar (falta el bit x) / ordre no trobada (típicament, el PATH de cron)
128+N Acabat pel senyal N: 130 = Ctrl+C, 143 = SIGTERM, 137 = SIGKILL

(I exit -1 acaba en 255, fora de rang.) exit sense argument retorna el codi de l'última ordre: sigues explícit. I evita inventar-te codis per sobre de 125, que xoquen amb els reservats; en tens de sobres amb el rang 3-125 per als teus propis significats, documentats a la capçalera. A 04-03 formalitzarem una taula per als nostres scripts.

  1. Primer script real: revisio_salut.sh

La Marta demana el mateix cada matí: «està amunt la web, queda disc, i hi ha hagut errors aquesta nit?». L'escrivim tal qual, com una tirada d'ordres, aplicant la plantilla de l'apartat anterior.

#!/usr/bin/env bash
#
# revisio_salut.sh - Revisió ràpida de salut de Tramontana Reserves.
#
# Autor : Operador de sistemes <operador@srv-tramontana>
# Data  : 2026-08-18
# Ús    : revisio_salut.sh
#
# Comprova tres coses: que l'aplicació respon al port 8080, quant disc
# queda a l'arrel i quants errors s'han registrat avui. No modifica res:
# és de només lectura i segur d'executar en producció.

set -euo pipefail

# --- Constants -----------------------------------------------------------
URL_SALUT="http://10.0.2.15:8080/salut"
LOG_ERRORS="/var/log/tramontana/errors.log"
LLINDAR_DISC=80
ESPERA_MAX=5

# --- Cos -----------------------------------------------------------------
echo "=== Revisió de salut de Tramontana Reserves ==="
echo "Servidor : $(hostname)"
echo "Data     : $(date '+%F %T')"
echo

# -s silencia la barra de progrés, -o /dev/null llença el cos de la
# resposta, -w imprimeix només el codi, --max-time evita penjar-se.
codi_http=$(curl -s -o /dev/null -w '%{http_code}' \
    --max-time "$ESPERA_MAX" "$URL_SALUT" || true)
echo "HTTP 8080  : $codi_http"

# --output=pcent dona només la columna del percentatge; tail -1 salta la
# capçalera "Use%"; tr -dc '0-9' deixa el número net per comparar.
us_disc=$(df --output=pcent / | tail -1 | tr -dc '0-9')
echo "Disc /     : ${us_disc}% usat (llindar ${LLINDAR_DISC}%)"

# grep -c retorna 1 quan no troba res i amb set -e això avortaria
# l'script justament el dia que tot va bé. D'aquí el || true.
avui=$(date '+%Y-%m-%d')
errors_avui=$(grep -c "^$avui" "$LOG_ERRORS" || true)
echo "Errors avui: $errors_avui"
exit 0
operador@srv-tramontana:~$ chmod 750 ~/scripts/revisio_salut.sh
operador@srv-tramontana:~$ ~/scripts/revisio_salut.sh; echo "codi: $?"
=== Revisió de salut de Tramontana Reserves ===
Servidor : srv-tramontana
Data     : 2026-08-18 09:22:41

HTTP 8080  : 200
Disc /     : 30% usat (llindar 80%)
Errors avui: 6
codi: 0

Ja és infinitament millor que tres ordres a l'historial. Però és honestament dolent, i convé que vegis per què des del principi, perquè aquella llista és l'índex de la resta del mòdul:

  • Les constants són dins de l'script i no accepta opcions: ni -h, ni un llindar diferent, ni mode silenciós (04-02 i 04-03).
  • Imprimeix el llindar però no el compara amb res. No hi ha cap decisió: si el disc estigués al 95 % diria «95%» tan tranquil (04-04).
  • Tres blocs gairebé idèntics que demanen a crits ser funcions (04-05), i si errors.log no existeix l'script mor a mitges amb un missatge lleig (04-06).
  • Sempre retorna 0, així que cap monitoració no el pot fer servir (04-07).

Desa'l tal qual: al final del mòdul el compararàs amb la seva versió final.

  1. Estil i eines: shellcheck i shfmt

Bash té una sintaxi plena de paranys i un intèrpret que gairebé mai no es queixa: escriu $fitxer sense cometes i funcionarà durant mesos, fins al dia que aparegui un nom amb un espai. L'anàlisi estàtica no és opcional en shell.

operador@srv-tramontana:~$ sudo apt install -y shellcheck shfmt
operador@srv-tramontana:~$ printf '#!/usr/bin/env bash\ndir=/opt/tramontana/releases\ncd $dir\nrm -f $1\n' > /tmp/dolent.sh
operador@srv-tramontana:~$ shellcheck /tmp/dolent.sh
In /tmp/dolent.sh line 3:
cd $dir
   ^--^ SC2086: Double quote to prevent globbing and word splitting.
^-----^ SC2164: Use 'cd ... || exit' in case cd fails.
In /tmp/dolent.sh line 4:
rm -f $1
      ^-- SC2086: Double quote to prevent globbing and word splitting.

Tres avisos en quatre línies i tots tres són bombes de rellotgeria: sense cometes, un directori amb espais es parteix en dos arguments; i si el cd falla, el rm -f s'executa al directori on estiguessis. Cada codi SCxxxx té la seva pàgina al wiki de ShellCheck; a 04-06 esmicolarem els quatre més freqüents. La regla del mòdul des d'avui: cap script no es dona per acabat fins que shellcheck no diu res.

operador@srv-tramontana:~$ shellcheck ~/scripts/revisio_salut.sh && echo "Net"
Net
operador@srv-tramontana:~$ shfmt -i 4 -d ~/scripts/revisio_salut.sh   # -d: només el diff
operador@srv-tramontana:~$ shfmt -i 4 -w ~/scripts/revisio_salut.sh   # -w: reescriu

shfmt formata en lloc de criticar: -i 4 fixa quatre espais, -d ensenya què canviaria sense tocar res (el costum de --dry-run del curs) i -w escriu. Sobre estil, tres decisions que no discutirem més: quatre espais d'indentació (mai tabuladors, que es trenquen en copiar), línies de menys de 100 caràcters partint amb \, i una instrucció per línia.

Errors Comuns i Consells

  • Escriure l'script a Windows i pujar-lo. Els finals CRLF fan que el shebang sigui #!/usr/bin/env bash\r i l'error, bad interpreter: No such file or directory, no diu res. Diagnostica'l amb file script.sh i arregla'l amb dos2unix.
  • Deixar línies abans del shebang. Ha de ser la primera línia, començant a la columna 1. I no anomenis test.sh el teu script: test és un builtin i el conflicte de noms dona maldecaps.
  • Executar amb sh script.sh un script amb shebang de Bash. El shebang s'ignora quan invoques l'intèrpret a mà: és la causa número u de «funciona al meu terminal i falla al servidor».
  • Esperar que un script canviï el teu directori o les teves variables. No pot: és un altre procés. Si de debò ho necessites, és una funció o un fitxer per fer source. I no oblidis l'exit 0 final: si l'última línia és un grep sense coincidències, el teu script correcte retorna 1 i cron t'avisa d'una fallada inexistent.
  • Consell: durant el desenvolupament, bash -n script.sh comprova la sintaxi sense executar res; costa un segon i evita descobrir un fi que falta a mig desplegament. Integra a més shellcheck al teu editor i comença cada script copiant la plantilla de l'apartat 7.

Exercicis

Exercici 1. Demostra empíricament, amb un sol fitxer i sense editar-lo entre proves, la diferència entre bash script.sh, ./script.sh i source script.sh. L'script ha de definir una variable, canviar de directori i imprimir el seu propi PID. Explica què observes en cada cas i per què.

Exercici 2. Escriu ~/scripts/info_release.sh: quina versió de Tramontana està activa (resolent /opt/tramontana/app), quant ocupa aquell release i quan es va desplegar. Aplica la plantilla completa, deixa'l net de shellcheck i publica'l a ~/bin com a info-release.

Exercici 3. En Luis ha deixat aquest script al servidor. Troba-hi quatre problemes diferents sense executar-lo, justifica'n cadascun i reescriu-lo.

#! /bin/sh
# neteja temporals
cd /srv/tramontana/backups/temporals
rm -rf *
if [[ $? == 0 ]]; then echo ok; fi

Solucions

Solució 1. $$ conté el PID del shell que executa el codi: és el testimoni que delata si hi ha hagut procés nou.

operador@srv-tramontana:~$ cat > /tmp/demo_ambit.sh <<'EOF'
#!/usr/bin/env bash
echo "PID de l'script   : $$"
LA_MEVA_VARIABLE="definida dins"
cd /opt/tramontana/releases
echo "Directori dins    : $PWD"
EOF
operador@srv-tramontana:~$ chmod +x /tmp/demo_ambit.sh; echo "El meu shell: $$"
El meu shell: 3120
operador@srv-tramontana:~$ /tmp/demo_ambit.sh
PID de l'script   : 3456
Directori dins    : /opt/tramontana/releases
operador@srv-tramontana:~$ echo "$PWD - ${LA_MEVA_VARIABLE:-(no existeix)}"
/home/operador - (no existeix)
operador@srv-tramontana:~$ source /tmp/demo_ambit.sh
PID de l'script   : 3120
Directori dins    : /opt/tramontana/releases
operador@srv-tramontana:~$ echo "$PWD - ${LA_MEVA_VARIABLE:-(no existeix)}"
/opt/tramontana/releases - definida dins

bash /tmp/demo_ambit.sh dona el mateix resultat que ./: PID diferent del del teu shell, per tant procés nou, i el cd i la variable moren amb ell. Només canvia el mecanisme: bash fitxer arrenca Bash explícitament i ignora el shebang, mentre que ./fitxer fa que el nucli llegeixi el shebang i decideixi —i per això aquell cas, i només aquell, exigeix el bit x—. El tercer imprimeix el mateix PID 3120: no hi ha procés nou, l'ha executat el teu propi Bash, i per això tot persisteix. El ${LA_MEVA_VARIABLE:-(no existeix)} evita que echo imprimeixi un buit ambigu; el veuràs en detall a 04-02.

Solució 2.

#!/usr/bin/env bash
#
# info_release.sh - Informa del release actiu de Tramontana Reserves.
#
# Autor : Operador de sistemes <operador@srv-tramontana>
# Data  : 2026-08-18
# Ús    : info_release.sh  (només lectura)

set -euo pipefail

ENLLAC_APP="/opt/tramontana/app"

# readlink -f resol l'enllaç fins al seu destí final en ruta absoluta,
# necessari perquè 'app' és un enllaç *relatiu* al release actiu.
ruta_release=$(readlink -f "$ENLLAC_APP")
versio=$(basename "$ruta_release")
mida=$(du -sh "$ruta_release" | cut -f1)

# stat sobre l'ENLLAÇ (no sobre el seu destí) dona quan es va canviar, que
# és exactament quan es va desplegar. cut treu la fracció de segon.
desplegat=$(stat -c '%y' "$ENLLAC_APP" | cut -d'.' -f1)

printf 'Release actiu  : %s\nRuta           : %s\n' "$versio" "$ruta_release"
printf 'Mida           : %s\nDesplegat      : %s\n' "$mida" "$desplegat"

exit 0
operador@srv-tramontana:~$ chmod 750 ~/scripts/info_release.sh
operador@srv-tramontana:~$ shellcheck ~/scripts/info_release.sh && echo "Net"
Net
operador@srv-tramontana:~$ ln -s ~/scripts/info_release.sh ~/bin/info-release
operador@srv-tramontana:~$ info-release
Release actiu  : 3.2.1
Ruta           : /opt/tramontana/releases/3.2.1
Mida           : 98M
Desplegat      : 2026-08-11 17:42:03

Solució 3. Els quatre problemes, de més greu a menys:

  1. cd sense comprovar seguit de rm -rf *. El defecte letal: si el directori no existeix o no hi tens permís, el cd falla, l'script continua on estiguessis i esborra tot el que hi hagi allà. És SC2164, i s'arregla amb cd ... || exit o amb mode estricte.
  2. #! /bin/sh amb [[ ]]. Dos errors: l'espai després de #! l'admet Linux però no tots els Unix, i sobretot /bin/sh és dash, que no coneix [[ ]]. L'script mor a l'última línia amb [[: not found.
  3. if [[ $? == 0 ]]. Redundant i fràgil: en Bash es ramifica directament sobre l'ordre (if rm -rf ...; then), com veurem a 04-04. A més == compara cadenes on s'hauria de fer servir un test numèric.
  4. Falta tota la resta: sense capçalera, sense mode estricte, sense exit explícit, i el comentari «neteja temporals» no diu què considera temporal ni des de quan. Un rm -rf * mereix més explicació que cap altra línia del fitxer.

La versió reescrita fa a més el que el nom promet —esborrar l'antic, no tot—:

#!/usr/bin/env bash
#
# netejar_temporals.sh - Esborra els temporals de còpia amb més de 7 dies.
# Autor : Operador de sistemes <operador@srv-tramontana>  Data: 2026-08-18

set -euo pipefail
DIR_TEMPORALS="/srv/tramontana/backups/temporals"
DIES_RETENCIO=7

# -mindepth 1 no toca el propi directori; -xdev no creua muntatges;
# -delete evita dependre d'un glob que podria quedar sense expandir i
# intentar esborrar un fitxer anomenat literalment *.
find "$DIR_TEMPORALS" -mindepth 1 -xdev -type f \
    -mtime +"$DIES_RETENCIO" -delete
echo "Temporals de més de ${DIES_RETENCIO} dies eliminats."
exit 0

Ja no hi ha cd, així que el perill desapareix d'arrel, i el mode estricte avorta si find falla.

Conclusió

Tens els fonaments, i són més dels que sembla.

  • Saps quan escriure un script i quan n'hi ha prou amb un àlies, una funció o Ansible, amb la regla de les tres repeticions com a criteri.
  • Entens que un script és text pla amb ordres, domines el shebang i per què #!/usr/bin/env bash és l'elecció per defecte, i coneixes el parany que a Ubuntu /bin/sh és dash i [[ ]] no hi existeix.
  • Distingeixes les tres formes d'executar un script i per què només source afecta el teu shell; col·loques els scripts a ~/scripts, els publiques amb un enllaç a ~/bin i continues sense posar . al PATH.
  • Arrenques tot script amb la mateixa plantilla —shebang, capçalera amb ús, set -euo pipefail, constants en majúscules, cos i exit 0—, comentes el perquè, retornes codis de sortida amb significat i passes shellcheck abans de donar res per acabat.

revisio_salut.sh ja existeix i ja funciona, però té les constants clavades al codi, no compara res i no sap dir si el resultat és bo o dolent. A la lliçó següent, Variables i Tipus de Dades, ataquem la primera mancança: veuràs que en Bash tot és una cadena i què implica això, dominaràs l'expansió de paràmetres —aquella col·lecció de ${VAR#...}, ${VAR:-...} i ${VAR//.../...} que converteix deu línies en una—, aprendràs per què Bash no sap sumar decimals i què fer amb l'import mitjà de reserves.csv, i comptaràs reserves per casa amb matrius associatives sense sortir del shell. En acabar-la, revisio_salut.sh tindrà la seva configuració fora del cos de l'script.

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