Logotipo de Bitdoze

Sed Insertar y Añadir Texto: Ejemplos y Gotchas de macOS

Domina los comandos sed de insertar y añadir texto: números de línea, patrones, bloques multilínea, edición in-place en GNU y BSD sed, y los gotchas de macOS a evitar.

Dragos

43 min de lectura

Sed Insertar y Añadir Texto: Ejemplos y Gotchas de macOS

Los comandos sed insert (i) y sed append (a) añaden texto antes o después de cualquier línea de un archivo, apuntados por número de línea o coincidencia de patrón. Una trampa: la sintaxis de una línea sed '4i text' que la mayoría de tutoriales enseñan falla en el sed de macOS/BSD. Esta guía cubre primero la forma portable que funciona en todas partes, luego números de línea, patrones, bloques multilínea, edición in-place segura, y los modos de fallo silenciosos (archivos vacíos, streams multi-archivo, hard links) que aparecen en scripts.

GNU sed vs BSD sed (macOS)

La mayoría de distribuciones Linux usan GNU sed, macOS trae BSD sed, y las imágenes Alpine/Docker traen BusyBox sed. Dos diferencias importan para este artículo:

  1. Edición in-place: GNU sed usa sed -i '...' file. BSD sed (macOS) necesita un argumento de sufijo de backup: sed -i '' '...' file.
  2. Insert/append de una línea: sed '4i text' funciona en GNU sed y BusyBox sed, pero BSD/macOS sed requiere la forma i\ + newline.

Si quieres un enfoque que funcione en todas partes, usa la sintaxis i\ + newline y escribe a un archivo temporal en vez de -i (ejemplos abajo).

Probado con GNU sed 4.9 y BusyBox sed 1.36.1 en Linux. El GNU sed actual es 4.10 (publicado 2026-04-21); Ubuntu 24.04 y distros era-Debian-12 siguen trayendo 4.9. Nada en 4.10 cambió el manejo de i, a o c, así que cada ejemplo de abajo aplica a ambas.

Salta a la Referencia Rápida

¿Qué es sed?

sed (Stream Editor) es una utilidad de línea de comandos para filtrar y transformar texto en sistemas tipo Unix. Procesa archivos línea a línea sin interacción del usuario, lo que lo hace una buena pieza para scripts y operaciones por lotes. Junto a herramientas como grep, awk y find, pertenece a tu conjunto de comandos esenciales de Linux.

