La cosa migliore sarebbe usare il timeoutcomando 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 SIGALRMconsegnato tra waitpid()rinvio e di timeoutuscire (annullando efficacemente che allarme ). Durante quella piccola finestra, timeoutpuò 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). timeoutpresterà 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 perlcome:
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 timelimitcomando su http://devel.ringlet.net/sysutils/timelimit/ (precede GNU timeoutdi 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 SIGALRMse 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 timelimitcomando. Che uno precede tutti gli altri di decenni, adotta un altro approccio, ma non funziona correttamente per i comandi arrestati e restituisce uno 1stato 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 psrecupero dello stato di quel processo e l' killuccisione) 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 killsegnalerebbe 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 sleepprocesso rimanga in sospeso anche dopo che cmdè morto, con basho ksh93è usare una pipa con read -tinvece 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 cmdche non chiuda il suo fd 4.
Potresti provare a implementare una soluzione priva di gare perlcome:
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.