La lliçó anterior va acabar amb una frontera clara: awk llegeix i resumeix, però no és l'eina per reescriure text conservant-ne la forma. Quan cal anonimitzar les IP d'acces.log abans d'enviar-lo a un proveïdor, arreglar els separadors d'un CSV mal exportat o canviar una clau de veloz-ops.conf sense obrir un editor, el que fa falta és un editor de flux: un programa que rebi text per un costat, apliqui una llista d'ordres d'edició a cada línia i deixi anar el resultat per l'altre. Això és sed, i funciona amb les mateixes expressions regulars que ja domines des de 05-04, només que aplicades a transformar en lloc de a filtrar.

Contingut

  1. El cicle de sed i l'espai de patrons
  2. sed, awk o ${var//a/b}: quin fer servir
  3. Sintaxi i opcions
  4. -i: editar fitxers al lloc, amb xarxa de seguretat
  5. Adreces: a quines línies s'aplica una ordre
  6. L'ordre s/// a fons
  7. Altres ordres: p, d, q, a, i, c, y
  8. Filtrar i extreure trams
  9. Encadenar ordres i blocs
  10. Espai de retenció i N, a grans trets
  11. Receptes reals del toolkit
  12. Aplicació: veloz_anonimitza_log a lib/comu.sh

  1. El cicle de sed i l'espai de patrons

sed no veu el fitxer sencer: treballa línia a línia sobre una memòria intermèdia anomenada espai de patrons. Per a cada línia de l'entrada repeteix sempre el mateix cicle.

flowchart LR
    A[Llegeix una linia] --> B["La copia a<br/>l'espai de patrons"]
    B --> C[Aplica les ordres<br/>en ordre]
    C --> D{-n actiu?}
    D -- No --> E["Imprimeix<br/>l'espai de patrons"]
    D -- Si --> F[No imprimeix res]
    E --> G["Esborra l'espai<br/>i seguent linia"]
    F --> G
    G --> A

D'aquest esquema en surten tres conseqüències que expliquen gairebé tot el comportament de sed:

  • Les ordres s'apliquen en ordre sobre la mateixa memòria intermèdia. Si la primera substitueix A per B, la segona ja veu B. Encadenar substitucions no és el mateix que aplicar-les per separat.
  • La impressió és automàtica al final del cicle, no la fa l'ordre. Per això sed 's/a/b/' imprimeix totes les línies, no només les que ha canviat.
  • -n apaga aquesta impressió automàtica, i a partir d'aquí només surt el que demanis explícitament amb p. D'aquí ve l'idioma sed -n '...p'.

  1. sed, awk o ${var//a/b}: quin fer servir

Les tres eines substitueixen text, i triar malament és la causa més habitual de codi enrevessat:

${var//a/b} (04-04) sed awk (06-01)
Opera sobre Una variable en memòria Un flux o fitxer Un flux o fitxer
Cost Zero: és Bash pur Un procés extern Un procés extern
Emparellament Globs, no regex Regex (BRE o ERE) Regex (ERE)
Entén de camps No No (només línies) : $1, $NF
Sap fer comptes Només enters No Sí, en coma flotant
Recorda entre línies No hi aplica Només amb l'espai de retenció Sí: variables i arrays
Fort en Un valor solt Reescriure text Agregar i calcular

La regla pràctica: si el text ja és en una variable, ${var//a/b} i no llancis cap procés; si cal reescriure un fitxer o un flux, sed; si cal sumar, comptar o mirar columnes, awk. I si et trobes comptant camps amb sed, o substituint amb awk en diverses passades, gairebé segur que has triat l'eina equivocada.

  1. Sintaxi i opcions

sed [opcions] 'ordres' fitxer...

Les ordres van entre cometes simples pel mateix motiu que a awk: contenen $, & i barres invertides que Bash expandiria. Les opcions que es fan servir de debò:

Opció Què fa
-n Suprimeix la impressió automàtica (vegeu la secció 1)
-e 'cmd' Afegeix un guió d'ordres; es pot repetir per acumular-ne diversos
-f fitxer.sed Llegeix les ordres d'un fitxer, com el -f d'awk
-i[SUF] Edita el fitxer al lloc en comptes d'escriure a la sortida estàndard
-E Fa servir ERE en lloc de BRE (-r és el sinònim antic de GNU)
-z Separa registres per byte nul, per casar-lo amb find -print0 (05-01)

-E mereix una recomanació explícita. Per defecte sed parla BRE, el dialecte de 05-04 en què +, ?, (, ) i | són literals i s'han d'escapar: s/[0-9]\+/N/. Amb -E escrius s/[0-9]+/N/, que és el que tens al cap. Fes servir -E sempre llevat que copiïs una recepta aliena escrita en BRE.

  1. -i: editar fitxers al lloc, amb xarxa de seguretat

-i és l'opció més útil i la més perillosa de sed: reescriu el fitxer original sense preguntar i sense possibilitat de desfer. Un patró mal escrit pot buidar un fitxer de configuració en producció.

sed -i.bak 's/timeout=30/timeout=60/' ~/veloz-ops/etc/veloz-ops.conf   # deixa veloz-ops.conf.bak

Dos hàbits que eviten el desastre:

  1. Prova sempre sense -i primer. Executa l'ordre tal qual, mira la sortida, i només llavors afegeix -i. És gratis i resol el 95 % dels ensurts.
  2. Fes servir un sufix de còpia, -i.bak, no -i a seques. Costa cinc pulsacions i deixa l'original recuperable.

Aquí hi ha a més una incompatibilitat clàssica: a GNU sed (Linux) el sufix va enganxat, -i.bak, i -i tot sol és vàlid; a BSD/macOS -i exigeix argument, així que sense còpia s'escriu -i ''. Conseqüència: sed -i.bak funciona als dos, sed -i només a Linux, i un script que faci servir sed -i '' fallarà a Linux interpretant '' com a nom de fitxer. Si l'script ha de córrer als dos llocs, la sortida neta és escriure a un temporal (mktemp, 05-01) i moure'l a sobre amb mv.

I un avís seriós: no llancis mai sed -i sobre fitxers d'/etc sense còpia prèvia. sed -i 's/PermitRootLogin no/PermitRootLogin yes/' /etc/ssh/sshd_config és un canvi de seguretat fet a cegues, sense validació i sense marxa enrere. Les implicacions es veuen a 08-03.

  1. Adreces: a quines línies s'aplica una ordre

Una ordre sense adreça s'aplica a totes les línies. Anteposant-li una adreça es restringeix:

Adreça S'aplica a
5 Només la línia 5
$ L'última línia
/ERROR/ Les línies que casen amb la regex
2,5 De la línia 2 a la 5
2,$ De la 2 fins al final
/inici/,/fi/ Del primer emparellament d'una al següent de l'altra
/ERROR/,+3 La línia que casa i les 3 següents (GNU)
0~3 Una de cada 3 línies (GNU: primera~pas)
/ERROR/! Negació: totes les que NO casen

Així, sed -n '/2026-08-03 10:/,/2026-08-03 11:/p' /var/log/veloz/app.log extreu una hora del log, i sed '1!d' enviaments.csv deixa només la capçalera. El ! es llegeix «excepte»: 1!d és «esborra les línies que no són la 1». És la manera idiomàtica de conservar només una cosa, i apareix constantment a les receptes reals.

  1. L'ordre s/// a fons

És l'ordre estrella i la que justifica el 90 % dels usos de sed. La seva forma és s/regex/reemplacament/banderes:

sed -E 's/[0-9]{4}-[0-9]{2}-[0-9]{2}/DATA/g' /var/log/veloz/app.log

El delimitador no ha de ser /. Qualsevol caràcter val, i triar bé evita la «síndrome de les barres inclinades» quan treballes amb camins:

sed 's|/srv/veloz/dades|/mnt/copia/dades|' camins.txt         # llegible
sed 's/\/srv\/veloz\/dades/\/mnt\/copia\/dades/' camins.txt   # el mateix canvi, illegible

Les banderes que van després de l'últim delimitador:

Bandera Efecte
g Substitueix totes les ocurrències de la línia, no només la primera
i Ignora majúscules i minúscules
p Imprimeix la línia si hi ha hagut substitució (es fa servir amb -n)
w f Escriu al fitxer f les línies substituïdes
2 Substitueix només la 2a ocurrència; 2g des de la 2a endavant

Al reemplaçament hi ha dos metacaràcters que cal conèixer. & significa «tot el que ha casat», i \1, \2… són els grups de captura de 05-04, amb els grups escrits \\(...\\) en BRE o (...) amb -E:

# Posar entre claudators el nivell d'app.log:  [ERROR] -> [[ERROR]]
sed -E 's/\[(INFO|WARN|ERROR)\]/[&]/' /var/log/veloz/app.log
# Reordenar "data nivell" a "nivell data" fent servir dos grups
sed -E 's/^([0-9-]+ [0-9:]+) \[([A-Z]+)\]/[\2] \1/' /var/log/veloz/app.log

Corol·lari pràctic: si vols un & literal al reemplaçament, escapa'l (\&), igual que cal escapar el delimitador si apareix dins del patró. I recorda que s/// sense g canvia només la primera ocurrència de cada línia: és l'oblit número u.

  1. Altres ordres: p, d, q, a, i, c, y

Ordre Què fa Exemple
p Imprimeix l'espai de patrons (amb -n, és l'única que imprimeix) sed -n '5p'
d Esborra la línia i comença el cicle següent sed '/^#/d'
q Surt de sed immediatament sed '100q'
a text Afegeix una línia després sed '$a fi informe'
i text Insereix una línia abans sed '1i id,data,ciutat'
c text Canvia la línia sencera sed '/obsolet/c # linia retirada'
y/abc/xyz/ Tradueix caràcter a caràcter, com tr (02-02) y/aeiou/AEIOU/

d té una peculiaritat important: avorta el cicle, així que les ordres posteriors no s'executen sobre aquesta línia. I q és un optimitzador silenciós: sed '100q' fitxer_gegant deixa de llegir a la línia 100, mentre que head -100 amb una canonada continuaria llegint. La sintaxi d'a/i/c amb text a la mateixa línia és una extensió de GNU còmoda però no portable; la forma POSIX fa servir una barra invertida i un salt de línia.

  1. Filtrar i extreure trams

Amb -n i p es filtra igual que amb grep: sed -n '/ERROR/p' app.log fa exactament el mateix que grep ERROR app.log. Si només vas a filtrar, fes servir grep. És més llegible, sol ser més ràpid, i té -c, -v, -i, -l i -o per als casos habituals. sed guanya tan bon punt l'operació deixa de ser «sí o no» i passa a ser «transformar», o quan el que vols és un tram per número de línia, que grep no sap fer:

sed -n '10,20p' /var/log/veloz/app.log          # linies 10 a 20
sed -n '10,20p;20q' /var/log/veloz/app.log      # igual, pero deixa de llegir a la 20

Aquest ;20q és el detall que marca la diferència en un log de dos gigues: sense ell, sed continua llegint fins al final encara que ja no imprimeixi res.

  1. Encadenar ordres i blocs

Diverses ordres se separen amb ; o amb diversos -e. I si vols aplicar un grup d'ordres a la mateixa adreça, s'agrupen amb claus:

sed -E '/^#/d; /^[[:space:]]*$/d' ~/veloz-ops/etc/veloz-ops.conf   # treu comentaris i buides
sed -n '/inici informe/,/fi informe/{ s/^/  /; p }' informe.txt    # sagna nomes aquest bloc

Al segon exemple, { s/^/ /; p } s'aplica únicament a les línies del rang: primer sagna, després imprimeix. L'ordre importa —recorda la secció 1: les ordres veuen la memòria intermèdia tal com l'ha deixat l'anterior—. Si invertissis p i s, imprimiries la línia sense sagnar.

  1. Espai de retenció i N, a grans trets

A més de l'espai de patrons, sed té una segona memòria intermèdia, l'espai de retenció, que sobreviu entre línies. Es maneja amb h (copiar patró→retenció), H (afegir), x (intercanviar) i G (afegir retenció→patró). És el que dona a sed una memòria mínima: sed G informe.txt afegeix una línia en blanc després de cada línia, i el críptic sed '1!G;h;$!d' fitxer inverteix l'ordre de les línies —el tac de sed—.

L'ordre N afegeix la línia següent a l'espai de patrons, i permet que una regex creui dues línies —útil per ajuntar una línia d'app.log amb la seva traça—. Dit això, aquí hi ha el límit honest de l'eina: tan bon punt necessitis aquest nivell d'acrobàcia, awk o un llenguatge de guió seran més llegibles i més fàcils de mantenir. Sàpigues que existeix, no ho converteixis en el teu estil.

  1. Receptes reals del toolkit

Anonimitzar les IP d'acces.log conservant el primer octet per saber la xarxa d'origen, i normalitzar els separadors d'un CSV exportat amb punt i coma i coma decimal:

sed -E 's/^([0-9]{1,3})\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}/\1.x.x.x/' /var/log/veloz/acces.log
sed -E 's/;/,/g; s/([0-9]),([0-9]{2})$/\1.\2/' enviaments-cru.csv > enviaments.csv

Canviar una clau de configuració de forma idempotent, és a dir, que es pugui executar mil vegades amb el mateix resultat i que funcioni tant si la clau existia com si no:

if grep -q '^LLINDAR_DISC=' "$CONF"; then
    sed -i.bak -E 's/^LLINDAR_DISC=.*/LLINDAR_DISC=85/' "$CONF"
else
    printf 'LLINDAR_DISC=85\n' >> "$CONF"
fi

L'if no és cap adorn: sed no pot afegir una línia que no existeix, només transformar les que hi ha. Sense la comprovació prèvia, l'script semblaria funcionar i no canviaria res. Fixa't en l'àncora ^, que evita tocar una clau que només contingui aquest text, i en el .* que es menja el valor antic sigui quin sigui.

Netejar un fitxer de configuració per veure'l sense soroll, i inserir una capçalera a un CSV que la va perdre:

sed -E '/^[[:space:]]*#/d; /^[[:space:]]*$/d' /etc/veloz/api.conf
sed '1i id_enviament,data,ciutat,repartidor,estat,import' sense-capcalera.csv > enviaments.csv

  1. Aplicació: veloz_anonimitza_log a lib/comu.sh

La recepta de la IP es converteix en funció reutilitzable, seguint les regles de llibreria de 05-06: només definició, prefix d'espai de noms, sense codi de nivell superior:

# veloz_anonimitza_log — Anonimitza IP i correus d'un log. Us: veloz_anonimitza_log <fitxer>
# Escriu el resultat per la sortida estandard; no toca l'original.
veloz_anonimitza_log() {
    local origen="${1:?falta el fitxer de log}"
    [[ -r "$origen" ]] || { veloz_log_error "no puc llegir $origen"; return 66; }
    sed -E -e 's/^([0-9]{1,3})\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}/\1.x.x.x/' \
           -e 's/[[:alnum:]._%+-]+@[[:alnum:].-]+\.[[:alpha:]]{2,}/<correu>/g' \
           -- "$origen"
}

Tres decisions mereixen comentari. La funció no fa servir -i: escriu a la sortida estàndard i deixa que qui la crida decideixi (veloz_anonimitza_log "$LOG" > /tmp/compartir.log), que és el correcte en una llibreria —una funció que modifica fitxers aliens per sorpresa és un parany—. Els dos -e separats són més llegibles que un ; llarg i permeten comentar-ne un en depurar. I el -- marca el final d'opcions, així que un fitxer anomenat -estrany.log es tracta com a fitxer i no com a opció; és el mateix hàbit defensiu de 03-05. El codi 66 és EX_NOINPUT, de la taula de 05-03.

Verificar abans de confiar és obligatori amb sed: una comprovació ràpida com head -3 /var/log/veloz/acces.log | veloz_anonimitza_log /dev/stdin n'hi ha prou per veure que les IP surten emmascarades i la resta de la línia intacta.

Errors Habituals i Consells

  • Oblidar la bandera g. s/,/;/ canvia només la primera coma de cada línia. Per a totes, s/,/;/g.
  • Fer servir -i sense provar abans. Executa sempre l'ordre sense -i, mira la sortida i només llavors edita. I fes servir -i.bak, que funciona a GNU i a BSD.
  • Esperar ERE per defecte. sed 's/[0-9]+/N/' no fa res, perquè en BRE + és un + literal. Afegeix -E.
  • Escapar barres a mà. Si el patró porta camins, canvia de delimitador: s|/a/b|/c/d|.
  • Confondre el . dins d'una IP. [0-9]{1,3}.[0-9] casa també amb 192x168; el punt literal s'escriu \..
  • Creure que sed pot afegir claus que falten. Només transforma línies existents; per afegir, comprova amb grep -q i fes servir >>.
  • Fer servir sed per filtrar. Si és un «sí o no», grep; si cal canviar el text, sed; si hi ha columnes o comptes, awk.
  • Consell: amb fitxers grans, sed -n '10,20p;20q' i sed '100q' eviten llegir de més. LC_ALL=C sed ... també accelera.
  • Consell: un guió de sed llarg es treu a un fitxer .sed i s'invoca amb -f, igual que a awk: es comenta amb # i es versiona.

Exercicis

Exercici 1. Escriu una ordre que extregui de /var/log/veloz/app.log únicament les línies de l'interval entre 10:00:00 i 11:00:00 i els tregui la data (deixant només hora, nivell i missatge), sense modificar el fitxer original.

Exercici 2. Escriu una funció veloz_set_conf per a lib/comu.sh que fixi una clau a un valor a ~/veloz-ops/etc/veloz-ops.conf de forma idempotent, amb còpia de seguretat, i que conservi els permisos 600 del fitxer.

Exercici 3. El proveïdor envia enviaments-cru.csv amb separador ;, imports amb coma decimal, espais sobrants al voltant dels camps i una línia de comentari inicial que comença per #. Escriu un sol sed que el deixi en el format d'enviaments.csv.

Solucions

Solució 1.

sed -nE '/ 10:00:00 /,/ 11:00:00 /{ s/^[0-9]{4}-[0-9]{2}-[0-9]{2} //; p }' /var/log/veloz/app.log

El rang /inici/,/fi/ selecciona el tram, i les claus apliquen les dues ordres només a ell: primer es treu la data amb s/// ancorat a ^, després s'imprimeix amb p. -n és imprescindible —sense ell sortiria el fitxer sencer, amb el tram a més duplicat pel p—. I l'ordre dins de les claus importa: si p anés primer, imprimiria la línia amb la data encara posada.

Solució 2.

# veloz_set_conf — Fixa clau=valor de forma idempotent. Us: veloz_set_conf CLAU valor
veloz_set_conf() {
    local clau="${1:?falta la clau}" valor="${2-}" conf="${VELOZ_CONF:?}"
    [[ "$clau" =~ ^[A-Z_][A-Z0-9_]*$ ]] || { veloz_log_error "clau invalida: $clau"; return 64; }
    if grep -q "^${clau}=" "$conf"; then
        sed -i.bak -E "s|^${clau}=.*|${clau}=${valor}|" "$conf"
    else
        printf '%s=%s\n' "$clau" "$valor" >> "$conf"
    fi
    chmod 600 "$conf"
}

Aquí sí que es fan servir cometes dobles, perquè cal interpolar $clau i $valor, i per això la validació prèvia amb =~ (05-04) no és opcional: sense ella, una clau amb | o & trencaria la substitució o hi injectaria comportament. El delimitador | evita problemes si el valor és un camí, i chmod 600 al final restaura els permisos, perquè sed -i crea un fitxer nou i el reanomena, de manera que els permisos poden canviar. El codi 64 és EX_USAGE.

Solució 3.

sed -E '/^#/d; s/[[:space:]]*;[[:space:]]*/,/g; s/^[[:space:]]+//; s/[[:space:]]+$//; s/([0-9]),([0-9]{2})$/\1.\2/' \
    enviaments-cru.csv > /srv/veloz/dades/enviaments.csv

L'ordre és deliberat i és tot l'exercici. Primer s'esborra el comentari (d avorta el cicle, així que aquesta línia ni es processa). Després es normalitzen els ; absorbint els espais que els envolten, cosa que resol de cop el sobrant interior. Tot seguit es retallen els extrems de la línia. I només al final s'arregla el decimal: si aquesta substitució anés abans, la , acabada de crear pel pas del ; podria confondre's amb el decimal. Convé verificar-ho amb head -3 enviaments-cru.csv | sed -E '...' abans de sobreescriure res.

Conclusió

sed és un editor de flux: per cada línia la copia a l'espai de patrons, li aplica les ordres en ordre i la imprimeix en acabar el cicle llevat que -n ho impedeixi —d'aquí l'idioma sed -n '...p'—. Les seves ordres accepten adreces (número de línia, $, regex, rangs 2,5 i /inici/,/fi/, i la negació !) que decideixen a quines línies s'apliquen, i el rei de totes és s/regex/reemplacament/banderes, amb delimitador lliure per no barallar-se amb els camins, banderes g, i, p i w, i un reemplaçament on & és allò que ha casat i \1 els grups de 05-04. Al voltant hi ha d, p, q, a, i, c i y, els blocs { } per agrupar per adreça, i l'espai de retenció (h, x, G, N) com a memòria mínima que convé conèixer i no abusar-ne. -E per escriure ERE còmodament, -i.bak sempre amb còpia i provant abans sense -i, i la frontera clara amb els seus veïns: ${var//a/b} per a una variable, grep per filtrar, awk per a columnes i comptes, sed per reescriure.

Amb awk i sed, el toolkit ja sap llegir, resumir i transformar qualsevol text que li tiris. Però tot aquest text continua venint de fitxers que algú va deixar al disc. Un script d'operacions necessita alguna cosa més: preguntar al mateix sistema en quin estat és —quant disc queda, quina càrrega suporta, qui hi ha connectat, quina versió de sistema operatiu corre, si el servei veloz-api és viu— per poder decidir en funció de la resposta. Això és la lliçó següent (06-03): les ordres que interroguen el sistema i com convertir les seves respostes en llindars i decisions dins d'un script.

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