Lo que rm ya rechaza y lo que no
El rm de GNU rechaza rm -rf / por defecto. Eso protege una sola ruta literal. Los accidentes habituales son distintos:
rm -rf /*: la shell expande antes el asterisco, así quermrecibe/bin,/etcy el resto, y ninguno es/.rm -rf /var/app /*: un espacio de más añade todo el sistema a la lista.- Una variable vacía en un script:
rm -rf "$DIR"/*se convierte enrm -rf /*. - El comando correcto en la terminal equivocada, en un servidor que creías de staging.
--preserve-root=all va un paso más allá y rechaza cualquier carpeta que esté en un disco distinto del de su carpeta padre, como un volumen montado:
rm: skipping '/dev/shm', since it's on a different device
rm: and --preserve-root=all is in effect
Hábitos como listar un patrón con echo antes de borrarlo están en cómo evitar errores en servidores de producción.
Haz que rm pregunte una vez
Pon estas dos líneas en ~/.bashrc en el servidor, y en /root/.bashrc:
alias rm='rm -I --preserve-root=all'
alias sudo='sudo '
-I pregunta una vez antes de un borrado recursivo o de eliminar más de tres archivos. Cualquier respuesta que no sea y no borra nada. El espacio final del alias de sudo hace que bash expanda también la palabra siguiente como alias, así que sudo rm también pregunta:
$ sudo rm /tmp/s/*
rm: remove 4 arguments? n
zsh pregunta por sí solo antes de rm * y rm path/*, salvo que esté activo RM_STAR_SILENT:
zsh: sure you want to delete all 2 files in /tmp/z [yn]? n
Los alias solo se aplican en shells interactivas. Los scripts, las tareas de cron y ssh server 'rm ...' usan el rm real, y \rm o command rm se saltan el alias a propósito. Evita alias rm='rm -i': una pregunta por cada archivo acostumbra a la gente a escribir -f.
safe-rm: rutas que rm se salta
safe-rm pone un envoltorio delante de rm que se salta cualquier ruta de una lista protegida. En Debian y Ubuntu:
sudo apt install safe-rm
echo '/srv/app/shared' | sudo tee -a /etc/safe-rm.conf
La lista ya incluye carpetas del sistema como /etc, /usr y /var. Nuestra prueba:
$ rm -rf /srv/app/shared
safe-rm: Skipping /srv/app/shared.
Conoce sus límites antes de confiar en él:
- Protege la ruta de la lista, no lo que hay dentro.
rm -rf /srv/app/shared/*vació igualmente la carpeta. Una línea como/srv/data/*protege cada entrada que está directamente en/srv/data. - Sale con 0 aunque se haya saltado algo, así que un script no se entera.
- El paquete añade su carpeta al
PATHen/etc/profile.d, así que funciona en shells de inicio de sesión y consudo -i.sudo rmusa elsecure_pathpropio de sudo y ejecutó elrmreal en nuestra prueba, y lo mismo pasa conssh server 'rm ...'.
Protege las variables de los scripts
La mayoría de los desastres en scripts vienen de una variable vacía o sin definir. Detén el script antes de que llegue a rm:
#!/usr/bin/env bash
set -euo pipefail
rm -rf -- "${BUILD_DIR:?}/"
s.sh: line 3: BUILD_DIR: parameter null or not set
${BUILD_DIR:?} se detiene con un error cuando la variable está vacía o no existe, y set -u hace lo mismo con cualquier variable sin definir del script. -- cierra las opciones, así un archivo llamado -rf se trata como archivo.
Permisos y archivos inmutables
La mejor protección es no poder borrar. Trabaja como usuario normal y usa sudo solo para los comandos que lo necesitan, así un descuido en tu carpeta personal no alcanza al sistema. Consulta cómo crear un usuario SSH con sudo.
Para archivos que no deben cambiar ni siquiera para root, activa el atributo inmutable, en ext4 y otros sistemas de archivos que lo admiten:
sudo chattr +i /etc/app/config.yml
lsattr /etc/app/config.yml
----i---------e------- /etc/app/config.yml
rm: cannot remove '/etc/app/config.yml': Operation not permitted
El mismo error detuvo rm -rf en la carpeta que lo contiene. Quita el atributo con sudo chattr -i antes de un cambio planificado. Las actualizaciones y los editores tampoco pueden escribir en el archivo, así que reserva el atributo para los archivos que de verdad lo necesitan.
Una papelera en lugar de rm
Cuando limpias a mano, mueve los archivos a una papelera. trash-cli usa la misma papelera que el escritorio de Linux:
sudo apt install trash-cli
trash-put old-release/
trash-list
trash-restore
trash-empty 30
trash-restore pregunta qué elemento recuperar. trash-empty 30 elimina lo que lleva más de 30 días en la papelera, algo que encaja bien en una tarea de cron. Hasta entonces los archivos siguen ocupando disco. No hagas de rm un alias de trash-put: las opciones son distintas y la costumbre falla en servidores que no lo tienen.
Snapshots, copias de seguridad, y si ya ha pasado
Todas las capas anteriores se pueden saltar. Un snapshot no se borra con rm. Haz uno antes de un trabajo arriesgado: zfs snapshot pool/data@before-cleanup en ZFS, btrfs subvolume snapshot -r en btrfs, lvcreate --snapshot en LVM, o un snapshot del volumen en la consola de tu proveedor cloud. Copia las copias de seguridad a otra máquina, por ejemplo con rsync por SSH, y prueba a restaurar de vez en cuando.
Si los archivos ya no están, deja de escribir en ese disco: los datos nuevos pueden sobrescribir lo que una herramienta de recuperación aún podría encontrar. Después restaura desde un snapshot o una copia de seguridad. Hay un caso que se salva al momento: un archivo que un programa en marcha aún tiene abierto. Búscalo y cópialo antes de que ese programa se detenga:
lsof +L1
cp /proc/374/fd/3 /root/data.recovered
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NLINK NODE NAME
tail 374 root 3r REG 0,186 2 0 12873355 /srv/data.txt (deleted)
El número después de /proc/ es el PID, y el que va después de fd/ es el número de la columna FD sin su letra.
Preguntas
¿Sigue funcionando rm -rf / en Linux?
No con el rm de GNU: rechaza / salvo que añadas --no-preserve-root. Pero la shell expande rm -rf /* en /bin, /etc y el resto, y eso no se rechaza.
¿Se puede deshacer un rm -rf?
No con el propio rm. Restaura desde un snapshot o una copia de seguridad. Un archivo que un programa en marcha aún tiene abierto se puede copiar desde /proc/PID/fd hasta que ese programa lo cierre.
¿Es buena idea alias rm='rm -i'?
Pregunta por cada archivo, así que la gente aprende a responder que sí sin leer o a añadir -f. rm -I pregunta una sola vez, con tres archivos o más o en un borrado recursivo, y así la pregunta sigue teniendo sentido.
¿safe-rm protege los archivos dentro de una carpeta?
No. Se salta las rutas de su configuración. rm -rf /srv/app/shared/* sigue vaciando un /srv/app/shared protegido, salvo que también se listen las entradas de dentro, por ejemplo con /srv/app/shared/*.
Fuentes
- Manual de GNU coreutils: rm invocation
- Proyecto safe-rm
- chattr(1), manual de Linux
- Proyecto trash-cli
- Manual de zsh: RM_STAR_SILENT
- OpenZFS: zfs-snapshot(8)