Ventajas para insertar y añadir texto:

  • No interactivo: corre hasta el final una vez invocado, sin prompts
  • Posicionamiento preciso: inserta o añade en números de línea específicos o coincidencias de patrón
  • Ligero en memoria: procesa una línea cada vez (una salvedad: el arreglo estrella de sed 4.10 fue s///g haciendo pánico en líneas de más de 2GB, así que las líneas sueltas gigantes eran un modo de fallo real hasta hace poco)
  • Scripteable: funciona en pipes, bucles y trabajos de CI junto al resto del toolkit Unix

Otras guías de sed para manipulación de texto:

Si quieres un GNU sed de verdad para practicar en vez de lo que tenga tu portátil, un Hetzner Cloud VPS empieza alrededor de 4-5 EUR/mes y te da el entorno Linux exacto que estos ejemplos asumen. Un Hostinger VPS económico también sirve.

¿Qué sed estás corriendo?

Antes de copiar cualquier ejemplo, comprueba en qué implementación estás. La sintaxis de una línea se comporta distinto entre ellas.

GNU sed (Linux)

bash
sed --version
# sed (GNU sed) 4.9
# or: sed (GNU sed) 4.10
  • i text / a text de una línea: funciona (extensión GNU)
  • sed -i '...' file: funciona, sin sufijo de backup necesario
  • sed -s: soportado (modo por-archivo para múltiples entradas)
  • Esto es lo que asumen los ejemplos de distro de esta guía

BSD sed (macOS)

bash
which sed
# /usr/bin/sed
man sed
# no --version flag on BSD sed
  • i text / a text de una línea: falla. Usa la forma i\ + newline
  • In-place necesita un sufijo explícito: sed -i '' '...' file
  • Sin flag -s
  • Esta es la implementación sobre la que van los “gotchas de macOS” de esta guía

BusyBox sed (Alpine/Docker)

bash
sed --help 2>&1 | head -1
# BusyBox v1.36.1 sed
  • i text / a text de una línea: funciona, como GNU
  • sed -i '...' file: funciona
  • Archivo vacío + $a / 1i: no-op silencioso, igual que GNU
  • Las imágenes basadas en Alpine traen BusyBox sed, lo que vale saber cuando escribes sed en un Dockerfile y lo pruebas en tu Mac primero. Para el resto del toolkit de contenedores, mira los comandos Docker imprescindibles.

Resumen rápido de capacidades:

Sintaxis / flag GNU sed BusyBox sed BSD sed (macOS)
i text de una línea funciona funciona falla
i\ + newline (forma POSIX) funciona funciona funciona
-i sin sufijo de backup funciona funciona falla, usa -i ''
$ = última línea del último archivo sí (a menos que -s) sí sí

-s (trata cada archivo como un stream separado) es una extensión GNU y no existe en BSD sed. En BusyBox, comprueba sed --help antes de confiar en él.

Una nota de portabilidad para scripts nuevos: -E para regex extendida está estandarizado desde POSIX.1-2024 (Issue 8). Prefiere -E sobre el viejo flag GNU -r.

Insertando texto con sed

Solo imprimir vs edición in-place

Por defecto sed imprime la salida modificada a stdout y deja el archivo intacto. Empieza siempre aquí:

bash
sed '4i Inserted line' example.txt

Si la salida se ve bien, hazla permanente usando la sección “Edición in-place segura” más abajo. Las diferencias GNU vs BSD importan una vez que añades -i, así que no te saltes esa parte.

El comando i inserta texto antes de una línea especificada. Es la herramienta correcta para añadir headers, comentarios o entradas de configuración en posiciones concretas.

Sintaxis básica de insert

Hay dos formas. La portable viene de POSIX y funciona en sed de GNU, BusyBox y BSD:

bash
sed 'LINE_NUMBER i\
TEXT' filename

La de una línea es más corta y es lo que la mayoría de blogs enseñan:

bash
sed 'LINE_NUMBER i TEXT' filename

La sintaxis i/a de una línea es una extensión GNU

La forma de una línea i text es aceptada por GNU sed y BusyBox sed, pero no está en el estándar POSIX. POSIX.1-2024 sigue requiriendo la forma con backslash a\ / i\. BSD/macOS sed rechaza la de una línea. Historia completa en la siguiente sección.

Por qué tu comando sed ‘1i text’ falla en macOS

Esta es la queja de sed más común de los usuarios de Mac. Copias un comando de tutorial como este:

Falla en macOS

bash
sed '4i Inserted line' example.txt

En BSD sed esto da error. La redacción esperada viene del código de sed de FreeBSD, del que deriva el sed de macOS (confirma el texto exacto en un Mac real):

text
sed: command i expects \ followed by text

Nada se inserta. Lo mismo aplica a a y c.

Funciona en todas partes

bash
sed '4i\
Inserted line' example.txt

El newline inmediatamente tras i\ es la clave. GNU sed, BusyBox sed y BSD sed aceptan todos esta forma, así que un script escrito así corre en un Mac, en una máquina Linux y dentro de un contenedor Alpine.

Regla de dedo

Si el script debe correr en macOS, usa siempre i\ + newline. Si solo corres en GNU/Linux (incluyendo Alpine/BusyBox), la de una línea vale y es menos ruidosa.

La misma regla cubre a (append) y c (change/replace line). Los tres comandos viven en el mismo bloque POSIX y comparten la rareza de sintaxis.

Insertar por número de línea

Insertar antes de una línea específica (primero la de una línea GNU/BusyBox, la forma portable debajo):

bash
sed '4i This is the inserted line.' example.txt

# portable form: works on macOS too
sed '4i\
This is the inserted line.' example.txt

Insertar al principio de un archivo:

bash
sed '1i\
Header line goes here' filename

Insertar múltiples líneas (estilo portable):

bash
sed '4i\
First inserted line\
Second inserted line\
Third inserted line' filename

Notas:

  • El newline justo tras i\ es requerido para portabilidad entre GNU sed y BSD sed
  • Mantén la comilla de cierre al final de la última línea insertada
  • Los números de línea originales bajan tras la inserción. Si insertas antes de la línea 4, la vieja línea 4 se vuelve la línea 5

Insertar por coincidencia de patrón

Insertar antes de líneas que coinciden con un patrón:

bash
sed '/PATTERN/i\
TEXT' filename

Ejemplos prácticos:

bash
sed '/function main/i\
# Main function starts here' script.py
sed '/^server {/i\
# Nginx server configuration' nginx.conf
sed '/export PATH/i\
# Adding to PATH variable' .bashrc

Si estás editando nginx.conf como parte de un setup de servidor real, mira cómo configurar Nginx en Linux para la configuración alrededor.

Ejemplos avanzados de insert

Insertar con variables (comilla con seguridad):

bash
header="Generated on $(date)"
sed "1i\\
$header" datafile.txt

Por qué este estilo: si tu variable contiene caracteres que sed trata de forma especial (guiones iniciales, backslashes, etc.), la forma i\ + newline se comporta más consistentemente entre plataformas que la de una línea.

Insertar bloques de configuración:

bash
sed '/^# Database settings/i \
# Redis configuration\
redis.host=localhost\
redis.port=6379' config.ini

Insertar tras encontrar contenido específico:

bash
sed '/TODO:/i\
# FIXME: Review this section' code.py

Puntos a recordar

  • El texto se inserta antes de la línea especificada o antes de cada línea que coincida con un patrón
  • Los números de línea originales bajan tras una inserción
  • Usa \ al final de cada línea para inserciones multilínea
  • sed escribe a stdout por defecto. Añade -i solo después de previsualizar la salida

Añadiendo texto con sed

El comando a añade texto después de una línea especificada. Úsalo para footers, tags de cierre, o datos que deban seguir a contenido específico.

Sintaxis básica de append

Forma portable (POSIX, funciona en todas partes):

bash
sed 'LINE_NUMBER a\
TEXT' filename

De una línea (solo GNU + BusyBox):

bash
sed 'LINE_NUMBER a TEXT' filename

Añadir por número de línea

Añadir después de una línea específica:

bash
sed "2a\\
Don't forget to subscribe!" filename

Añadir al final del archivo. Este es el caso sed append to end of file que la gente busca, y $ es la dirección para ello:

bash
sed '$a\
Footer text goes here' filename

$ coincide con la última línea. Con múltiples archivos sin -i, “última línea” significa la última línea del último archivo. Mira la sección de gotchas.

Añadir múltiples líneas (estilo portable):

bash
sed '4a\
First appended line\
Second appended line\
Third appended line' filename

A diferencia de insert, las líneas añadidas no desplazan los números de línea del contenido existente.

Añadir por coincidencia de patrón

Añadir después de líneas que coinciden con un patrón:

bash
sed '/PATTERN/a\
TEXT' filename

Ejemplos prácticos:

bash
sed '/^}$/a\
# End of function block' script.js
sed '/^server {/a\
    # Server configuration continues' nginx.conf
sed '/export PATH/a\
# PATH modified above' .bashrc

Ejemplos avanzados de append

Añadir secciones de configuración:

bash
sed '/^# Database settings/a \
host=localhost\
port=5432\
database=myapp' config.ini

Añadir con variables:

bash
timestamp="Last modified: $(date)"
sed "\$a\
$timestamp" datafile.txt

Añadir tras marcadores específicos:

bash
sed '/<!-- INSERT HERE -->/a\
<div>New content</div>' template.html

Si te encuentras pegando blobs de JSON o YAML a través del quoting de sed, para y lee la sección “Cuándo NO usar sed” más abajo. Hay herramientas mejores para ese trabajo.

Insert vs append vs change

i y a tienen un tercer hermano: c (change) reemplaza la línea coincidente por completo. Los tres aparecen en el mismo bloque POSIX.

Diagrama mostrando que sed insert añade texto antes de una línea, append después, y change reemplaza la línea

Comando Posición Ejemplo (una línea GNU/BusyBox) Notas
i Antes de línea/patrón sed '5i text' Inserta arriba; los números de línea posteriores bajan
a Después de línea/patrón sed '5a text' Inserta abajo; los números de línea existentes se quedan
c Reemplaza la línea sed '5c text' La línea coincidente se descarta y se reemplaza

En macOS/BSD sed, escribe la forma portable en su lugar: i\, a\ o c\ seguido de un newline y luego el texto.

Puntos a recordar

  • El texto se añade después de la línea o patrón especificado
  • Usa $a para añadir al final de un archivo
  • Usa \ al final de línea para appends multilínea
  • Los números de línea existentes no cambian; las líneas nuevas aterrizan debajo

Apuntando posiciones específicas

La fuerza de sed es apuntar dónde debe aterrizar el texto, usando números de línea, rangos o patrones.

Apunte basado en posición

Números de línea:

bash
sed '1i\
Header text' filename        # Insert at beginning
sed '5a\
Middle text' filename        # Append after line 5
sed '$a\
Footer text' filename        # Append at end

Rangos de líneas. Ojo a la semántica: un comando con un rango corre una vez por línea en el rango, no una vez para el rango entero:

bash
sed '10,15i\
# Section start' filename    # Inserts before EACH line from 10 to 15
sed '20,$a\
# End section' filename      # Appends after every line from 20 to EOF

Si querías una inserción antes de la línea 10 solamente, direcciona la línea 10 (10i\), no el rango 10,15i. Esto tropieza a la gente regularmente.

Apunte basado en patrón

Patrones simples:

bash
sed '/TODO/i\
# FIXME: Address this' filename
sed '/^function/a\
# Function definition ends' filename
sed '/^#/a\
# Comment continues' filename

Patrones complejos con expresiones regulares:

bash
sed '/^[0-9]/i\
# Numbered item:' filename              # Before lines starting with digits
sed '/\.log$/a\
# Log entry processed' filename         # After lines ending with .log
sed '/^[[:space:]]*$/i\
# Empty line above' filename            # Before blank lines

Trabajo regex mixto (formas de identificadores, patrones de email):

bash
sed '/^[A-Z][a-z]*[0-9]\{2,4\}/i\
# Code identifier found' data.txt

sed '/[a-zA-Z0-9._%+-]\+@[a-zA-Z0-9.-]\+\.[a-zA-Z]\{2,\}/a\
# Email processed' contacts.txt

Para manipulación de texto que va más allá de insert/append, como reemplazar coincidencias o reestructurar líneas, la guía de sed buscar y reemplazar cubre el comando s en serio.

Casos de uso prácticos

Archivos de configuración:

bash
# Add database configuration
sed '/^# Database/a \
host=localhost\
port=5432\
user=admin' config.ini

# Insert security headers
sed '/^server {/a \
    add_header X-Frame-Options SAMEORIGIN;\
    add_header X-Content-Type-Options nosniff;' nginx.conf

Archivos de código:

bash
# Add function documentation
sed '/^def /i\
# Function: Performs calculation' script.py

# Insert debugging statements
sed '/^if /a\
print("Debug: Condition checked")' debug.py

Procesamiento de datos:

bash
# Add CSV headers
sed '1i\
Name,Age,Email' data.csv

# Insert separators
sed '/^---/i\
# Section divider' document.txt

Técnicas avanzadas de apunte

Inserción condicional (forma de bloque):

bash
sed '/config_section/{
i\
# Configuration starts here
}' filename

