Un controllo di rete che a volte non risponde mai. Un comando che dovrebbe durare un secondo e resta lì per ore, bloccando uno script o una copia di sicurezza notturna. Il problema è sempre lo stesso: qualcosa che non finisce. timeout gli dà un limite di tempo: se il comando non finisce entro quel tempo, lo ferma.
Cos'è
timeout fa parte del pacchetto coreutils, quindi è presente su ogni distribuzione. È un "involucro": si scrive davanti a un altro comando, e vigila su di esso. Non conosce il comando che sorveglia e non ne cambia il funzionamento: lo avvia, guarda l'orologio e interviene solo se il tempo scade.
Cosa fa
Avvia il comando e aspetta. Se il comando finisce prima del limite, timeout finisce con lo stesso risultato. Se il tempo scade, invia al comando un segnale (di norma TERM, "terminati": vedi l'articolo su ps e kill) e termina con un codice di uscita speciale, 124, che dice "il tempo è scaduto".
Come si usa
$ timeout [opzioni] durata comando [argomenti]La durata è un numero, con un'unità facoltativa: s secondi (predefinita), m minuti, h ore, d giorni; sono ammessi i decimali. Gli esempi sono stati provati in un sandbox; i nomi e i percorsi del server web01 sono illustrativi.
Esempio 1: il comando di base
$ timeout 5 sleep 10
$ echo $?
124
$ timeout 5 sleep 1
$ echo $?
0
$ timeout 1.5 sleep 3; echo $?
124
$ timeout 2m /home/enzo/bin/backup.sh
$ timeout 1h rsync -a /var/www/ /mnt/dati/www/Il primo sleep (una pausa di 10 secondi) viene interrotto dopo 5 e il codice di uscita (la variabile $?, vedi gli articoli su echo e export) è 124. Il secondo finisce da solo in tempo: il codice è quello del comando (0, tutto bene). La durata può essere decimale (1.5) e usare le unità: 2m sono due minuti, 1h un'ora. Se scrivi 0, il limite è disattivato.
Esempio 2: i codici di uscita
$ timeout 5 true; echo $?
0
$ timeout 5 false; echo $?
1
$ timeout 1 sleep 5; echo $?
124
$ timeout 5 nonesiste; echo $?
timeout: failed to run command 'nonesiste': No such file or directory
127
$ timeout abc true; echo $?
timeout: invalid time interval 'abc'
125
$ timeout -s KILL 1 sleep 5; echo $?
137Il codice di uscita permette agli script di distinguere i casi:
- 0 e gli altri: il comando è finito in tempo, e
timeoutrestituisce il suo codice (0 per riuscito, 1 per fallito, ecc.); - 124: il tempo è scaduto e il comando è stato fermato;
- 125: è
timeoutstesso che ha avuto un problema (per esempio una durata scritta male); - 126 e 127: il comando esiste ma non si può eseguire, oppure non esiste;
- 137 (128 + 9): il comando è stato ucciso dal segnale
KILL(numero 9). Allo stesso modo, 143 èTERM(128 + 15).
Con l'opzione --preserve-status il codice di uscita è sempre quello del comando, anche in caso di scadenza (con TERM diventa 143 e non più 124): utile se non vuoi distinguere i due casi.
Esempio 3: quando il comando ignora il segnale, con -k
$ cat testardo.sh
#!/bin/bash
trap '' TERM
for i in 1 2 3 4 5 6 7 8; do sleep 1; done
$ timeout 1 ./testardo.sh; echo $?
124
$ timeout -k 1 1 ./testardo.sh; echo $?
Killed
137Il segnale TERM è una richiesta educata: un programma può ignorarla o gestirla a modo suo (come fa lo script dell'esempio con trap ''). Nella prima prova, timeout ha mandato TERM dopo 1 secondo, ma lo script l'ha ignorato ed è andato avanti per i suoi 8 secondi; solo allora timeout ha restituito 124. Con -k 1 (kill-after) timeout aspetta ancora 1 secondo dopo il primo segnale e poi manda KILL, che non può essere ignorato: il comando muore dopo 2 secondi, e il codice è 137. È la combinazione consigliata per i comandi che potrebbero non rispondere.
Esempio 4: scegliere il segnale, con -s, e vedere cosa succede, con -v
$ timeout -v 1 sleep 3
timeout: sending signal TERM to command 'sleep'
$ timeout -s INT 1 sleep 3; echo $?
124
$ timeout -s HUP 1 sleep 3; echo $?
124
$ timeout -s KILL 1 sleep 3; echo $?
137Con -s (signal) si cambia il segnale (INT è lo stesso di Ctrl+C, HUP quello dell'articolo su nohup, KILL l'uccisione immediata). Con -v (verbose) timeout scrive su quale comando sta intervenendo: utile per capire cosa è successo in uno script. Se il comando ha rispettato il segnale scelto, il codice resta 124.
Esempio 5: comandi di rete che potrebbero non rispondere
$ timeout 10 curl -s https://example.org/ -o pagina.html
$ echo $?
124
$ curl -s --max-time 10 https://example.org/ -o pagina.html
$ echo $?
28
$ timeout 20 ssh enzo@203.0.113.10 uptime
$ ssh -o ConnectTimeout=5 enzo@203.0.113.10 uptime
$ timeout 5 ping 192.168.1.1Molti programmi hanno già un loro limite: curl --max-time (vedi l'articolo su curl; il codice di uscita in questo caso è 28), ssh -o ConnectTimeout= (limita solo il tempo della connessione: vedi l'articolo su ssh), ping -c e -w, rsync --timeout. Dove esiste, il limite integrato è più preciso. timeout ha il vantaggio di funzionare con qualunque comando, e di limitare il tempo complessivo: un ssh che si collega ma poi resta appeso a un comando che non finisce, il suo ConnectTimeout non lo fermerebbe. Ho provato con un finto server che accetta la connessione e non risponde: timeout 2 curl è stato interrotto dopo 2 secondi.
Esempio 6: guardare per un tempo limitato
$ timeout 30 tail -f /var/log/nginx/access.log
$ timeout 60 journalctl -f -u nginx
$ timeout 5m watch -n 5 'df -h /mnt/dati'
$ timeout 10 sh -c 'while true; do date +%T; sleep 1; done'Alcuni comandi non finiscono mai per scelta: tail -f, journalctl -f (vedi l'articolo su journalctl), watch (vedi l'articolo su watch), i cicli infiniti. Con timeout si seguono per un tempo preciso, e poi lo schermo (o lo script) torna libero: comodo per raccogliere 30 secondi di registri in un file, senza dover premere Ctrl+C al momento giusto. Nel mio test, timeout 2 tail -f è terminato con codice 124, come previsto.
Esempio 7: agire in base al risultato, negli script
#!/bin/bash
timeout -k 5 30 /home/enzo/bin/controlla-sito.sh
STATO=$?
case $STATO in
0) echo "Controllo riuscito" ;;
124) echo "Il controllo ha superato i 30 secondi" >&2 ;;
137) echo "Il controllo è stato ucciso" >&2 ;;
*) echo "Controllo fallito (codice $STATO)" >&2 ;;
esacLo script lancia un controllo (qui un programma immaginario) con un limite di 30 secondi, e con -k 5 lo uccide se non risponde al primo segnale. Salva subito il codice di uscita in STATO e lo valuta con case, distinguendo il successo, la scadenza (124) e l'uccisione (137). Salvare subito $? in una variabile è importante: il comando successivo lo sovrascriverebbe.
Esempio 8: pipe, funzioni e comandi composti
$ timeout 5 comando1 | comando2
$ timeout 5 sh -c 'comando1 | comando2'
$ f() { echo "inizio"; sleep 3; echo "fine"; }
$ timeout 1 f
timeout: failed to run command 'f': No such file or directory
$ export -f f
$ timeout 1 bash -c f
inizioDue trappole classiche. La prima: timeout controlla un solo comando, quello che segue; in una pipe (|) il limite vale solo per il primo, per metterlo su tutta la catena si usa sh -c '...' tra apici (come per nohup). La seconda: una funzione della shell non è un programma, e timeout non la vede (codice 127, "failed to run command"). Si risolve con export -f funzione (vedi l'articolo su export) e lanciandola con bash -c funzione: così timeout avvia una nuova bash che la conosce. Lo stesso vale per gli alias (vedi l'articolo su alias): timeout non li vede.
Esempio 9: proteggere i lavori programmati
30 3 * * * timeout -k 60 2h /home/enzo/bin/backup.sh >> /home/enzo/backup.log 2>&1
$ echo "timeout 1h /home/enzo/bin/rielabora.sh" | at 03:00
$ nohup timeout 6h ./elabora-statistiche.sh > stat.log 2>&1 < /dev/null &I lavori che girano senza nessuno davanti sono quelli che hanno più bisogno di un limite. La prima riga è una voce di crontab (vedi l'articolo relativo): la copia notturna parte alle 3:30 e, se dopo due ore non è finita, viene fermata, con un minuto di tolleranza prima dell'uccisione. Senza il limite, una copia bloccata potrebbe restare lì fino al giorno dopo, e la copia successiva partire sopra la prima. Lo stesso vale per at (vedi l'articolo su at) e per i lavori lanciati con nohup.
Esempio 10: il limite per l'input, con read -t
$ read -t 5 -p "Continuare? [s/N] " RISPOSTA || RISPOSTA=n
Continuare? [s/N]
$ echo "${RISPOSTA:-n}"
nPer un input da tastiera esiste il comando interno di bash read -t secondi, che smette di aspettare allo scadere del tempo: restituisce un codice di uscita maggiore di 128 (nei miei test, 142) e la variabile resta vuota. È il modo giusto di scrivere una domanda con risposta predefinita in uno script, senza bloccarlo se nessuno risponde.
Opzioni utili
timeout durata comando: ferma il comando dopo la durata (s,m,h,d; decimali ammessi;0= nessun limite).-s SEGNALE: sceglie il segnale (TERMpredefinito;INT,HUP,KILL, anche in numero).-k durata: se il comando è ancora vivo dopo questo tempo, lo uccide conKILL.--preserve-status: restituisce sempre il codice del comando, anche alla scadenza.-v: scrive quale segnale viene inviato;--foreground: per i comandi che devono leggere dal terminale.sleep,read -t,curl --max-time,ssh -o ConnectTimeout,ping -w,rsync --timeout: limiti già presenti in altri programmi.- Codici di uscita:
124scaduto,125errore ditimeout,126/127comando non eseguibile o assente,137ucciso conKILL.
Attenzione a
- Il codice 124 può essere ambiguo: se il comando stesso restituisce 124 per un motivo suo, non lo distingui da una scadenza. Per i comandi che usano questo codice, o per capire cosa è successo, aggiungi
-v. - Interrompere a metà può lasciare danni: un file copiato solo in parte, un archivio
tartroncato, un'esportazione di un database incompleta. Dopo una scadenza non fidarti del risultato: controlla, o scrivi in un file temporaneo e rinomina solo se tutto è riuscito (vedi l'articolo sumv). TERMsi può ignorare: un programma ostinato continua a girare, etimeoutaspetta che finisca da solo (esempio 3). Se il comando deve essere fermato in ogni caso, aggiungi sempre-k.- I processi figli possono restare vivi:
timeoutferma il comando che ha lanciato, ma non è detto che si fermino anche i programmi lanciati a loro volta. Nel mio sandbox, untimeout 1 bash -c 'sleep 37; echo dopo'ha fermato labashma ha lasciato ilsleepin esecuzione. Dopo una scadenza verifica conpgrep(vedi l'articolo sups), e se serve lancia direttamente il programma che vuoi limitare, o usaexecnellabash -c(timeout 1 bash -c 'exec sleep 36'funziona bene). - Non vale per le funzioni e per gli alias: né per le catene con pipe, che vanno racchiuse in
sh -c(esempio 8). - I comandi che chiedono qualcosa al terminale (la password di
ssh, una conferma):timeoutli esegue in modo che possano non riuscire a leggere dalla tastiera. Se serve, aggiungi--foreground(con la limitazione che i processi figli non ricevono il limite). Per le domande negli script, usaread -t. - Il tempo è quello dell'orologio, non del processore:
timeoutnon limita il consumo di CPU o di memoria, ma solo la durata. Un comando fermo ad aspettare consuma tempo come uno che lavora. Per limitare le risorse servono altri strumenti (ulimit, o le impostazioni disystemd). - Un limite troppo stretto causa falsi allarmi: un server sotto carico (vedi l'articolo su
uptime) può rallentare un comando che di solito è rapido. Scegli un tempo con un margine, e registra le scadenze (per esempio conlogger: vedi l'articolo sujournalctl). - Prima di un comando distruttivo, pensa due volte:
timeout 5 rm -rf ...non protegge da niente, e se il tempo scade a metà lascia il lavoro incompleto.timeoutè fatto per i comandi che potrebbero bloccarsi, non per quelli pericolosi.
Riepilogo
| Comando | Cosa fa |
|---|---|
timeout 10 comando | Ferma il comando dopo 10 secondi (codice 124) |
timeout 2m comando | Limite di 2 minuti (anche h, d, decimali) |
timeout -k 5 30 comando | Dopo 30 s richiede la fine, dopo altri 5 s uccide (137) |
timeout -s KILL 10 comando | Usa subito il segnale KILL |
timeout -v 10 comando | Dice quale segnale invia |
timeout --preserve-status 10 comando | Restituisce il codice del comando, anche se scaduto |
timeout 10 sh -c 'a | b' | Limita tutta una pipe |
export -f f; timeout 5 bash -c f | Limita una funzione di bash |
timeout 30 tail -f file | Segue un file per un tempo limitato |
read -t 5 -p "..." VAR | Aspetta una risposta per 5 secondi |
curl --max-time 10 URL | Limite integrato di curl (codice 28) |