La lección anterior terminó con una frontera clara: awk lee y resume, pero no es la herramienta para reescribir texto conservando su forma. Cuando hay que anonimizar las IP de acceso.log antes de mandárselo a un proveedor, arreglar los separadores de un CSV mal exportado o cambiar una clave de veloz-ops.conf sin abrir un editor, lo que hace falta es un editor de flujo: un programa que reciba texto por un lado, aplique una lista de órdenes de edición a cada línea y suelte el resultado por el otro. Eso es sed, y funciona con las mismas expresiones regulares que ya dominas desde 05-04, solo que aplicadas a transformar en lugar de a filtrar.
Contenido
- El ciclo de sed y el espacio de patrones
sed,awko${var//a/b}: cuál usar- Sintaxis y opciones
-i: editar ficheros en el sitio, con red de seguridad- Direcciones: a qué líneas se aplica un comando
- El comando
s///a fondo - Otros comandos:
p,d,q,a,i,c,y - Filtrar y extraer tramos
- Encadenar comandos y bloques
- Espacio de retención y
N, en pinceladas - Recetas reales del toolkit
- Aplicación:
veloz_anonimiza_logenlib/comun.sh
- El ciclo de sed y el espacio de patrones
sed no ve el fichero entero: trabaja línea a línea sobre un búfer llamado espacio de patrones. Para cada línea de la entrada repite siempre el mismo ciclo.
flowchart LR
A[Lee una linea] --> B[La copia al<br/>espacio de patrones]
B --> C[Aplica los comandos<br/>en orden]
C --> D{¿-n activo?}
D -- No --> E[Imprime el espacio<br/>de patrones]
D -- Si --> F[No imprime nada]
E --> G[Borra el espacio<br/>y siguiente linea]
F --> G
G --> A
De este esquema salen tres consecuencias que explican casi todo el comportamiento de sed:
- Los comandos se aplican en orden sobre el mismo búfer. Si el primero sustituye
AporB, el segundo ya veB. Encadenar sustituciones no es lo mismo que aplicarlas por separado. - La impresión es automática al final del ciclo, no la hace el comando. Por eso
sed 's/a/b/'imprime todas las líneas, no solo las que cambió. -napaga esa impresión automática, y a partir de ahí solo sale lo que pidas explícitamente conp. De ahí el idiomased -n '...p'.
sed, awk o ${var//a/b}: cuál usar
sed, awk o ${var//a/b}: cuál usarLas tres herramientas sustituyen texto, y elegir mal es la causa más común de código enrevesado:
${var//a/b} (04-04) |
sed |
awk (06-01) |
|
|---|---|---|---|
| Opera sobre | Una variable en memoria | Un flujo o fichero | Un flujo o fichero |
| Coste | Cero: es Bash puro | Un proceso externo | Un proceso externo |
| Emparejamiento | Globs, no regex | Regex (BRE o ERE) | Regex (ERE) |
| Entiende de campos | No | No (solo líneas) | Sí: $1, $NF… |
| Sabe hacer cuentas | Solo enteros | No | Sí, en coma flotante |
| Recuerda entre líneas | No aplica | Solo con el espacio de retención | Sí: variables y arrays |
| Fuerte en | Un valor suelto | Reescribir texto | Agregar y calcular |
La regla práctica: si el texto ya está en una variable, ${var//a/b} y no lances un proceso; si hay que reescribir un fichero o un flujo, sed; si hay que sumar, contar o mirar columnas, awk. Y si te encuentras contando campos con sed, o sustituyendo con awk en varias pasadas, casi seguro que has elegido la herramienta equivocada.
- Sintaxis y opciones
Los comandos van entre comillas simples por el mismo motivo que en awk: contienen $, & y barras invertidas que Bash expandiría. Las opciones que se usan de verdad:
| Opción | Qué hace |
|---|---|
-n |
Suprime la impresión automática (ver sección 1) |
-e 'cmd' |
Añade un guion de comandos; se puede repetir para acumular varios |
-f fichero.sed |
Lee los comandos de un fichero, como el -f de awk |
-i[SUF] |
Edita el fichero en el sitio en lugar de escribir en la salida estándar |
-E |
Usa ERE en vez de BRE (-r es el sinónimo antiguo de GNU) |
-z |
Separa registros por byte nulo, para casarlo con find -print0 (05-01) |
-E merece una recomendación explícita. Por defecto sed habla BRE, el dialecto de 05-04 en el que +, ?, (, ) y | son literales y hay que escaparlos: s/[0-9]\+/N/. Con -E escribes s/[0-9]+/N/, que es lo que tienes en la cabeza. Usa -E siempre salvo que copies una receta ajena escrita en BRE.
-i: editar ficheros en el sitio, con red de seguridad
-i: editar ficheros en el sitio, con red de seguridad-i es la opción más útil y la más peligrosa de sed: reescribe el fichero original sin preguntar y sin posibilidad de deshacer. Un patrón mal escrito puede vaciar un fichero de configuración en producción.
Dos hábitos que evitan el desastre:
- Prueba siempre sin
-iprimero. Ejecuta el comando tal cual, mira la salida, y solo entonces añade-i. Es gratis y resuelve el 95 % de los sustos. - Usa un sufijo de copia,
-i.bak, no-ia secas. Cuesta cinco pulsaciones y deja el original recuperable.
Aquí hay además una incompatibilidad clásica: en GNU sed (Linux) el sufijo va pegado, -i.bak, y -i solo es válido; en BSD/macOS -i exige argumento, así que sin copia se escribe -i ''. Consecuencia: sed -i.bak funciona en ambos, sed -i solo en Linux, y un script que use sed -i '' fallará en Linux interpretando '' como nombre de fichero. Si el script debe correr en los dos sitios, la salida limpia es escribir a un temporal (mktemp, 05-01) y mover encima con mv.
Y una advertencia seria: nunca lances sed -i sobre ficheros de /etc sin copia previa. sed -i 's/PermitRootLogin no/PermitRootLogin yes/' /etc/ssh/sshd_config es un cambio de seguridad hecho a ciegas, sin validación y sin vuelta atrás. Las implicaciones se ven en 08-03.
- Direcciones: a qué líneas se aplica un comando
Un comando sin dirección se aplica a todas las líneas. Anteponiéndole una dirección se restringe:
| Dirección | Se aplica a |
|---|---|
5 |
Solo la línea 5 |
$ |
La última línea |
/ERROR/ |
Las líneas que emparejan la regex |
2,5 |
De la línea 2 a la 5 |
2,$ |
De la 2 hasta el final |
/inicio/,/fin/ |
Del primer emparejamiento de una al siguiente de la otra |
/ERROR/,+3 |
La línea que empareja y las 3 siguientes (GNU) |
0~3 |
Una de cada 3 líneas (GNU: primera~paso) |
/ERROR/! |
Negación: todas las que NO emparejan |
Así, sed -n '/2026-08-03 10:/,/2026-08-03 11:/p' /var/log/veloz/app.log extrae una hora del log, y sed '1!d' envios.csv deja solo la cabecera. El ! se lee "excepto": 1!d es "borra las líneas que no son la 1". Es la forma idiomática de conservar solo algo, y aparece constantemente en recetas reales.
- El comando
s/// a fondo
s/// a fondoEs el comando estrella y el que justifica el 90 % de los usos de sed. Su forma es s/regex/reemplazo/banderas:
El delimitador no tiene que ser /. Cualquier carácter vale, y elegir bien evita el "síndrome de las barras inclinadas" al trabajar con rutas:
sed 's|/srv/veloz/datos|/mnt/backup/datos|' rutas.txt # legible
sed 's/\/srv\/veloz\/datos/\/mnt\/backup\/datos/' rutas.txt # el mismo cambio, ilegibleLas banderas que van tras el último delimitador:
| Bandera | Efecto |
|---|---|
g |
Sustituye todas las ocurrencias de la línea, no solo la primera |
i |
Ignora mayúsculas y minúsculas |
p |
Imprime la línea si hubo sustitución (se usa con -n) |
w f |
Escribe en el fichero f las líneas sustituidas |
2 |
Sustituye solo la 2.ª ocurrencia; 2g desde la 2.ª en adelante |
En el reemplazo hay dos metacaracteres que hay que conocer. & significa "todo lo que emparejó", y \1, \2… son los grupos de captura de 05-04, con los grupos escritos \\(...\\) en BRE o (...) con -E:
# Poner entre corchetes el nivel de app.log: [ERROR] -> [[ERROR]]
sed -E 's/\[(INFO|WARN|ERROR)\]/[&]/' /var/log/veloz/app.log
# Reordenar "fecha nivel" a "nivel fecha" usando dos grupos
sed -E 's/^([0-9-]+ [0-9:]+) \[([A-Z]+)\]/[\2] \1/' /var/log/veloz/app.logCorolario práctico: si quieres un & literal en el reemplazo, escápalo (\&), igual que hay que escapar el delimitador si aparece dentro del patrón. Y recuerda que s/// sin g cambia solo la primera ocurrencia de cada línea: es el olvido número uno.
- Otros comandos:
p, d, q, a, i, c, y
p, d, q, a, i, c, y| Comando | Qué hace | Ejemplo |
|---|---|---|
p |
Imprime el espacio de patrones (con -n, es el único que imprime) |
sed -n '5p' |
d |
Borra la línea y empieza el ciclo siguiente | sed '/^#/d' |
q |
Sale de sed inmediatamente | sed '100q' |
a texto |
Añade una línea después | sed '$a fin del informe' |
i texto |
Inserta una línea antes | sed '1i id,fecha,ciudad' |
c texto |
Cambia la línea entera | sed '/obsoleto/c # linea retirada' |
y/abc/xyz/ |
Traduce carácter a carácter, como tr (02-02) |
y/aeiou/AEIOU/ |
d tiene una peculiaridad importante: aborta el ciclo, así que los comandos posteriores no se ejecutan sobre esa línea. Y q es un optimizador silencioso: sed '100q' fichero_gigante deja de leer en la línea 100, mientras que head -100 con una tubería seguiría leyendo. La sintaxis de a/i/c con texto en la misma línea es una extensión de GNU cómoda pero no portable; la forma POSIX usa una barra invertida y salto de línea.
- Filtrar y extraer tramos
Con -n y p se filtra igual que con grep: sed -n '/ERROR/p' app.log hace exactamente lo mismo que grep ERROR app.log. Si solo vas a filtrar, usa grep. Es más legible, suele ser más rápido, y tiene -c, -v, -i, -l y -o para los casos habituales. sed gana en cuanto la operación deja de ser "sí o no" y pasa a ser "transformar", o cuando lo que quieres es un tramo por número de línea, que grep no sabe hacer:
sed -n '10,20p' /var/log/veloz/app.log # lineas 10 a 20
sed -n '10,20p;20q' /var/log/veloz/app.log # igual, pero deja de leer en la 20Ese ;20q es el detalle que marca la diferencia en un log de dos gigas: sin él, sed sigue leyendo hasta el final aunque ya no imprima nada.
- Encadenar comandos y bloques
Varios comandos se separan con ; o con varios -e. Y si quieres aplicar un grupo de comandos a la misma dirección, se agrupan con llaves:
sed -E '/^#/d; /^[[:space:]]*$/d' ~/veloz-ops/etc/veloz-ops.conf # quita comentarios y vacias
sed -n '/inicio informe/,/fin informe/{ s/^/ /; p }' informe.txt # sangra solo ese bloqueEn el segundo ejemplo, { s/^/ /; p } se aplica únicamente a las líneas del rango: primero sangra, después imprime. El orden importa —recuerda la sección 1: los comandos ven el búfer tal como lo dejó el anterior—. Si invirtieras p y s, imprimirías la línea sin sangrar.
- Espacio de retención y
N, en pinceladas
N, en pinceladasAdemás del espacio de patrones, sed tiene un segundo búfer, el espacio de retención, que sobrevive entre líneas. Se maneja con h (copiar patrón→retención), H (añadir), x (intercambiar) y G (añadir retención→patrón). Es lo que da a sed una memoria mínima: sed G informe.txt añade una línea en blanco tras cada línea, y el críptico sed '1!G;h;$!d' fichero invierte el orden de las líneas —el tac de sed—.
El comando N añade la línea siguiente al espacio de patrones, permitiendo que una regex cruce dos líneas —útil para juntar una línea de app.log con su traza—. Dicho esto, aquí está el límite honesto de la herramienta: en cuanto necesites este nivel de acrobacia, awk o un lenguaje de guion serán más legibles y más fáciles de mantener. Conoce que existe, no lo conviertas en tu estilo.
- Recetas reales del toolkit
Anonimizar las IP de acceso.log conservando el primer octeto para saber la red de origen, y normalizar los separadores de un CSV exportado con punto y coma y 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/acceso.log
sed -E 's/;/,/g; s/([0-9]),([0-9]{2})$/\1.\2/' envios-crudo.csv > envios.csvCambiar una clave de configuración de forma idempotente, es decir, que se pueda ejecutar mil veces con el mismo resultado y que funcione tanto si la clave existía como si no:
if grep -q '^UMBRAL_DISCO=' "$CONF"; then
sed -i.bak -E 's/^UMBRAL_DISCO=.*/UMBRAL_DISCO=85/' "$CONF"
else
printf 'UMBRAL_DISCO=85\n' >> "$CONF"
fiEl if no es un adorno: sed no puede añadir una línea que no existe, solo transformar las que hay. Sin la comprobación previa, el script parecería funcionar y no cambiaría nada. Nótese el ancla ^, que evita tocar una clave que solo contenga ese texto, y el .* que se come el valor antiguo sea cual sea.
Limpiar un fichero de configuración para verlo sin ruido, y insertar una cabecera a un CSV que la perdió:
sed -E '/^[[:space:]]*#/d; /^[[:space:]]*$/d' /etc/veloz/api.conf
sed '1i id_envio,fecha,ciudad,repartidor,estado,importe' sin-cabecera.csv > envios.csv
- Aplicación:
veloz_anonimiza_log en lib/comun.sh
veloz_anonimiza_log en lib/comun.shLa receta de la IP se convierte en función reutilizable, siguiendo las reglas de librería de 05-06: solo definición, prefijo de espacio de nombres, sin código de nivel superior:
# veloz_anonimiza_log — Anonimiza IP y correos de un log. Uso: veloz_anonimiza_log <fichero>
# Escribe el resultado por la salida estandar; no toca el original.
veloz_anonimiza_log() {
local origen="${1:?falta el fichero de log}"
[[ -r "$origen" ]] || { veloz_log_error "no puedo leer $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,}/<correo>/g' \
-- "$origen"
}Tres decisiones merecen comentario. La función no usa -i: escribe a la salida estándar y deja que el llamante decida (veloz_anonimiza_log "$LOG" > /tmp/compartir.log), que es lo correcto en una librería —una función que modifica ficheros ajenos por sorpresa es una trampa—. Los dos -e separados son más legibles que un ; largo y permiten comentar uno al depurar. Y el -- marca el fin de opciones, así que un fichero llamado -raro.log se trata como fichero y no como opción; es el mismo hábito defensivo de 03-05. El código 66 es EX_NOINPUT, de la tabla de 05-03.
Verificar antes de confiar es obligatorio con sed: una comprobación rápida como head -3 /var/log/veloz/acceso.log | veloz_anonimiza_log /dev/stdin basta para ver que las IP salen enmascaradas y el resto de la línea intacto.
Errores Comunes y Consejos
- Olvidar la bandera
g.s/,/;/cambia solo la primera coma de cada línea. Para todas,s/,/;/g. - Usar
-isin probar antes. Ejecuta siempre el comando sin-i, mira la salida y solo entonces edita. Y usa-i.bak, que funciona en GNU y en BSD. - Esperar ERE por defecto.
sed 's/[0-9]+/N/'no hace nada, porque en BRE+es un+literal. Añade-E. - Escapar barras a mano. Si el patrón lleva rutas, cambia de delimitador:
s|/a/b|/c/d|. - Confundir el
.dentro de una IP.[0-9]{1,3}.[0-9]empareja también192x168; el punto literal se escribe\.. - Creer que
sedpuede añadir claves que faltan. Solo transforma líneas existentes; para añadir, comprueba congrep -qy usa>>. - Usar
sedpara filtrar. Si es un "sí o no",grep; si hay que cambiar el texto,sed; si hay columnas o cuentas,awk. - Consejo: con ficheros grandes,
sed -n '10,20p;20q'ysed '100q'evitan leer de más.LC_ALL=C sed ...también acelera. - Consejo: un guion de sed largo se saca a un fichero
.sedy se invoca con-f, igual que en awk: se comenta con#y se versiona.
Ejercicios
Ejercicio 1. Escribe un comando que extraiga de /var/log/veloz/app.log únicamente las líneas del intervalo entre 10:00:00 y 11:00:00 y les quite la fecha (dejando solo hora, nivel y mensaje), sin modificar el fichero original.
Ejercicio 2. Escribe una función veloz_set_conf para lib/comun.sh que fije una clave a un valor en ~/veloz-ops/etc/veloz-ops.conf de forma idempotente, con copia de seguridad, y que conserve los permisos 600 del fichero.
Ejercicio 3. El proveedor manda envios-crudo.csv con separador ;, importes con coma decimal, espacios sobrantes alrededor de los campos y una línea de comentario inicial que empieza por #. Escribe un solo sed que lo deje en el formato de envios.csv.
Soluciones
Solución 1.
El rango /inicio/,/fin/ selecciona el tramo, y las llaves aplican los dos comandos solo a él: primero se quita la fecha con s/// anclado en ^, después se imprime con p. -n es imprescindible —sin él saldría el fichero entero, con el tramo además duplicado por el p—. Y el orden dentro de las llaves importa: si p fuera primero, imprimiría la línea con la fecha todavía puesta.
Solución 2.
# veloz_set_conf — Fija clave=valor de forma idempotente. Uso: veloz_set_conf CLAVE valor
veloz_set_conf() {
local clave="${1:?falta la clave}" valor="${2-}" conf="${VELOZ_CONF:?}"
[[ "$clave" =~ ^[A-Z_][A-Z0-9_]*$ ]] || { veloz_log_error "clave invalida: $clave"; return 64; }
if grep -q "^${clave}=" "$conf"; then
sed -i.bak -E "s|^${clave}=.*|${clave}=${valor}|" "$conf"
else
printf '%s=%s\n' "$clave" "$valor" >> "$conf"
fi
chmod 600 "$conf"
}Aquí sí se usan comillas dobles, porque hay que interpolar $clave y $valor, y por eso la validación previa con =~ (05-04) no es opcional: sin ella, una clave con | o & rompería la sustitución o inyectaría comportamiento. El delimitador | evita problemas si el valor es una ruta, y chmod 600 al final restaura los permisos, porque sed -i crea un fichero nuevo y lo renombra, de modo que los permisos pueden cambiar. El código 64 es EX_USAGE.
Solución 3.
sed -E '/^#/d; s/[[:space:]]*;[[:space:]]*/,/g; s/^[[:space:]]+//; s/[[:space:]]+$//; s/([0-9]),([0-9]{2})$/\1.\2/' \
envios-crudo.csv > /srv/veloz/datos/envios.csvEl orden es deliberado y es todo el ejercicio. Primero se borra el comentario (d aborta el ciclo, así que esa línea ni se procesa). Después se normalizan los ; absorbiendo los espacios que los rodean, lo que resuelve de golpe el sobrante interior. Luego se recortan los extremos de la línea. Y solo al final se arregla el decimal: si esa sustitución fuera antes, el , recién creado por el paso del ; podría confundirse con el decimal. Conviene verificarlo con head -3 envios-crudo.csv | sed -E '...' antes de sobrescribir nada.
Conclusión
sed es un editor de flujo: por cada línea la copia al espacio de patrones, le aplica los comandos en orden y la imprime al terminar el ciclo salvo que -n lo impida —de ahí el idioma sed -n '...p'—. Sus comandos aceptan direcciones (número de línea, $, regex, rangos 2,5 y /inicio/,/fin/, y la negación !) que deciden a qué líneas se aplican, y el rey de todos es s/regex/reemplazo/banderas, con delimitador libre para no pelearse con las rutas, banderas g, i, p y w, y un reemplazo donde & es lo emparejado y \1 los grupos de 05-04. Alrededor están d, p, q, a, i, c y y, los bloques { } para agrupar por dirección, y el espacio de retención (h, x, G, N) como memoria mínima que conviene conocer y no abusar. -E para escribir ERE cómodo, -i.bak siempre con copia y probando antes sin -i, y la frontera clara con sus vecinos: ${var//a/b} para una variable, grep para filtrar, awk para columnas y cuentas, sed para reescribir.
Con awk y sed, el toolkit ya sabe leer, resumir y transformar cualquier texto que le eches. Pero todo ese texto sigue viniendo de ficheros que alguien dejó en el disco. Un script de operaciones necesita algo más: preguntarle al propio sistema en qué estado está —cuánto disco queda, qué carga soporta, quién está conectado, qué versión de sistema operativo corre, si el servicio veloz-api está vivo— para poder decidir en función de la respuesta. Eso es la próxima lección (06-03): las órdenes que interrogan al sistema y cómo convertir sus respuestas en umbrales y decisiones dentro de un script.
Curso de Programación en Bash
Módulo 1: Introducción a Bash
- ¿Qué es Bash?
- Configurando tu Entorno
- Navegación Básica en la Línea de Comandos
- Entendiendo el Shell
- Encontrar Ayuda: man, help y --help
Módulo 2: Comandos Básicos de Bash
- Operaciones con Archivos y Directorios
- Comandos de Procesamiento de Texto
- Permisos y Propiedad de Archivos
- Redirección y Tuberías
- Comodines y Expansión de Rutas
- Historial y Atajos de Teclado
Módulo 3: Fundamentos de Scripting
- Creando y Ejecutando un Script
- Variables y Constantes
- Operadores Básicos
- Sentencias Condicionales
- Argumentos y Entrada del Usuario
- Comillas, Expansión y Sustitución
Módulo 4: Scripting Intermedio
- Bucles en Bash
- Funciones en Bash
- Arrays y Arrays Asociativos
- Manipulación de Cadenas
- La Sentencia case y los Menús Interactivos
- Aritmética y Cálculos Numéricos
Módulo 5: Técnicas Avanzadas de Scripting
- Operaciones Avanzadas con Archivos
- Gestión de Procesos
- Manejo de Errores y Depuración
- Expresiones Regulares
- Entrada/Salida Avanzada: Descriptores y Here-Documents
- Scripts Modulares y Librerías Reutilizables
Módulo 6: Trabajando con Herramientas Externas
Módulo 7: Automatización y Programación
- Trabajos Cron
- Automatizando Tareas
- Scripts de Respaldo y Restauración
- Monitoreo y Registro
- Servicios y Temporizadores con systemd
- Automatización Remota con SSH
Módulo 8: Mejores Prácticas y Optimización
- Escribiendo Código Legible
- Optimizando Scripts en Bash
- Consideraciones de Seguridad
- Control de Versiones con Git
- Análisis Estático con ShellCheck y shfmt
- Pruebas Automatizadas con Bats
- Portabilidad: POSIX sh frente a Bashismos