Dentro de bloques, quédate con la forma i\ + newline. Se comporta igual en GNU sed y BSD sed.

Operaciones múltiples (insert + append alrededor de la misma coincidencia):

bash
sed '/important_line/{
i\
# Important section begins
a\
# Important section ends
}' filename

Usando variables para contenido dinámico:

bash
section_name="Database Configuration"
sed "/^# $section_name/a\\
host=localhost" config.ini

Un detalle solo-GNU: como extensión GNU, i y a aceptan dos direcciones (un rango en un solo comando). El sed POSIX solo acepta una. Mantén los scripts a una dirección si BSD/macOS tiene que correrlos.

Seguridad y pruebas

Previsualiza los cambios primero (siempre):

bash
sed '/pattern/i\
NEW TEXT' filename
sed '/pattern/i\
NEW TEXT' filename | head

Muestra números de línea para contexto:

bash
nl -ba filename | sed '/pattern/i\
NEW TEXT'

La verificación post-edición (diff, grep, conteos de líneas) vive en la sección “Verifica tu edición”. Hazla tras cualquier corrida in-place.

Edición in-place segura (GNU vs BSD sed)

GNU sed (la mayoría de Linux):

bash
sed -i.bak '/pattern/i\
NEW TEXT' filename

BSD sed (macOS):

