Salta al contenuto principale

nohup: comandi che sopravvivono al terminale

Inviato da tuxsa il

Lanci una copia o un'elaborazione lunga, e ti accorgi che servirà un'ora. Chiudi il terminale, o la connessione ssh cade, e il comando si ferma a metà. nohup serve a evitarlo: dice al programma di non arrendersi quando il terminale "riaggancia". È piccolo, vecchio e utilissimo, e si usa quando tmux (vedi l'articolo precedente) sarebbe eccessivo.

Cos'è

nohup (da no hang up, "non riagganciare") fa parte del pacchetto coreutils, quindi è presente su ogni distribuzione. Per capirne il senso serve un dettaglio: quando un terminale si chiude (perché lo chiudi tu, o perché la connessione cade) il sistema manda a tutti i programmi che stava gestendo un segnale chiamato SIGHUP (hangup, "riaggancio", come il telefono). Per impostazione predefinita, un programma che riceve SIGHUP si ferma. I segnali si vedono nell'articolo su ps e kill.

Cosa fa

nohup lancia un comando facendogli ignorare SIGHUP, e sistema l'ingresso e l'uscita perché non dipendano più dal terminale: l'ingresso viene scollegato, e l'uscita, se non l'hai già indirizzata altrove, viene scritta in un file chiamato nohup.out. Non fa altro: non manda il programma "in secondo piano" (per questo si aggiunge &) e non lo rende immune ad altri modi di essere fermato.

Come si usa

$ nohup comando [argomenti] &

Gli esempi usano il server web01 degli articoli precedenti. Il comportamento di nohup, i messaggi e il fatto che il segnale ferma un programma normale e non uno protetto li ho provati in un sandbox; gli altri dettagli (copia, tempi) sono illustrativi.

Esempio 1: il problema

$ ./elabora-statistiche.sh
(la connessione ssh cade)
Connection to web01 closed by remote host.

Il programma è stato lanciato dal terminale, e dipende da esso. Quando la sessione cade, il sistema invia SIGHUP al programma, che si ferma con il lavoro a metà: lo stesso accade se chiudi la finestra del terminale per sbaglio. Si può simulare: un sleep 120 lanciato in secondo piano muore se gli si manda kill -HUP, mentre lo stesso comando lanciato con nohup continua a vivere.

Esempio 2: il comando di base

$ nohup ./elabora-statistiche.sh &
[1] 4410
nohup: ignoring input and appending output to 'nohup.out'
$ tail -f nohup.out
$ exit

Con nohup comando & il programma parte in secondo piano (&) e protetto. La shell stampa il numero del lavoro ([1]) e il numero del processo (4410, il PID); nohup avverte che sta ignorando l'ingresso e che scrive l'uscita in nohup.out, nella cartella in cui ti trovi (se non è scrivibile, nella tua cartella personale). Il file viene creato con permessi riservati al solo proprietario (600: vedi l'articolo su chmod e chown). Con tail -f (vedi l'articolo su head e tail) si segue l'uscita in tempo reale, e con exit ci si può anche scollegare: il programma continua.

Esempio 3: scegliere dove scrivere l'uscita

$ nohup ./elabora-statistiche.sh > statistiche.log 2>&1 &
$ nohup ./elabora-statistiche.sh > statistiche.log 2>&1 < /dev/null &
$ nohup ./elabora-statistiche.sh >> statistiche.log 2>&1 < /dev/null &
$ head -n 2 statistiche.log
nohup: ignoring input
Elaborazione iniziata

Al posto del generico nohup.out è meglio indicare un file con un nome sensato: > file scrive l'uscita normale, 2>&1 manda nello stesso file anche gli errori (vedi l'articolo su echo e tee). Nella prima forma, la riga nohup: ignoring input finisce dentro il file. Per evitarla si scollega l'ingresso da soli con < /dev/null (seconda forma): l'avviso sparisce. Con >> (terza forma) il file non viene sovrascritto ma allungato, utile se lo stesso lavoro viene lanciato più volte. Le tre forme sono equivalenti per nohup: cambia solo come tieni in ordine i registri.

Esempio 4: controllare e fermare un lavoro

$ jobs
[1]+  In esecuzione   nohup ./elabora-statistiche.sh > statistiche.log 2>&1 &
$ pgrep -af elabora-statistiche
4410 /bin/bash ./elabora-statistiche.sh
$ ps -o pid,etime,stat,cmd -p 4410
  PID     ELAPSED STAT CMD
 4410       00:42 S    /bin/bash ./elabora-statistiche.sh
