Cosa rm rifiuta già, e cosa no
GNU rm rifiuta rm -rf / per impostazione predefinita. Questo protegge un solo percorso letterale. Gli incidenti comuni sono diversi:
rm -rf /*: la shell espande prima l'asterisco, quindirmriceve/bin,/etce il resto, e nessuno di questi è/.rm -rf /var/app /*: uno spazio di troppo aggiunge tutto il sistema all'elenco.- Una variabile vuota in uno script:
rm -rf "$DIR"/*diventarm -rf /*. - Il comando giusto nel terminale sbagliato, su un server che credevi fosse lo staging.
--preserve-root=all fa un passo in più e rifiuta qualsiasi cartella che sta su un disco diverso da quello della cartella madre, come un volume montato:
rm: skipping '/dev/shm', since it's on a different device
rm: and --preserve-root=all is in effect
Abitudini come elencare un glob con echo prima di cancellarlo sono in come evitare errori sui server di produzione.
Far chiedere a rm una volta sola
Metti queste due righe in ~/.bashrc sul server, e in /root/.bashrc:
alias rm='rm -I --preserve-root=all'
alias sudo='sudo '
-I chiede una volta prima di una cancellazione ricorsiva o prima di togliere più di tre file. Qualsiasi risposta diversa da y non cancella nulla. Lo spazio finale nell'alias di sudo dice a bash di espandere come alias anche la parola dopo, così chiede anche sudo rm:
$ sudo rm /tmp/s/*
rm: remove 4 arguments? n
zsh chiede da solo prima di rm * e rm percorso/*, a meno che non sia impostato RM_STAR_SILENT:
zsh: sure you want to delete all 2 files in /tmp/z [yn]? n
Gli alias valgono solo nelle shell interattive. Script, cron e ssh server 'rm ...' usano il vero rm, e \rm o command rm saltano l'alias di proposito. Evita alias rm='rm -i': una domanda per ogni file abitua a scrivere -f.
safe-rm: percorsi che rm salterà
safe-rm mette davanti a rm un programma che salta tutti i percorsi di un elenco protetto. Su Debian e Ubuntu:
sudo apt install safe-rm
echo '/srv/app/shared' | sudo tee -a /etc/safe-rm.conf
L'elenco include già cartelle di sistema come /etc, /usr e /var. La nostra prova:
$ rm -rf /srv/app/shared
safe-rm: Skipping /srv/app/shared.
Conosci i suoi limiti prima di contarci:
- Protegge il percorso elencato, non quello che c'è dentro.
rm -rf /srv/app/shared/*ha comunque svuotato la cartella. Una riga come/srv/data/*protegge ogni elemento direttamente sotto/srv/data. - Esce con 0 anche quando ha saltato qualcosa, quindi uno script non se ne accorge.
- Il pacchetto aggiunge la sua cartella al
PATHin/etc/profile.d, quindi funziona nelle shell di login e consudo -i.sudo rmusa ilsecure_pathdi sudo e nella nostra prova ha eseguito il verorm, e lo stesso vale perssh server 'rm ...'.
Proteggere le variabili negli script
La maggior parte dei disastri negli script nasce da una variabile vuota o non impostata. Ferma lo script prima che arrivi 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:?} si ferma con un errore quando la variabile è vuota o manca, e set -u fa lo stesso per ogni variabile non impostata nello script. -- chiude le opzioni, così un file chiamato -rf viene trattato come un file.
Permessi e file immutabili
La protezione più forte è non poter cancellare. Lavora come utente normale e usa sudo solo per i comandi che lo richiedono, così uno sbaglio nella tua cartella home non arriva al sistema. Vedi come creare un utente SSH con sudo.
Per i file che non devono cambiare nemmeno per root, imposta l'attributo immutabile, su ext4 e sugli altri filesystem che lo supportano:
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
Lo stesso errore ha fermato anche rm -rf sulla cartella che lo contiene. Togli l'attributo con sudo chattr -i prima di una modifica prevista. Nemmeno aggiornamenti ed editor possono scrivere nel file, quindi usalo solo per i file che ne hanno davvero bisogno.
Un cestino al posto di rm
Quando fai pulizia a mano, sposta i file in un cestino. trash-cli usa lo stesso cestino di Linux desktop:
sudo apt install trash-cli
trash-put old-release/
trash-list
trash-restore
trash-empty 30
trash-restore chiede quale elemento rimettere al suo posto. trash-empty 30 toglie ciò che sta nel cestino da più di 30 giorni, ed è adatto a un cron. Fino ad allora i file occupano ancora spazio su disco. Non fare un alias di rm verso trash-put: le opzioni sono diverse, e l'abitudine tradisce sui server dove non c'è.
Snapshot, backup, e se è già successo
Ogni livello qui sopra si può aggirare. Uno snapshot non si cancella con rm. Fanne uno prima di un lavoro rischioso: zfs snapshot pool/data@before-cleanup su ZFS, btrfs subvolume snapshot -r su btrfs, lvcreate --snapshot su LVM, o uno snapshot del volume dalla console del tuo cloud. Copia i backup su un'altra macchina, per esempio con rsync via SSH, e prova ogni tanto un ripristino.
Se i file sono già spariti, smetti di scrivere su quel disco: i dati nuovi possono sovrascrivere ciò che uno strumento di recupero potrebbe ancora trovare. Poi ripristina da uno snapshot o da un backup. Un caso si salva subito: un file ancora aperto da un programma in esecuzione. Trovalo e ricopialo prima che il programma si fermi:
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)
Il numero dopo /proc/ è il PID, quello dopo fd/ è il numero della colonna FD senza la lettera.
Domande
rm -rf / funziona ancora su Linux?
Non con GNU rm: rifiuta / se non aggiungi --no-preserve-root. Però rm -rf /* viene espanso dalla shell in /bin, /etc e il resto, e quello non viene rifiutato.
Si può annullare un rm -rf?
Non con rm. Ripristina da uno snapshot o da un backup. Un file ancora aperto da un programma in esecuzione si può ricopiare da /proc/PID/fd finché quel programma non lo chiude.
alias rm='rm -i' è una buona idea?
Chiede conferma per ogni file, così si impara a rispondere sì senza leggere o ad aggiungere -f. rm -I chiede una volta sola, per tre o più file o per una cancellazione ricorsiva, e la domanda resta significativa.
safe-rm protegge i file dentro una cartella?
No. Salta i percorsi elencati nella sua configurazione. rm -rf /srv/app/shared/* svuota comunque una /srv/app/shared protetta, a meno di elencare anche il contenuto, per esempio con /srv/app/shared/*.
Fonti
- Manuale di GNU coreutils: rm invocation
- Progetto safe-rm
- chattr(1), manuale Linux
- Progetto trash-cli
- Manuale di zsh: RM_STAR_SILENT
- OpenZFS: zfs-snapshot(8)