bash
sed -i '.bak' '/pattern/i\
NEW TEXT' filename

Ambos crean filename.bak para que puedas revertir. El sufijo es la única diferencia: GNU toma -i.bak como una palabra, BSD quiere -i '.bak' (y -i '' si de verdad no quieres backup, lo cual yo no haría).

Edición in-place portable que preserva inodo, modo, propiedad y hard links:

bash
tmp="$(mktemp)"
sed '/pattern/i\
NEW TEXT' filename > "$tmp" && cat "$tmp" > filename && rm -f "$tmp"

Esto escribe el resultado a un archivo temporal, luego copia los bytes sobre el original con cat >. El objeto de archivo original nunca se reemplaza, así que los hard links y permisos sobreviven. La versión más simple mv "$tmp" filename también funciona, pero el archivo renombrado hereda el modo 0600 de mktemp y tu propiedad. Bien para archivos desechables, mal para cualquier cosa compartida. La historia del inodo está en la sección de gotchas.

Rollback

Con -i.bak o un sufijo .bak, mv filename.bak filename revierte la edición por completo. Sin un archivo de backup, deshacer significa re-correr la edición inversa o restaurar desde tu propio backup.

Estos son los modos de fallo que no aparecen en la mayoría de tutoriales de sed. Cada uno produce una situación de “pero el comando corrió bien”, que es exactamente lo que los hace caros en scripts.

Archivos vacíos: 1i y $a son no-ops silenciosos

Sobre un archivo de entrada vacío, 1i y $a no hacen nada. sed nunca arranca un ciclo, así que el texto de insert/append encolado nunca se imprime. Código de salida 0. Sin aviso. Verificado tanto en GNU sed como en BusyBox sed.

El manual de GNU describe el texto de i/a como encolado “al final del ciclo actual, o cuando se lee la siguiente línea de entrada”. Archivo vacío significa ninguna línea, ningún ciclo, ninguna salida.