$ kill 4410

Finché sei nella stessa sessione, jobs elenca i lavori in secondo piano. Dopo esserti scollegato e ricollegato, non li vedi più, ma il programma c'è: lo trovi con pgrep -af (vedi l'articolo su ps) o con ps, che mostra anche da quanto tempo gira (ELAPSED). Per fermarlo, kill con il suo numero. Il programma non è più legato a un terminale: nella colonna TTY di ps compare un punto interrogativo (?).

Esempio 5: conservare il numero del processo

$ nohup ./elabora-statistiche.sh > statistiche.log 2>&1 < /dev/null &
$ echo $! > statistiche.pid
$ cat statistiche.pid
4410
$ kill "$(cat statistiche.pid)"

La variabile $! contiene il numero dell'ultimo processo avviato in secondo piano. Salvarlo in un file permette di fermare il lavoro anche giorni dopo, senza doverlo cercare. È un'abitudine molto usata per i programmi lanciati a mano.

Esempio 6: più comandi insieme, con sh -c

$ nohup rsync -a /var/www/ /mnt/dati/www/ && echo finito &
$ nohup sh -c 'rsync -a /var/www/ /mnt/dati/www/ && echo finito' \
    > copia.log 2>&1 < /dev/null &
$ cat copia.log
nohup: ignoring input
finito

