---
title: "Sed Insertar y Añadir Texto: Ejemplos y Gotchas de macOS"
description: "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."
date: 2026-10-05
categories: ["linux"]
tags: ["sed","linux","command-line"]
---

import Button from "@components/widgets/Button.astro";
import Notice from "@components/widgets/Notice.astro";
import ListCheck from "@components/widgets/ListCheck.astro";
import Accordion from "@components/widgets/Accordion.astro";
import Tabs from "@components/widgets/Tabs.astro";
import Tab from "@components/widgets/Tab.astro";

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.

<Notice type="info" title="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).
</Notice>

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.

<Button text="Salta a la Referencia Rápida" link="#guía-de-referencia-rápida" variant="outline" color="blue" size="sm" />

## ¿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](https://www.bitdoze.com/linux-commands/).

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:

- [Borrar líneas con sed](https://www.bitdoze.com/sed-delete-lines/) usando el comando `d`
- Insertar o añadir texto con los comandos `i` y `a` (esta guía)
- [Cambiar mayúsculas con sed](/es/sed-cambiar-mayusculas-minusculas/) para estandarización
- [Sed buscar y reemplazar](https://www.bitdoze.com/sed-search-replace/) con coincidencia de patrones

Si quieres un GNU sed de verdad para practicar en vez de lo que tenga tu portátil, un [Hetzner Cloud VPS](https://go.bitdoze.com/hetzner) empieza alrededor de 4-5 EUR/mes y te da el entorno Linux exacto que estos ejemplos asumen. Un [Hostinger VPS](https://go.bitdoze.com/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.

<Tabs>
<Tab name="GNU sed (Linux)">

```sh
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

</Tab>
<Tab name="BSD sed (macOS)">

```sh
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

</Tab>
<Tab name="BusyBox sed (Alpine/Docker)">

```sh
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](https://www.bitdoze.com/docker-commands/).

</Tab>
</Tabs>

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í:

```sh
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:

```sh
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:

```sh
sed 'LINE_NUMBER i TEXT' filename
```

<Notice type="warning" title="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.
</Notice>

### 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:

<Tabs>
<Tab name="Falla en macOS">

```sh
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):

```
sed: command i expects \ followed by text
```

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

</Tab>
<Tab name="Funciona en todas partes">

```sh
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.

</Tab>
</Tabs>

<Notice type="info" title="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.
</Notice>

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):

```sh
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:**

```sh
sed '1i\
Header line goes here' filename
```

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

```sh
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:**

```sh
sed '/PATTERN/i\
TEXT' filename
```

**Ejemplos prácticos:**

```sh
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](https://www.bitdoze.com/setup-webdav-nginx/) para la configuración alrededor.

### Ejemplos avanzados de insert

**Insertar con variables (comilla con seguridad):**

```sh
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:**

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

**Insertar tras encontrar contenido específico:**

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

### Puntos a recordar

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

## 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):

```sh
sed 'LINE_NUMBER a\
TEXT' filename
```

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

```sh
sed 'LINE_NUMBER a TEXT' filename
```

### Añadir por número de línea

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

```sh
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:

```sh
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):**

```sh
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:**

```sh
sed '/PATTERN/a\
TEXT' filename
```

**Ejemplos prácticos:**

```sh
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:**

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

**Añadir con variables:**

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

**Añadir tras marcadores específicos:**

```sh
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](@assets/images/23/11/sed-where-text-lands.svg)

| 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

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

## 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:**

```sh
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:

```sh
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:**

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

**Patrones complejos con expresiones regulares:**

```sh
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):

```sh
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](https://www.bitdoze.com/sed-search-replace/) cubre el comando `s` en serio.

### Casos de uso prácticos

**Archivos de configuración:**

```sh
# 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:**

```sh
# 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:**

```sh
# 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):**

```sh
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):**

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

**Usando variables para contenido dinámico:**

```sh
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):**

```sh
sed '/pattern/i\
NEW TEXT' filename
sed '/pattern/i\
NEW TEXT' filename | head
```

**Muestra números de línea para contexto:**

```sh
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):**

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

**BSD sed (macOS):**

```sh
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:**

```sh
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.

<Notice type="info" title="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.
</Notice>

## Gotchas: archivos vacíos, múltiples archivos y hard links

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.

<Notice type="warning" title="¿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.
</Notice>

Alternativas seguras-con-archivo-vacío:

```sh
# 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:

```sh
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:

```sh
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:

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

### Inodos, hard links y permisos

`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](@assets/images/23/11/sed-inplace-inode.svg)

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:

```sh
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](https://www.bitdoze.com/ssh-proxyjump-jumphost/) 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

```sh
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](https://www.bitdoze.com/compare-folders-content-differences/) para la versión recursiva de esto.

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

### 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ó:

```sh
$ 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:**

```sh
# 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:**

```sh
# 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:**

```sh
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:**

```sh
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:**

```sh
# 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:

```sh
# 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
```

<Notice type="warning" title="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ó.
</Notice>

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

```sh
# 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:**

```sh
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`:

```
/^# 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:

```sh
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:**

```sh
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:**

```sh
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.

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

Más comandos sed que vale conocer:

- [Borrar líneas](https://www.bitdoze.com/sed-delete-lines/) con sed para quitar contenido no deseado
- [Cambiar mayúsculas con sed](/es/sed-cambiar-mayusculas-minusculas/) para estandarizar capitalización
- [Sed buscar y reemplazar](https://www.bitdoze.com/sed-search-replace/) para sustitución basada en patrones

## Guía de referencia rápida

### Comandos comunes de insert

```sh
# 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

```sh
# 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

<Accordion label="¿Cuál es la diferencia entre insert y append (y change)?" group="faq" expanded="true">
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.
</Accordion>

<Accordion label="¿Cómo añado texto multilínea?" group="faq">
Usa la forma portable con backslash y un newline tras la letra del comando:

```sh
sed '1i\
Line 1\
Line 2' file.txt
```

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

<Accordion label="¿Puedo insertar variables en el texto?" group="faq">
Sí. Usa comillas dobles para que el shell las expanda, y prefiere la forma `i\` + newline por seguridad:

```sh
sed "1i\\
Current date: $(date)" file.txt
```
</Accordion>

<Accordion label="¿Cómo hago los cambios permanentes?" group="faq">
Usa edición in-place con un sufijo de backup:

```sh
# 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](https://www.bitdoze.com/fish-shell-vs-bash-vs-zsh/) 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".
</Accordion>

<Accordion label="¿Qué pasa con los números de línea tras una inserción?" group="faq">
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.
</Accordion>

<Accordion label="¿Puedo insertar/añadir a múltiples archivos a la vez?" group="faq">
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.
</Accordion>

<Accordion label="¿Por qué sed no hace nada en un archivo vacío?" group="faq">
`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.
</Accordion>

<Accordion label="¿Cómo sé que mi edición ocurrió? ¿Por qué mi script insertó la línea dos veces?" group="faq">
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:

```sh
grep -q '^PermitRootLogin' /etc/ssh/sshd_config || \
  sed -i '/^#PermitRootLogin/a\
PermitRootLogin no' /etc/ssh/sshd_config
```
</Accordion>

<Accordion label="¿Por qué sed 'r file' no insertó nada?" group="faq">
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.
</Accordion>

<Accordion label="¿Por qué mi hard link sigue mostrando contenido viejo tras sed -i?" group="faq">
`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`.
</Accordion>

<Accordion label="¿Todo esto funciona en macOS y en imágenes Docker Alpine?" group="faq">
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.
</Accordion>