¿Archivo vacío? sed no hará nada

Si corres sed '1i Header' empty.txt y el archivo sigue vacío, sed no está roto. Es comportamiento esperado. Usa un enfoque de shell en su lugar.

Alternativas seguras-con-archivo-vacío:

bash
# Prepend a header (works on empty files too)
printf 'Header line\n' | cat - file.txt > out && cat out > file.txt && rm out

# Append a footer
echo 'Footer line' >> file.txt

Múltiples archivos: un stream vs -s vs -i

Sin -i, pasar varios archivos hace que sed los trate como un stream continuo. Así que esto:

bash
sed '$a END' f1 f2

añade END solo después de la última línea de f2, no después de cada archivo. Si esperabas un footer en cada archivo, obtuviste un footer al mismísimo final y un resultado confuso.

Tus opciones:

bash
sed -s '$a END' f1 f2       # GNU only: END after each file, on stdout
sed -i '$a END' f1 f2       # works per-file: -i implies -s in GNU sed

El manual de GNU lo dice claramente: la opción in-place implica -s. Por eso sed -i '1i Header' *.txt pone un header en cada archivo.

BSD sed no tiene -s, así que la respuesta portable por-archivo es un bucle:

bash
for f in *.txt; do
  sed '$a END' "$f" > "$f.tmp" && cat "$f.tmp" > "$f"
  rm -f "$f.tmp"
done

sed -i no edita tu archivo. Escribe un archivo temporal y lo renombra sobre el original. La ruta se queda igual; el inodo no.

Diagrama mostrando sed -i escribiendo un archivo temporal y renombrándolo sobre el original, lo que cambia el inodo y deja los hard links con el contenido viejo

Consecuencias:

  • Los hard links quedan rancios. Una ruta hermana hard-linkeada sigue apuntando al inodo viejo con el contenido viejo. Verificado: tras sed -i, el hard link aún mostraba el texto original.
  • Los symlinks se reemplazan. Sin --follow-symlinks, el propio symlink se sobrescribe con un archivo regular.
  • El modo se preserva. Un archivo 600 se quedó 600 tras -i en pruebas. La propiedad también se preserva generalmente, pero verifica en tu sistema si corres sed -i bajo sudo sobre archivos de otro usuario. Esa combinación es la que vale comprobar en vez de asumir.

Si necesitas que el inodo sobreviva (archivos compartidos, hard links, permisos raros), usa la receta cat > de la sección “Edición in-place segura” en vez de -i.

Para archivos de /etc symlinkeados, GNU sed tiene --follow-symlinks (solo válido con -i). sed 4.10 arregló una race de symlink TOCTOU en ese flag, buena razón para estar en una release actual.

Caso extremo del newline final

Si la última línea de un archivo no tiene newline final, sed '$a appended' sigue funcionando en GNU sed: añade el newline a la vieja última línea y añade normalmente. Se reporta que BSD sed se comporta distinto. Verifica antes de confiar en esto en macOS.

Dónde importa: archivos sensibles a bytes como assets generados o ciertas configs de /etc donde un newline final ausente es significativo. Comprueba con tail -c 1 file | xxd si no estás seguro.

Códigos de salida y ediciones de config idempotentes

sed sale con 0 coincida o no. Tu script no puede usar $? para preguntar “¿pasó el insert?”. Para aprovisionamiento de una vez está bien. Para cualquier cosa que corra dos veces (cron, gestión de config, scripts de setup re-ejecutables) necesitas un guard, o la línea aterriza dos veces:

bash
grep -qxF 'export EDITOR=vim' ~/.bashrc || echo 'export EDITOR=vim' >> ~/.bashrc

grep -q '^PermitRootLogin' /etc/ssh/sshd_config || \
  sed -i '/^#PermitRootLogin/a\
PermitRootLogin no' /etc/ssh/sshd_config

El segundo ejemplo es el patrón estándar de “descomenta un default de hardening exactamente una vez”. Si estás editando sshd_config en hosts remotos a través de un jumphost, el setup de SSH ProxyJump vale tenerlo primero para no bloquearte fuera probando.

Una nota de ops: a escala de flota, no hagas estos guards a mano. Los módulos lineinfile y blockinfile de Ansible existen exactamente para este trabajo: idempotencia, check mode y rollback ya están resueltos ahí. sed es la herramienta correcta para una máquina en la que estás ahora mismo; una herramienta de gestión de config es la correcta para mil máquinas.

Tabla rápida de modos de fallo