Attenzione: nohup protegge un solo comando, quello che gli segue direttamente. Nel primo esempio protegge solo rsync (vedi l'articolo relativo), mentre echo finito viene dopo, fuori dalla protezione, e il tutto è lanciato nel modo sbagliato. Lo stesso vale per le pipe: in nohup cmd1 | cmd2 è protetto solo cmd1. Quando servono più comandi, pipe o &&, li si racchiude in sh -c '...' tra apici, così nohup protegge tutta la sequenza (secondo esempio, andato a capo con la barra rovesciata).

Esempio 7: partire da un lavoro già avviato, con disown

$ ./elabora-statistiche.sh
^Z
[1]+  Fermato     ./elabora-statistiche.sh
$ bg %1
[1]+ ./elabora-statistiche.sh &
$ disown -h %1
$ exit

Se ti accorgi dopo di aver lanciato un comando lungo senza nohup, non serve ricominciare. Ctrl+Z lo sospende, bg %1 lo fa proseguire in secondo piano, e disown -h %1 (un comando interno di bash) gli toglie il collegamento con la shell, così non riceve più SIGHUP alla chiusura. Un limite: l'uscita resta legata al terminale, per cui un programma che continua a scrivere sullo schermo può fermarsi o fallire quando il terminale sparisce. È un rimedio d'emergenza; meglio partire con nohup o con tmux.

Esempio 8: le alternative a nohup

$ setsid ./elabora-statistiche.sh > statistiche.log 2>&1 < /dev/null &
$ tmux new -d -s stat './elabora-statistiche.sh'
$ sudo systemd-run --unit=statistiche ./elabora-statistiche.sh
$ echo './elabora-statistiche.sh' | at 03:00

Ci sono più modi di far vivere un programma oltre il terminale, e ognuno ha il suo uso: setsid avvia il comando in una sessione nuova, separata dal terminale (nel mio sandbox il processo risultava figlio del primo processo del sistema, non della shell); tmux permette anche di tornare a vedere e a usare il programma, cosa che con nohup non si può; systemd-run lo avvia come servizio temporaneo gestito da systemd (e poi si segue con systemctl e journalctl); at lo lancia una volta sola, a un'ora decisa. Per un lavoro che deve ripartire dopo un riavvio, la scelta giusta è un servizio vero o crontab con @reboot.

Esempio 9: uso negli script

#!/bin/bash
LOG="$HOME/copia.log"
PIDFILE="$HOME/copia.pid"

if [ -f "$PIDFILE" ] && kill -0 "$(cat "$PIDFILE")" 2>/dev/null; then
    echo "Già in esecuzione (PID $(cat "$PIDFILE"))" >&2
    exit 1
fi

nohup rsync -a /var/www/ /mnt/dati/www/ >> "$LOG" 2>&1 < /dev/null &
echo $! > "$PIDFILE"
echo "Avviato, PID $!. Segui con: tail -f $LOG"

Lo script avvia la copia in secondo piano e si ricorda del suo numero. Prima controlla se esiste già un processo con quel numero: kill -0 non manda nessun segnale, ma ha successo solo se il processo esiste, ed è il modo standard di chiederselo. Così non lancia due copie uguali per sbaglio. Alla fine dice come seguire l'avanzamento. Ho provato la logica con un comando finto (sleep): la seconda esecuzione si rifiuta di partire.

Opzioni utili

  • nohup comando &: la forma normale (in secondo piano, protetto da SIGHUP).
  • > file 2>&1 < /dev/null: uscita e errori in un file scelto, ingresso scollegato (nessun avviso).
  • $!: il numero dell'ultimo processo in secondo piano; jobs, pgrep -af, ps: per ritrovarlo.
  • nohup sh -c '...': protegge una sequenza di comandi o una pipe.
  • disown -h %1, bg, Ctrl+Z: salvare un lavoro già avviato senza nohup.
  • setsid, tmux, screen, systemd-run, at, crontab: le alternative.
  • kill -0 PID: verifica se un processo esiste; kill PID: lo ferma.

Attenzione a

  • Protegge solo da SIGHUP: se qualcuno manda kill (SIGTERM), se il sistema finisce la memoria (vedi l'articolo su free) o se la macchina si riavvia, il programma si ferma comunque. Non sostituisce un servizio vero.
  • Serve il &: senza, il comando resta in primo piano e il terminale è occupato finché non finisce; e se il terminale cade, nohup lo protegge, ma tu non hai più modo di vederlo.
  • Non può ricevere dati: l'ingresso viene scollegato, quindi un programma che fa domande (una conferma, una password) non può essere lanciato così: o si rende non interattivo con le opzioni apposite, o si usa tmux.
  • nohup.out cresce senza limiti: un programma molto loquace può riempire il disco (controlla con df -h, vedi l'articolo relativo). Indirizza l'uscita a un file con un nome chiaro e controlla ogni tanto la dimensione. Non dimenticare di cancellare i vecchi nohup.out sparsi nelle cartelle.
  • Se l'uscita non è indirizzata, la connessione ssh può restare appesa: se un programma in secondo piano tiene aperto lo schermo, il comando exit sembra non chiudere la sessione. Indirizza sempre l'uscita (> file 2>&1 < /dev/null) e il problema scompare.
  • sudo nohup o nohup sudo? Con sudo nohup comando & il programma gira come root (e nohup.out appartiene a root); con nohup sudo comando & sudo potrebbe chiedere la password, e non può farlo perché l'ingresso è scollegato. Fai prima l'autenticazione con sudo -v, oppure usa la prima forma.
  • I servizi non si lanciano con nohup: programmi come nginx o un database si avviano con systemctl, che si occupa di riavvii, registri e arresti ordinati. Alcuni programmi gestiscono SIGHUP a modo loro (rileggere la configurazione): nohup non è la strada giusta.
  • A volte il sistema chiude comunque tutto all'uscita: alcune configurazioni di systemd (KillUserProcesses=yes) terminano i processi di un utente quando l'ultima sessione si chiude, anche con nohup. Se capita, usa tmux, systemd-run o un servizio.
  • Un lavoro dimenticato resta attivo: ricordati di cercarlo con pgrep e ps se non finisce, e di chiudere ciò che non serve. Un programma in loop infinito consuma processore per giorni.
  • Gli errori di avvio compaiono subito: se il comando non esiste, nohup risponde con nohup: failed to run command 'nome': No such file or directory e termina con codice 127 (126 se il file non è eseguibile). Per i comandi in secondo piano, verifica dopo qualche secondo con jobs o con il registro.

Riepilogo

ComandoCosa fa
nohup comando &Lancia in secondo piano, protetto dalla chiusura del terminale
nohup comando > log 2>&1 < /dev/null &La forma più pulita: uscita nel file scelto, nessun avviso
nohup sh -c 'a && b' &Protegge una sequenza di comandi
echo $! > lavoro.pidSalva il numero dell'ultimo processo in secondo piano
tail -f nohup.outSegue l'uscita in tempo reale
pgrep -af nomeRitrova un lavoro dopo il ricollegamento
kill $(cat lavoro.pid)Ferma il lavoro salvato
kill -0 PIDVerifica se il processo esiste
Ctrl+Z, bg, disown -h %1Salva un lavoro già avviato senza nohup
setsid comandoAvvia in una sessione separata dal terminale
tmux new -d -s nome 'comando'Come sopra, ma puoi tornare a guardarlo
sudo systemd-run --unit=nome comandoAvvia come servizio temporaneo di systemd