Terminer une boucle infinie

52

J'ai une commande que je veux relancer automatiquement chaque fois qu'elle se termine, alors j'ai lancé quelque chose comme ceci:

while [ 1 ]; do COMMAND; done;

mais si je ne peux pas arrêter la boucle avec Ctrl-cça, ça tue COMMANDet pas la boucle entière.

Comment pourrais-je obtenir quelque chose de similaire mais que je peux arrêter sans avoir à fermer le terminal?

Howardh
la source
11
Si je suis en bash, j'utilise simplement Ctrl-Z pour arrêter le travail, puis "kill% 1" pour le tuer.
Paul Cager
3
Attendez, attendez ... Linus aurait déclaré: "Nous savons tous que Linux est génial ... il effectue des boucles infinies en 5 secondes." - Alors vraiment ... attendez quelques secondes de plus, cela devrait se terminer.
Lornix
@PaulCager a travaillé pour moi aussi! Pourquoi ça marche là où Ctrl-C ne fonctionne pas?
Ciro Santilli a annoncé le
@cirosantilli tue le travail externe (le "wrapper" de bash). Dans certaines situations, le "COMMAND" ne sera pas tué immédiatement. Par exemple, si vous le placez en arrière-plan, il risque de passer inaperçu, même si le parent est mort. Mais la boucle est morte, et c'est la partie importante.
orion

Réponses:

38

Vérifiez le statut de sortie de la commande. Si la commande s'est terminée par un signal, le code de sortie sera 128 + le numéro du signal. De la documentation en ligne GNU pour bash :

Pour les besoins du shell, une commande qui quitte avec un statut de sortie nul a réussi. Un état de sortie non nul indique un échec. Ce schéma apparemment contre-intuitif est utilisé, de sorte qu’il existe un moyen bien défini d’indiquer le succès et une variété de moyens pour indiquer divers modes de défaillance. Lorsqu'une commande se termine sur un signal fatal dont le numéro est N, Bash utilise la valeur 128 + N comme état de sortie.

POSIX spécifie également que la valeur d'une commande terminée par un signal est supérieure à 128, mais ne semble pas spécifier sa valeur exacte comme le fait GNU:

L'état de sortie d'une commande qui s'est terminée parce qu'elle a reçu un signal doit être supérieur à 128.

Par exemple, si vous interrompez une commande avec control-C, le code de sortie sera 130, car SIGINT est le signal 2 sur les systèmes Unix. Alors:

while [ 1 ]; do COMMAND; test $? -gt 128 && break; done
Kyle Jones
la source
9
Il convient de mentionner que cela n’est pas garanti. En fait, de nombreuses applications ne le feront pas.
Chris Down
1
@ Kyle Jones: pouvez-vous créer un lien vers les documents POSIX / GNU qui le mentionnent?
Ciro Santilli a annoncé le
@cirosantilli Fait.
Kyle Jones
@ KyleJones merci! Toujours en pratique ne fonctionne pas pour COMMAND = paplay alert.ogg, peut-être parce que paplaygère le signal?
Ciro Santilli a annoncé le
@cirosantilli Oui, c'est la raison. Si un processus gère le signal et se ferme, la procédure est terminée par un signal non géré.
Kyle Jones
49

Vous pouvez arrêter et mettre votre travail en arrière-plan pendant son exécution à l'aide de ctrl+ z. Ensuite, vous pouvez tuer votre travail avec:

$ kill %1

Où [1] est votre numéro de travail.

Jem
la source
Voir aussi cette réponse pour des explications et plus.
Skippy le Grand Gourou
2
Cette réponse relativement récente fonctionne tout simplement. Doit être voté. +1
shivams
Tu m'as beaucoup aidé. Voici ce que j'ai cherché dans cette question :)
Maximilian Ruta
18

Je dirais qu'il serait peut-être préférable de mettre votre boucle infinie dans un script et de gérer les signaux là-bas. Voici un point de départ de base . Je suis sûr que vous voudrez le modifier pour l'adapter. Le script utilise trappour attraper ctrl- c(ou SIGTERM), élimine la commande (je l’ai utilisé sleepici comme test) et quitte.

cleanup ()
{
kill -s SIGTERM $!
exit 0
}

trap cleanup SIGINT SIGTERM

while [ 1 ]
do
    sleep 60 &
    wait $!
done
ephsmith
la source
4
Agréable. Voici comment j'ai utilisé cette astuce pour créer un wrapper netcat à démarrage automatique:trap "exit 0" SIGINT SIGTERM; while true; do netcat -l -p 3000; done
Douglas, le
2
si vous ajoutez cette trapapproche au même script (bash) avec la boucle infinie à tuer, utilisez-la à la $$place de $!(voir ici )
lundi
15

En général, je tiens juste Ctrl-C. Tôt ou tard, il s'enregistrera entre COMMANDet sonnera ainsi la whileboucle. Peut-être y a-t-il un meilleur moyen.

jw013
la source
1
Vous ne savez pas pourquoi, mais cela échoue pour certaines commandes comme paplaysur un fichier 1s.
Ciro Santilli a annoncé le
Cela a fonctionné pour moi
Alexandre
C'est la force brute de toutes les solutions ici. : /
markroxor le
8

Si vous exécutez bash avec -eil se terminera en cas d'erreur:

#!/bin/bash -e
false # returns 1
echo This won't be printed
Rétablir Monica
la source
8

Pourquoi pas simplement,

while [ 1 ]; do COMMAND || break; done;

Ou lorsqu'il est utilisé dans un script,

#!/bin/bash
while [ 1 ]; do
  # ctrl+c terminates COMMAND and exits the while loop
  # (assuming COMMAND responds to ctrl+c)
  COMMAND || break
done;
Dale Anderson
la source
1
Solution très élégante. Mais cela ne fonctionnerait-il pas uniquement si COMMAND renvoie toujours un statut de sortie réussi?
Howardh
Oui @ howardh, c'est vrai.
Dale Anderson
3

Je préfère une autre solution:

touch .runcmd; while [ -f ".runcmd" ]; do COMMAND; sleep 1; done

Afin de tuer la boucle, il suffit de faire:

rm .runcmd && kill `pidof COMMAND`
M4tze
la source
2
  1. Vous pouvez toujours tuer un processus en utilisant son PID, il n'est pas nécessaire de fermer votre terminal
  2. Si vous voulez exécuter quelque chose dans une boucle infinie comme un démon, vous feriez bien de le mettre en arrière-plan.
  3. while : créera une boucle infinie et vous évitera d’écrire le [ 1 ]

    while :; do COMMAND; done &

Ceci imprimera le PID. Si vous quittez votre invite à l'aide ctrl+ddu travail en arrière-plan, il ne sera pas arrêté et vous pourrez ensuite le supprimer de n'importe où en utilisantkill PID

Si vous perdez la trace de votre PID, vous pouvez utiliser pstree -pa $USERou pgrep -fl '.*PROCESS.*'pour vous aider à le trouver.

errant.info
la source
0

Utiliser trap-

exit_()
{
    exit
}

while true
do
    echo 'running..'
    trap exit_ int
done
markroxor
la source