Síntoma Causa Arreglo
El comando corrió, exit 0, archivo sin cambios Archivo de entrada vacío, ningún ciclo de sed corrió printf 'Header\n' | cat - file o echo >> file
El footer solo apareció en el último archivo Múltiples archivos = un stream sin -s Usa sed -i (implica -s) o bucle por archivo
El hard link sigue mostrando contenido viejo tras -i Archivo temporal + rename, inodo nuevo Usa la receta in-place cat >
La línea aparece dos veces tras re-correr un script sed sale 0 coincida o no Guard grep -q antes de editar
sed: command i expects \ followed by text BSD/macOS sed rechazó la de una línea Usa la forma i\ + newline
El comando r no produjo nada Archivo ausente o ilegible, silenciosamente vacío Comprueba la ruta, luego grep -n después

Verifica tu edición

Editar archivos de config a ciegas es cómo se consigue un servicio que no arranca en el próximo reinicio. Tres one-liners cazan casi todo.

Comprobaciones post-edición

bash
sed -i.bak '/pattern/i\
NEW TEXT' file && diff file.bak file    # review exactly what changed
grep -n 'NEW TEXT' file                 # confirm it landed (and landed once)
wc -l file.bak file                     # line-count sanity

Si el diff está mal, mv file.bak file te devuelve atrás. Si necesitas comparar árboles enteros tras una edición masiva, mira comparar archivos y carpetas desde la terminal para la versión recursiva de esto.

  • Diff revisado: los únicos cambios son las líneas que pretendías
  • grep -n encuentra exactamente una ocurrencia por objetivo (ni cero, ni dos)
  • El conteo de líneas se movió la cantidad esperada
  • Servicio recargado o config validada si el archivo alimenta uno (nginx -t, sshd -t, systemctl reload ...)
  • Solo entonces borra el .bak

Traza la ejecución con sed –debug

GNU sed 4.6 y posteriores tiene --debug, que imprime el programa compilado y traza cada ciclo. Es la forma más rápida de ver por qué una dirección no coincidió:

bash
$ printf 'x\n' | sed --debug '1a hello'
SED PROGRAM:
  1 a\hello
INPUT:   'STDIN' line 1
PATTERN: x
COMMAND: 1 a\hello
END-OF-CYCLE:
x
hello

Obtienes el programa tal como sed lo parseó (útil cuando sospechas un error de quoting), la línea en la que está, y el pattern space en cada paso. sed 4.10 arregló un crash en el propio --debug, así que mantente al día si te apoyas en él.

--debug es solo-GNU. No existe en BSD/macOS sed ni en BusyBox sed.

Técnicas avanzadas de insert y append

Una vez sólidas las bases, estos patrones manejan los trabajos más liosos.

Adición condicional de texto

Insertar antes de líneas que coinciden con una condición:

bash
# Insert a warning before error lines
sed '/ERROR/i\
*** WARNING: Critical error detected ***' logfile.txt

# Append configuration after specific sections
sed '/^# Network settings/a \
interface=eth0\
dhcp=true' config.txt

Coincidencia de múltiples condiciones:

bash
# Insert before lines starting with date patterns
sed '/^[0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}/i\
Date entry:' dates.txt

# Append after lines containing both keywords
sed '/database.*config/a\
# Database configuration processed' app.conf

Bloques de texto multilínea

Insertar bloques de configuración complejos:

bash
sed '/^# SSL Configuration/i \
# SSL Certificate Setup\
ssl_certificate /path/to/cert.pem;\
ssl_certificate_key /path/to/key.pem;\
ssl_protocols TLSv1.2 TLSv1.3;' nginx.conf

Añadir datos estructurados:

bash
sed '/^{$/a \
    "name": "default",\
    "version": "1.0",\
    "active": true' config.json

El footer sed '$a END' de antes es un trabajo distinto: una línea al EOF, no un bloque estructurado. Para bloques JSON o YAML de cualquier tamaño real, prefiere jq o yq. Escapar datos estructurados multilínea a través del quoting de sed se vuelve frágil rápido.

Inserción de contenido dinámico

Usando variables y sustitución de comandos:

bash
# Insert timestamp
current_time=$(date)
sed "1i\
Generated on: $current_time" report.txt

# Append system information
sed "\$a\
System: $(uname -s), User: $(whoami)" logfile.txt

Insertar contenido de archivo con r

El comando r lee otro archivo a la salida después de la línea coincidente:

bash
# Insert entire file content
sed '/INCLUDE_POINT/r include.txt' main.txt

# Combine with text insertion
sed '/HEADER/{ 
i\
# Configuration file begins
r config_template.txt
a\
# Configuration file ends
}' main.conf

sed r falla en silencio

Si el nombre de archivo no puede leerse, GNU sed lo trata como un archivo vacío sin indicación de error. Una ruta mal tecleada produce salida sin cambios y código de salida 0, indistinguible del éxito. Haz siempre grep -n tras usar r para confirmar que el contenido aterrizó.

