A la lliçó 02-02 ja vas fer servir | de manera intuïtiva per encadenar cut, sort i uniq. Ara entendràs què passa per sota i dominaràs l'altra meitat del mecanisme: la redirecció. Aquesta és la lliçó que converteix una col·lecció d'ordres soltes en un llenguatge de composició. I és també la que necessites perquè veloz-ops deixi d'imprimir a la pantalla i comenci a generar fitxers d'informe i registres d'execució, que és el que s'espera d'una eina d'operacions seriosa.

Contingut

  1. Els tres fluxos estàndard
  2. Redirigir la sortida: > i >>
  3. El risc de truncament i noclobber
  4. Redirigir l'entrada: <
  5. Redirigir els errors: 2>, 2>&1 i &>
  6. /dev/null: el forat negre del sistema
  7. tee: veure i desar alhora
  8. Canonades i el concepte de filtre
  9. Cadenes de filtres que responen preguntes reals
  10. El codi de sortida d'una canonada

  1. Els tres fluxos estàndard

Tot procés a Linux neix amb tres canals de comunicació oberts, identificats per un número anomenat descriptor de fitxer:

Nom Descriptor Direcció Destí per defecte
stdin (entrada estàndard) 0 Entra El teclat
stdout (sortida estàndard) 1 Surt La pantalla
stderr (sortida d'error) 2 Surt La pantalla
flowchart LR
    T[Teclat o fitxer] -->|stdin 0| C[ordre]
    C -->|stdout 1| S[Pantalla, fitxer o ordre següent]
    C -->|stderr 2| E[Pantalla o fitxer d'errors]

La pregunta òbvia és per què hi ha dues sortides si totes dues acaben a la pantalla. La resposta és la clau de tot el disseny: per poder separar-les quan cal. Una ordre escriu els seus resultats a stdout i les seves queixes a stderr, de manera que pots desar els resultats en un fitxer mentre els errors continuen apareixent al teu terminal, o just al revés. Comprova-ho:

Si executes ls /srv/veloz/dades /srv/veloz/inexistent, hi veuràs barrejats el llistat del primer directori i el missatge El fitxer o directori no existeix del segon. Semblen un sol bloc, però són fluxos diferents: així que en redirigeixis un, se separaran.

  1. Redirigir la sortida: > i >>

L'operador > envia stdout a un fitxer en lloc de a la pantalla:

cut -d, -f3 /srv/veloz/dades/enviaments.csv | sort | uniq -c > ~/veloz-ops/logs/ciutats.txt

No veuràs res per pantalla: la sortida és al fitxer. > crea el fitxer si no existeix i el buida completament si existeix. Aquest buidatge és total i instantani, i passa abans que l'ordre comenci a executar-se.

L'operador >> afegeix al final sense esborrar el que hi havia:

date >> ~/veloz-ops/logs/execucions.log
echo "informe diari completat" >> ~/veloz-ops/logs/execucions.log
dl ago  3 11:52:03 CEST 2026
informe diari completat

Regla pràctica: > per a resultats que es regeneren, >> per a registres que s'acumulen. Un informe diari que es refà cada matí fa servir >; el registre d'execucions del toolkit fa servir >>.

  1. El risc de truncament i noclobber

El perill de > és que no avisa. Escriure per error > enviaments.csv en lloc de >> enviaments.csv destrueix el fitxer de dades a l'acte, sense confirmació i sense recuperació possible. És l'equivalent silenciós del rm de la lliçó 02-01.

Bash ofereix una xarxa de seguretat, l'opció noclobber:

set -o noclobber
echo "prova" > ~/veloz-ops/logs/ciutats.txt
bash: /home/joan/veloz-ops/logs/ciutats.txt: no es pot sobreescriure el fitxer existent

Amb noclobber actiu, > es nega a sobreescriure un fitxer que ja existeix. Quan que ho vulguis fer, s'utilitza l'operador >|, que força el truncament:

echo "prova" >| ~/veloz-ops/logs/ciutats.txt   # sobreescriu tot i noclobber
set +o noclobber                                # desactivar l'opció

Fixa't en l'asimetria de set: -o activa una opció i +o la desactiva, al revés del que suggereix la intuïció. Activar noclobber al teu ~/.bashrc és una bona idea per a sessions interactives; en scripts es prefereix no dependre'n i escriure les redireccions amb cura.

  1. Redirigir l'entrada: <

L'operador < fa que una ordre llegeixi d'un fitxer en lloc del teclat:

wc -l < /srv/veloz/dades/enviaments.csv    # → 1247, sense el nom del fitxer

Compara-ho amb wc -l /srv/veloz/dades/enviaments.csv, que imprimeix també el nom del fitxer. La diferència és conceptual: en el primer cas wc no sap que hi ha un fitxer, només rep un flux de dades per stdin; en el segon, obre el fitxer ell mateix. Aquesta distinció importarà quan escriguis bucles que llegeixen fitxers línia a línia al Mòdul 4.

Hi ha ordres que només accepten entrada estàndard, com tr, que no admet un fitxer com a argument: tr 'a-z' 'A-Z' < enviaments.csv és l'única manera d'alimentar-lo des d'un fitxer.

  1. Redirigir els errors: 2>, 2>&1 i &>

Com que stderr és el descriptor 2, es redirigeix anteposant aquest número a l'operador:

ls /srv/veloz/dades /srv/veloz/inexistent > sortida.txt 2> errors.txt

Ara sortida.txt conté només el llistat correcte i errors.txt només el missatge d'error. 2>> afegeix en lloc de truncar, exactament igual que >>.

Per combinar tots dos fluxos al mateix destí es fa servir 2>&1, que es llegeix «envia el descriptor 2 allà on apunta ara mateix el descriptor 1»:

./informe-diari.sh > ~/veloz-ops/logs/informe.log 2>&1

L'ordre importa, i és el parany clàssic. Bash processa les redireccions d'esquerra a dreta:

Escriptura Què passa
cmd > fitx 2>&1 1 va al fitxer; després 2 s'apunta on és 1 → tots dos al fitxer
cmd 2>&1 > fitx 2 s'apunta on és 1, que encara és la pantalla; després 1 va al fitxer → stdout al fitxer, stderr a la pantalla

La segona forma és un error real i freqüent: sembla que ho desa tot i en realitat els errors es perden al terminal. Recorda que 2>&1 copia el destí actual d'1, no crea un vincle permanent.

Bash ofereix a més una forma abreujada pròpia, més llegible i sense risc d'ordre:

./informe-diari.sh &>  ~/veloz-ops/logs/informe.log   # tot al fitxer (trunca)
./informe-diari.sh &>> ~/veloz-ops/logs/informe.log   # tot al fitxer (afegeix)

&> no és POSIX: funciona a Bash però no a sh pur. Per a scripts portables es fa servir > fitx 2>&1 (portabilitat, 08-07).

  1. /dev/null: el forat negre del sistema

/dev/null és un fitxer especial que descarta tot el que s'hi escriu i retorna buit en llegir-lo. Serveix per silenciar sortides que no interessen:

grep -q ERROR /var/log/veloz/app.log 2>/dev/null   # silenciar només els errors
ordre >/dev/null 2>&1                              # silenciar absolutament tot

El segon patró és un dels més freqüents en scripting. Es fa servir quan només t'importa el codi de sortida de l'ordre, no la seva sortida, típicament en una comprovació:

if ping -c1 srv-veloz-01 >/dev/null 2>&1; then echo "servidor accessible"; fi

Aquí no volem veure les estadístiques de ping, només saber si ha respost. Ara bé, silenciar errors és perillós: 2>/dev/null en un script de producció pot amagar exactament la fallada que necessitaves veure. Fes-ho servir només quan l'error sigui esperat i estigui previst.

  1. tee: veure i desar alhora

tee (per la «T» de lampisteria) duplica el flux: l'escriu en un fitxer i el deixa continuar per stdout.

grep ERROR /var/log/veloz/app.log | tee ~/veloz-ops/logs/errors-avui.log | wc -l
37

En una sola línia has desat els errors complets en un fitxer i has vist el recompte a la pantalla. Amb -a (append) afegeix en lloc de truncar, i combinat amb sudo resol un problema clàssic:

echo "veloz-api" | sudo tee -a /etc/veloz/serveis.conf

Això funciona on sudo echo ... >> /etc/... falla, i entendre'n el motiu és important: la redirecció la fa el teu shell, no sudo, així que és el teu usuari sense privilegis qui intenta obrir el fitxer del sistema. Amb tee, el procés privilegiat és el que escriu.

  1. Canonades i el concepte de filtre

Una canonada | connecta el stdout de l'ordre de l'esquerra amb el stdin de la de la dreta. Els dos processos s'executen alhora, no un darrere l'altre: les dades flueixen a mesura que es produeixen, sense fitxers intermedis i sense carregar res sencer a memòria.

Un filtre és un programa que llegeix de stdin, transforma i escriu a stdout. grep, cut, sort, uniq, tr, wc, head, tail i column són tots filtres, i per això encaixen entre ells en qualsevol ordre.

cat /var/log/veloz/app.log | grep ERROR | wc -l

Dos advertiments:

  • La canonada només transporta stdout. Els errors de la primera ordre van a la pantalla, no a la segona. Si els hi vols passar també, cmd 2>&1 | filtre (Bash té la drecera |&).
  • Evita l'«ús inútil de cat». L'exemple anterior s'escriu millor com grep -c ERROR /var/log/veloz/app.log: un procés en lloc de tres. cat al capdavant d'una canonada només es justifica quan concatenes diversos fitxers.

  1. Cadenes de filtres que responen preguntes reals

Aquí és on tot el mòdul convergeix. Les 10 IP que més peticions han fet avui a veloz-api:

cut -d' ' -f1 /var/log/veloz/acces.log | sort | uniq -c | sort -rn | head -10 \
  | tee ~/veloz-ops/logs/top-ips.txt
   4821 10.20.4.15
   2103 10.20.4.77

Pas a pas: cut -d' ' -f1 extreu el primer camp separat per espais, que en el format combinat és la IP → sort agrupa les iguals → uniq -c compta cada grup → sort -rn ordena per número descendent → head -10 es queda amb el podi → tee el desa a més al toolkit.

Ciutats amb més incidències, desant el resultat com a informe:

grep ',incidencia,' /srv/veloz/dades/enviaments.csv \
  | cut -d, -f3 | sort | uniq -c | sort -rn \
  | column -t > ~/veloz-ops/logs/incidencies-per-ciutat.txt

I per comptar i registrar alhora les peticions que van retornar error 500: grep ' 500 ' /var/log/veloz/acces.log | tee ~/veloz-ops/logs/errors-500.log | wc -l.

Fixa't en el patró general que es repeteix: filtrar → extreure → agrupar → comptar → ordenar → presentar → desar. Gairebé qualsevol pregunta sobre dades tabulars o registres es respon amb alguna variant d'aquesta seqüència, i aquest és exactament l'esquelet de l'analitzador de registres que construiràs al projecte 09-02.

  1. El codi de sortida d'una canonada

A la lliçó 01-04 vas veure $?, el codi de sortida de l'última ordre. En una canonada, $? és el de l'última ordre de la cadena, no el de tota ella:

grep INEXISTENT /var/log/veloz/app.log | wc -l   # imprimeix 0
echo $?                                           # imprimeix 0 també

grep no ha trobat res i ha retornat 1, però wc -l s'ha executat correctament i ha retornat 0, així que $? val 0. La canonada sembla haver tingut èxit quan en realitat el filtre ha fallat. És una font inesgotable de bugs silenciosos en scripts.

Bash ofereix dues solucions: l'array PIPESTATUS, que desa el codi de cada ordre de la canonada, i l'opció set -o pipefail, que fa que la canonada retorni el codi de la primera ordre que ha fallat. Totes dues pertanyen a la gestió d'errors i les estudiaràs a fons a 05-03; de moment, queda't amb l'advertiment.

Un últim apunt: quan necessitis passar la sortida d'una ordre com a arguments d'una altra (no com a entrada estàndard), la canonada no serveix i cal xargs, que veuràs a 05-01. I les formes avançades d'E/S —here-documents i descriptors propis— arriben a 05-05.

Errors Habituals i Consells

  • Escriure > fitxer volent >>. Destrueix el contingut a l'instant. Considera set -o noclobber al teu ~/.bashrc.
  • Posar 2>&1 abans de >. Els errors es queden a la pantalla. La forma correcta és cmd > fitx 2>&1, o directament cmd &> fitx.
  • Creure que la canonada transporta els errors. Només porta stdout; fes servir 2>&1 | si els necessites.
  • Fer servir sudo cmd > /etc/fitxer. La redirecció la fa el teu shell sense privilegis i falla. Fes servir sudo tee.
  • Abusar de 2>/dev/null. Amaga el diagnòstic que anaves a necessitar. Silencia només errors esperats.
  • Confiar en $? després d'una canonada. Només reflecteix l'última ordre (vegeu 05-03).
  • Llegir i escriure el mateix fitxer en una ordre. sort fitxer > fitxer deixa el fitxer buit, perquè > el trunca abans que sort el llegeixi. Escriu en un temporal i després reanomena amb mv.

Exercicis

Exercici 1 — Separar resultats i errors. Executa un ls sobre /srv/veloz/dades i sobre un directori inexistent, de manera que el llistat correcte quedi a ~/veloz-ops/logs/llistat.txt i el missatge d'error a ~/veloz-ops/logs/llistat.err. Després escriu la variant que ho desi tot junt en un sol fitxer, de les dues maneres possibles.

Exercici 2 — Informe de repartidors amb incidències. Sobre enviaments.csv, genera un fitxer ~/veloz-ops/logs/incidencies-repartidor.txt amb el nombre d'incidències de cada repartidor, ordenat de més a menys i presentat en taula alineada. Has de veure el resultat a la pantalla al mateix temps que es desa.

Exercici 3 — Depurar una ordre que falla en silenci. Un company té aquesta línia en un script i es queixa que «mai no detecta res»:

grep TIMEOUT /var/log/veloz/app.log | wc -l > /dev/null 2>&1
if [ $? -eq 0 ]; then echo "hi ha timeouts"; fi

Explica les dues fallades i reescriu-la correctament.

Solucions

Solució a l'Exercici 1

# Separats
ls /srv/veloz/dades /srv/veloz/inexistent \
   > ~/veloz-ops/logs/llistat.txt 2> ~/veloz-ops/logs/llistat.err

# Tot junt, forma POSIX (portable)
ls /srv/veloz/dades /srv/veloz/inexistent > ~/veloz-ops/logs/tot.log 2>&1

# Tot junt, forma abreujada de Bash
ls /srv/veloz/dades /srv/veloz/inexistent &> ~/veloz-ops/logs/tot.log

Les dues últimes són equivalents a Bash. La primera és preferible en scripts que es puguin executar amb sh; la segona és més llegible i elimina el risc d'invertir l'ordre. El que no has d'escriure és ls ... 2>&1 > ~/veloz-ops/logs/tot.log, que deixaria els errors a la pantalla.

Solució a l'Exercici 2

grep ',incidencia,' /srv/veloz/dades/enviaments.csv \
  | cut -d, -f4 | sort | uniq -c | sort -rn | column -t \
  | tee ~/veloz-ops/logs/incidencies-repartidor.txt
52  mgarcia
41  alopez
25  jruiz

grep ',incidencia,' filtra pel camp d'estat amb les comes al davant i al darrere, cosa que evita falsos positius si aquesta paraula aparegués en un altre camp; cut -d, -f4 extreu el repartidor; sort | uniq -c agrupa i compta; sort -rn ordena descendentment; column -t alinea; i tee al final —en lloc de >— és el que permet veure el resultat i desar-lo simultàniament.

Solució a l'Exercici 3

Les dues fallades:

  1. $? recull el codi de wc, no el de grep. wc -l sempre acaba amb èxit, així que la condició es compleix sempre, hi hagi timeouts o no. L'script diria «hi ha timeouts» fins i tot amb el registre buit.
  2. La redirecció > /dev/null 2>&1 no aporta res d'útil aquí i de passada descarta el número que wc havia calculat, amb la qual cosa la informació es perd del tot.

Versió correcta, fent servir el codi de sortida de grep (0 si ha trobat alguna cosa, 1 si no):

if grep -q TIMEOUT /var/log/veloz/app.log; then
    echo "hi ha timeouts"
fi

grep -q (quiet) no imprimeix res i retorna només el codi de sortida, així que substitueix alhora el wc i la redirecció a /dev/null. Si a més necessites el recompte:

n=$(grep -c TIMEOUT /var/log/veloz/app.log)
if [ "$n" -gt 0 ]; then
    echo "hi ha $n timeouts"
fi

La sintaxi $(...) és substitució d'ordres i les condicionals amb if es formalitzen al Mòdul 3; aquí el que importa és el diagnòstic: avalua el codi de sortida de l'ordre que de debò decideix, mai el de l'últim graó d'una canonada.

Conclusió

Has entès el mecanisme que fa de Bash un llenguatge de composició. Saps que tot procés té tres fluxos —stdin, stdout i stderr—, i per què separar-los és útil; redirigeixes sortides amb > i >> coneixent el risc del truncament i la protecció de noclobber; redirigeixes l'entrada amb <; controles els errors amb 2>, 2>&1 i &>, sabent que l'ordre importa; silencies soroll amb /dev/null amb la deguda prudència; dupliques fluxos amb tee, fins i tot per escriure en fitxers del sistema amb sudo; i encadenes filtres amb | per respondre preguntes que cap ordre no resol tota sola. També saps que $? després d'una canonada només reflecteix l'últim graó, un detall que resoldràs definitivament a 05-03.

Amb això, les ordres del Mòdul 2 deixen de ser peces soltes: són components de línies de muntatge que ja produeixen informes reals a ~/veloz-ops/logs.

A la lliçó 02-05 tanquem el cercle de la manipulació de fitxers amb els comodins. Descobriràs que *.csv no l'interpreta ls ni rm, sinó el mateix shell abans d'executar-los, i que aquesta diferència explica des de l'error rm * més famós de la història fins a per què un bucle falla quan un patró no casa amb res.

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