La cosa migliore sarebbe usare il timeout
comando se ce l'hai che è pensato per quello:
timeout 86400 cmd
L'attuale implementazione (8.23) GNU funziona almeno usando alarm()
o equivalente durante l'attesa del processo figlio. Non sembra essere guardia dal SIGALRM
consegnato tra waitpid()
rinvio e di timeout
uscire (annullando efficacemente che allarme ). Durante quella piccola finestra, timeout
può persino scrivere messaggi su stderr (per esempio se il bambino ha scaricato un core) che allargherebbe ulteriormente quella finestra di corsa (indefinitamente se stderr è un tubo pieno per esempio).
Personalmente posso convivere con questa limitazione (che probabilmente verrà risolta in una versione futura). timeout
presterà inoltre particolare attenzione a segnalare lo stato di uscita corretto, gestire altri casi angolari (come SIGALRM bloccato / ignorato all'avvio, gestire altri segnali ...) meglio di quanto probabilmente si farebbe a mano.
Come approssimazione, potresti scriverlo perl
come:
perl -MPOSIX -e '
$p = fork();
die "fork: $!\n" unless defined($p);
if ($p) {
$SIG{ALRM} = sub {
kill "TERM", $p;
exit 124;
};
alarm(86400);
wait;
exit (WIFSIGNALED($?) ? WTERMSIG($?)+128 : WEXITSTATUS($?))
} else {exec @ARGV}' cmd
C'è un timelimit
comando su http://devel.ringlet.net/sysutils/timelimit/ (precede GNU timeout
di qualche mese).
timelimit -t 86400 cmd
Quello utilizza un alarm()
meccanismo simile ma installa un gestore SIGCHLD
(ignorando i bambini arrestati) per rilevare la morte del bambino. Annulla anche l'allarme prima dell'esecuzione waitpid()
(che non annulla la consegna di SIGALRM
se era in sospeso, ma il modo in cui è scritto, non riesco a vedere che è un problema) e uccide prima di chiamare waitpid()
(quindi non posso uccidere un pid riutilizzato ).
netpipes ha anche un timelimit
comando. Che uno precede tutti gli altri di decenni, adotta un altro approccio, ma non funziona correttamente per i comandi arrestati e restituisce uno 1
stato di uscita al timeout.
Come risposta più diretta alla tua domanda, potresti fare qualcosa del tipo:
if [ "$(ps -o ppid= -p "$p")" -eq "$$" ]; then
kill "$p"
fi
Cioè, controlla che il processo sia ancora un nostro figlio. Ancora una volta, c'è una piccola finestra di gara (tra il ps
recupero dello stato di quel processo e l' kill
uccisione) durante la quale il processo potrebbe morire e il suo pid può essere riutilizzato da un altro processo.
Con alcune conchiglie ( zsh
, bash
, mksh
), è possibile passare le specifiche di lavoro, invece di pid.
cmd &
sleep 86400
kill %
wait "$!" # to retrieve the exit status
Funziona solo se si genera solo un lavoro in background (altrimenti non è sempre possibile ottenere il jobpec giusto in modo affidabile).
Se questo è un problema, basta avviare una nuova istanza della shell:
bash -c '"$@" & sleep 86400; kill %; wait "$!"' sh cmd
Ciò funziona perché la shell rimuove il lavoro dalla tabella dei lavori quando il bambino muore. Qui, non dovrebbe esserci alcuna finestra di gara poiché al momento della chiamata della shell kill()
, o il segnale SIGCHLD non è stato gestito e il pid non può essere riutilizzato (poiché non è stato atteso), oppure è stato gestito e il il lavoro è stato rimosso dalla tabella dei processi (e kill
segnalerebbe un errore). bash
's kill
, almeno blocchi SIGCHLD prima di accedere la sua tabella di lavoro per espandere la %
e lo sblocca dopo il kill()
.
Un'altra opzione per evitare che quel sleep
processo rimanga in sospeso anche dopo che cmd
è morto, con bash
o ksh93
è usare una pipa con read -t
invece di sleep
:
{
{
cmd 4>&1 >&3 3>&- &
printf '%d\n.' "$!"
} | {
read p
read -t 86400 || kill "$p"
}
} 3>&1
Quella ha ancora le condizioni di gara e perdi lo stato di uscita del comando. Presuppone anche cmd
che non chiuda il suo fd 4.
Potresti provare a implementare una soluzione priva di gare perl
come:
perl -MPOSIX -e '
$p = fork();
die "fork: $!\n" unless defined($p);
if ($p) {
$SIG{CHLD} = sub {
$ss = POSIX::SigSet->new(SIGALRM); $oss = POSIX::SigSet->new;
sigprocmask(SIG_BLOCK, $ss, $oss);
waitpid($p,WNOHANG);
exit (WIFSIGNALED($?) ? WTERMSIG($?)+128 : WEXITSTATUS($?))
unless $? == -1;
sigprocmask(SIG_UNBLOCK, $oss);
};
$SIG{ALRM} = sub {
kill "TERM", $p;
exit 124;
};
alarm(86400);
pause while 1;
} else {exec @ARGV}' cmd args...
(anche se dovrebbe essere migliorato per gestire altri tipi di custodie angolari).
Un altro metodo senza gara potrebbe essere l'utilizzo di gruppi di processi:
set -m
((sleep 86400; kill 0) & exec cmd)
Tuttavia, notare che l'utilizzo di gruppi di processi può avere effetti collaterali se è presente l'I / O su un dispositivo terminale. Ha l'ulteriore vantaggio di uccidere tutti gli altri processi extra generati da cmd
.