POSIX también especifica que el archivo se lee “en el momento en que se escribe la salida”, no cuando el comando r se ejecuta. Para la salida normal de una pasada de sed eso rara vez importa, pero no asumas que el contenido se captura al momento de la coincidencia.

Operaciones con rangos de patrón

bash
# Insert text before each line in a range
sed '10,20i\
# Line in middle section' filename

# Append after each line in a pattern-delimited range
sed '/START/,/END/a\
# Block processed' filename

Recuerda la semántica de rangos de antes: el comando se dispara para cada línea del rango, no una vez al inicio de él.

Técnicas avanzadas de scripting

Múltiples operaciones en secuencia:

bash
sed -e '/function/i\
# Function definition' \
    -e '/function/a\
# Function body starts' \
    -e '/return/i\
# Function ends' \
    -e '/return/a\
# Return statement processed' script.py

Usar scripts sed para operaciones complejas. Crea modify.sed:

text
/^# Database/i\
# Database Configuration Section
/^# Database/a\
host=localhost
/^# Database/a\
port=5432
/^# Security/i\
# Security Settings Section
/^# Security/a\
enable_ssl=true

El estilo i\/a\ + newline es lo que quieres en archivos de script. Evita los casos extremos que varían entre implementaciones de sed.

Córrelo con:

bash
sed -f modify.sed config.ini

-f es el movimiento correcto una vez que un programa sed crece más allá de lo que puedes leer en una línea. Checkea el script junto al código que modifica.

Ejemplos avanzados prácticos

Procesamiento de archivos de log:

bash
now="$(date)"
sed -e '/ERROR/i\
=== ERROR DETECTED ===' \
    -e "/ERROR/a\
Timestamp: $now" \
    -e '/WARN/i\
--- Warning ---' application.log

Inyección en el head HTML:

bash
sed -e '1i\
<!DOCTYPE html>' \
    -e '/<head>/a\
<meta charset="UTF-8">' \
    -e '/<head>/a\
<meta name="viewport" content="width=device-width, initial-scale=1.0">' index.html

Flags solo-GNU que vale conocer

Cuatro flags que se ganan su lugar en scripts de operador (todos GNU sed):

  • --sandbox: rechaza los comandos e, w y r. Corre scripts sed no confiables con él.
  • --follow-symlinks: solo con -i; edita a través de symlinks en vez de reemplazarlos. Necesario para archivos de /etc symlinkeados. sed 4.10 arregló una race TOCTOU en este flag.
  • -z / --null-data: modo delimitado por NUL, hace pareja con pipelines find -print0 en nombres de archivo con espacios.
  • -E: regex extendida, ahora estándar POSIX desde Issue 8 (2024). Prefiérelo sobre -r en cualquier cosa nueva.

Cuándo NO usar sed

Fronteras honestas, de alguien que ha limpiado scripts sed ingeniosos:

  1. Añadir una línea al EOF. echo 'line' >> file es más simple, atómico vía O_APPEND, y funciona en archivos vacíos. sed '$a' no añade nada ahí y es más difícil de leer.
  2. Ediciones de config idempotentes repetidas entre máquinas. Un one-liner sed con guard de grep funciona en una máquina. En una flota, lineinfile/blockinfile de Ansible te da idempotencia, dry-run y diffs gratis.
  3. Bloques grandes o estructurados (JSON, YAML, fragmentos HTML). jq, yq, o una herramienta de templating real ganan a escapar comillas y backslashes a través del quoting de sed. Si el insert tiene más de tres líneas y algo en él está comillado, reconsidera.

Conclusión

sed insert (i) y sed append (a) son comandos pequeños que hacen un trabajo bien: poner texto en una posición exacta. La forma portable i\ + newline corre en todas partes incluyendo macOS, 1i y $a son no-ops silenciosos en archivos vacíos, y sed -i reemplaza tu archivo entre bambalinas. Previsualiza en stdout, guarda el .bak, verifica con diff.

  • Usa i\ + newline para cualquier script que deba correr en macOS o BSD
  • 1i y $a no hacen nada en archivos vacíos. Usa cat - o >> ahí
  • sed -i reemplaza el archivo (el inodo cambia, los hard links quedan rancios)
  • sed sale con 0 coincida o no. Guarda con grep -q para scripts idempotentes
  • Verifica con diff file.bak file y grep -n antes de borrar el backup

Más comandos sed que vale conocer:

Guía de referencia rápida

Comandos comunes de insert

bash
# Insert at specific positions
sed '1i\
Header text' file.txt            # Before first line
sed '5i\
Middle text' file.txt            # Before line 5
sed '/pattern/i\
Before text' file.txt            # Before matching lines

# Multi-line insert (portable)
sed '1i\
Line 1\
Line 2\
Line 3' file.txt

# One-liner: GNU/BusyBox only, fails on macOS
sed '1i Header text' file.txt

Comandos comunes de append

bash
# Append at specific positions
sed '5a\
After text' file.txt             # After line 5
sed '$a\
Footer text' file.txt            # After last line
sed '/pattern/a\
After text' file.txt             # After matching lines

# Multi-line append (portable)
sed '$a\
Footer line 1\
Footer line 2\
Footer line 3' file.txt

# Change/replace a line
sed '5c\
Replacement text' file.txt

Preguntas frecuentes

¿Cuál es la diferencia entre insert y append (y change)?

Insert (i) añade texto antes de la línea o coincidencia objetivo. Append (a) añade texto después. Change (c) descarta la línea objetivo y la reemplaza con el texto nuevo. Tras un insert, los números de línea posteriores bajan; append y change los dejan igual.

¿Cómo añado texto multilínea?

Usa la forma portable con backslash y un newline tras la letra del comando:

bash
sed '1i\
Line 1\
Line 2' file.txt

Esto funciona en GNU sed, BusyBox sed y BSD/macOS sed.

¿Puedo insertar variables en el texto?

Sí. Usa comillas dobles para que el shell las expanda, y prefiere la forma i\ + newline por seguridad:

bash
sed "1i\\
Current date: $(date)" file.txt
¿Cómo hago los cambios permanentes?

Usa edición in-place con un sufijo de backup:

bash
# GNU sed (Linux)
sed -i.bak '1i\
Header' file.txt

# BSD sed (macOS)
sed -i '.bak' '1i\
Header' file.txt

Puede que veas '1i\'$'\n''Header' en respuestas antiguas. Eso es quoting ANSI-C de bash/zsh, y no funciona en dash ni en sh puro. Mira en qué difieren bash, Zsh y fish para el porqué. Si quieres evitar las diferencias del flag -i por completo, usa la receta portable de archivo temporal de “Edición in-place segura”.

¿Qué pasa con los números de línea tras una inserción?

Insert: los números de línea debajo del punto de inserción bajan en uno por línea insertada. Append: los números de línea existentes se quedan igual, las líneas nuevas aparecen debajo. Change: el conteo de líneas no cambia a menos que el reemplazo sea multilínea.

¿Puedo insertar/añadir a múltiples archivos a la vez?

Con -i, sí: sed -i '1i Header' *.txt (forma GNU/BusyBox) pone el header en cada archivo, porque sed -i de GNU implica -s (streams separados por archivo). Sin -i, sed '$a END' f1 f2 trata los archivos como un solo stream y añade solo tras el último archivo. -s en sí es solo-GNU. La respuesta portable es un bucle por archivo. Mira el gotcha de “Múltiples archivos” de arriba.

¿Por qué sed no hace nada en un archivo vacío?

1i y $a son no-ops silenciosos sobre entrada vacía: sed nunca corre un ciclo, así que el texto encolado nunca se escribe. Código de salida 0, sin error. Preppend con printf 'Header\n' | cat - file.txt y append con echo 'Footer' >> file.txt en su lugar.

¿Cómo sé que mi edición ocurrió? ¿Por qué mi script insertó la línea dos veces?

sed sale con 0 coincida o no la dirección, así que $? no te dice nada sobre si se insertó texto. Comprueba con grep -n 'NEW TEXT' file. Para scripts que corren más de una vez, guarda la edición:

bash
grep -q '^PermitRootLogin' /etc/ssh/sshd_config || \
  sed -i '/^#PermitRootLogin/a\
PermitRootLogin no' /etc/ssh/sshd_config
¿Por qué sed 'r file' no insertó nada?

Si el nombre de archivo falta o es ilegible, GNU sed lo trata como un archivo vacío sin indicación de error. El comando “tiene éxito” y no inserta nada. Verifica que la ruta existe primero, y haz grep -n a la salida después.

¿Por qué mi hard link sigue mostrando contenido viejo tras sed -i?

sed -i escribe un archivo temporal y lo renombra sobre tu ruta. El inodo cambia, así que las rutas hermanas hard-linkeadas siguen apuntando al inodo viejo sin editar. Los symlinks se reemplazan por archivos regulares a menos que pases --follow-symlinks. Para preservar inodos y hard links, copia el contenido nuevo sobre el original en vez de renombrar: sed ... file > tmp && cat tmp > file.

¿Todo esto funciona en macOS y en imágenes Docker Alpine?

La forma portable i\ + newline funciona en macOS (BSD sed), Linux (GNU sed) e imágenes Alpine (BusyBox sed). La forma de una línea i text funciona en GNU y BusyBox pero falla en macOS. La sintaxis de edición in-place difiere (-i vs -i ''). Mira “¿Qué sed estás corriendo?” arriba para la comparación completa.