Bash: sommeil infini (blocage infini)

158

J'utilise startxpour démarrer X qui évaluera mon .xinitrc. Dans mon, .xinitrcje lance mon gestionnaire de fenêtres en utilisant /usr/bin/mywm. Maintenant, si je tue mon WM (afin de tester un autre WM), X se terminera aussi parce que le .xinitrcscript a atteint EOF. J'ai donc ajouté ceci à la fin de mon .xinitrc:

while true; do sleep 10000; done

De cette façon, X ne se terminera pas si je tue mon WM. Maintenant ma question: comment puis-je faire un sommeil infini au lieu de dormir en boucle? Existe-t-il une commande qui voudra un peu geler le script?

Meilleures salutations

Watain
la source

Réponses:

330

sleep infinity fait exactement ce qu'il suggère et fonctionne sans abus de chat.

Donarsson
la source
16
Cool. Malheureusement, ma busybox ne comprend pas.
not-a-user
12
BSD (ou du moins OS X) ne comprend pas non sleep infinityplus, même si c'était une chose intéressante à apprendre pour Linux. Cependant, while true; do sleep 86400; donedevrait être un substitut adéquat.
Ivan X
16
À ce sujet, j'ai fait des recherches que j'ai documentées dans une réponse distincte. Pour résumer: infinityest converti en C de "chaîne" en a double. Ensuite, cela doubleest tronqué aux valeurs maximales autorisées timespec, ce qui signifie une très grande quantité de secondes (dépendant de l'architecture) mais, en théorie, finie.
jp48
72

tail ne bloque pas

Comme toujours: pour tout, il y a une réponse courte, facile à comprendre, facile à suivre et complètement fausse. Entre ici tail -f /dev/nulldans cette catégorie;)

Si vous le regardez avec strace tail -f /dev/nullvous, vous remarquerez que cette solution est loin d'être bloquante! C'est probablement encore pire que la sleepsolution de la question, car elle utilise (sous Linux) des ressources précieuses comme le inotifysystème. Aussi d'autres processus qui écrivent pour /dev/nullfaire une tailboucle. (Sur mon Ubuntu64 16.10, cela ajoute plusieurs 10 appels système par seconde sur un système déjà occupé.)

La question était pour une commande de blocage

Malheureusement, une telle chose n'existe pas.

Lire: Je ne connais aucun moyen d'archiver cela directement avec le shell.

Tout (même sleep infinity) peut être interrompu par un signal. Donc, si vous voulez être vraiment sûr qu'il ne revient pas exceptionnellement, il doit s'exécuter en boucle, comme vous l'avez déjà fait pour votre sleep. Veuillez noter que (sous Linux) /bin/sleepest apparemment plafonné à 24 jours (jetez un œil à strace sleep infinity), donc le mieux que vous puissiez faire est probablement:

while :; do sleep 2073600; done

(Notez que je crois que les sleepboucles en interne pour des valeurs supérieures à 24 jours, mais cela signifie: ce n'est pas un blocage, c'est une boucle très lente. Alors pourquoi ne pas déplacer cette boucle vers l'extérieur?)

.. mais vous pouvez vous en approcher avec un nom sans nom fifo

Vous pouvez créer quelque chose qui bloque vraiment tant qu'aucun signal n'est envoyé au processus. Utilisations suivantes bash 4, 2 PID et 1 fifo:

bash -c 'coproc { exec >&-; read; }; eval exec "${COPROC[0]}<&-"; wait'

Vous pouvez vérifier que cela bloque vraiment avec stracesi vous aimez:

strace -ff bash -c '..see above..'

Comment cela a été construit

readbloque s'il n'y a pas de données d'entrée (voir quelques autres réponses). Cependant, le tty(aka. stdin) N'est généralement pas une bonne source, car il est fermé lorsque l'utilisateur se déconnecte. En outre, il pourrait voler une contribution du tty. Pas gentil.

Pour faire un readbloc, nous devons attendre quelque chose comme un fifoqui ne retournera jamais rien. En bash 4il y a une commande qui peut exactement nous fournir un tel fifo: coproc. Si nous attendons également le blocage read(qui est le nôtre coproc), nous avons terminé. Malheureusement, cela doit garder ouverts deux PID et un fichier fifo.

Variante avec un nommé fifo

Si vous ne vous embêtez pas à utiliser un nommé fifo, vous pouvez le faire comme suit:

mkfifo "$HOME/.pause.fifo" 2>/dev/null; read <"$HOME/.pause.fifo"

Ne pas utiliser de boucle sur la lecture est un peu bâclé, mais vous pouvez le réutiliser fifoaussi souvent que vous le souhaitez et faire le reads terminat en utilisant touch "$HOME/.pause.fifo"(s'il y a plus d'une lecture en attente, tout est terminé en même temps).

Ou utilisez Linux pause()syscall

Pour le blocage infini, il y a un appel du noyau Linux, appelé pause(), qui fait ce que nous voulons: Attendre indéfiniment (jusqu'à ce qu'un signal arrive). Cependant, il n'y a pas (encore) de programme d'espace utilisateur pour cela.

C

Créer un tel programme est facile. Voici un extrait de code pour créer un tout petit programme Linux appelé pausequi s'interrompt indéfiniment (besoins diet, gccetc.):

printf '#include <unistd.h>\nint main(){for(;;)pause();}' > pause.c;
diet -Os cc pause.c -o pause;
strip -s pause;
ls -al pause

python

Si vous ne voulez pas compiler quelque chose vous-même, mais que vous l'avez pythoninstallé, vous pouvez l'utiliser sous Linux:

python -c 'while 1: import ctypes; ctypes.CDLL(None).pause()'

(Remarque: Utilisez exec python -c ...pour remplacer le shell actuel, cela libère un PID. La solution peut être améliorée avec une redirection d'E / S également, en libérant les FD inutilisées. C'est à vous de décider.)

Comment cela fonctionne (je pense): ctypes.CDLL(None)charge la bibliothèque C standard et exécute la pause()fonction qu'elle contient dans une boucle supplémentaire. Moins efficace que la version C, mais fonctionne.

Ma recommandation pour vous:

Restez au sommeil en boucle. Il est facile à comprendre, très portable et bloque la plupart du temps.

Tino
la source
1
@Andrew Normalement, vous n'avez pas besoin du trap(qui modifie le comportement du shell en signaux) ni du fond (qui permet au shell d'intercepter les signaux du terminal, comme Strg + C). Donc sleep infinityc'est assez (se comporte comme exec sleep infinitysi c'était la dernière instruction. Pour voir la différence utiliser strace -ffDI4 bash -c 'YOURCODEHERE'). Le sommeil en boucle est meilleur, car sleeppeut revenir dans certaines circonstances. Par exemple, vous ne voulez pas que X11 s'arrête soudainement sur a killall sleep, simplement parce qu'il .xstartupse termine par au sleep infinitylieu d'une boucle de sommeil.
Tino
Peut-être un peu obscur, mais s6-pausec'est une commande utilisateur à exécuter pause(), en ignorant éventuellement divers signaux.
Patrick
@Tino /bin/sleepn'est pas plafonné à 24 jours comme vous le dites. Ce serait bien si vous pouviez mettre à jour cela. Sur Linux en ce moment, ce code est actif. Il limite les nanosleep()appels système individuels à 24 jours, mais les appelle en boucle. sleep infinityNe devrait donc pas quitter après 24 jours. L' doubleinfini positif est converti en a struct timespec. En regardant rpl_nanosleepdans GDB, infinityest converti { tv_sec = 9223372036854775807, tv_nsec = 999999999 }sur Ubuntu 16.04.
nh2
@ nh2 Il était déjà mentionné dans le texte que le sommeil fait probablement des boucles au lieu d'être complètement bloquant. Je l'ai maintenant légèrement modifié pour clarifier ce fait, espérons-le. Veuillez noter ceci " probablement ", car à lui straceseul, je ne peux pas prouver le fait qu'il y a vraiment du code en boucle compilé sleep, et je ne veux pas attendre 24 jours juste pour tester cela (ou décompiler /bin/sleep). Il est toujours préférable de programmer de manière défensive, s'il n'y a pas de preuve mathématique solide, que quelque chose est vraiment, tel qu'il semble être. Ne faites jamais confiance à rien non plus:killall -9 sleep
Tino
L'option pause () peut être faite assez facilement avec perl: perl -MPOSIX -e 'pause ()'
tgoodhart
70

Peut-être que cela semble moche, mais pourquoi ne pas simplement courir catet le laisser attendre pour toujours?

Michał Trybus
la source
4
Cela ne fonctionne pas si vous n'avez pas de tuyau suspendu à partir duquel lire. S'il vous plaît donnez votre avis.
Matt Joiner
2
@Matt, peut-être faire une pipe et catça? mkfifo pipe && cat pipe
Michał Trybus
Ce que dit @twalberg, mais en plus, vous pouvez immédiatement réaffecter à 3 et le dissocier, comme indiqué ici: superuser.com/a/633185/762481
jp48
32

TL; DR: sleep infinitydort en fait le temps maximum autorisé, qui est fini.

Me demandant pourquoi cela n'est documenté nulle part, j'ai pris la peine de lire les sources de GNU coreutils et j'ai trouvé qu'il exécute à peu près ce qui suit:

  1. Utilisez strtodfrom C stdlib sur le premier argument pour convertir 'infini' en double précision. Ainsi, en supposant la double précision IEEE 754, la valeur d' infini positive de 64 bits est stockée dans la secondsvariable.
  2. Invoke xnanosleep(seconds)( trouvé dans gnulib ), ceci à son tour invoque dtotimespec(seconds)( également dans gnulib ) pour convertir de doubleen struct timespec.
  3. struct timespecest juste une paire de nombres: partie entière (en secondes) et partie fractionnaire (en nanosecondes). Naïvement convertir l' infini positif en entier entraînerait un comportement indéfini (voir §6.3.1.4 du standard C), donc à la place, il tronque en TYPE_MAXIMUM (time_t).
  4. La valeur réelle de TYPE_MAXIMUM (time_t)n'est pas définie dans la norme (même sizeof(time_t)pas); Donc, à titre d'exemple, choisissons x86-64 à partir d'un noyau Linux récent.

C'est TIME_T_MAXdans le noyau Linux, qui est défini ( time.h) comme:

(time_t)((1UL << ((sizeof(time_t) << 3) - 1)) - 1)

Notez que time_test __kernel_time_tet time_test long; le modèle de données LP64 est utilisé, de même que sizeof(long)8 (64 bits).

Quels sont les résultats dans: TIME_T_MAX = 9223372036854775807.

Autrement dit: sleep infinitedonne un temps de sommeil réel de 9223372036854775807 secondes (10 ^ 11 ans). Et pour les systèmes Linux 32 bits ( sizeof(long)soit 4 (32 bits)): 2147483647 secondes (68 ans; voir aussi le problème de l'année 2038 ).


Edit : apparemment, la nanosecondsfonction appelée n'est pas directement l'appel système, mais un wrapper dépendant du système d'exploitation (également défini dans gnulib ).

Il y a une étape supplémentaire en raison: pour certains systèmes où HAVE_BUG_BIG_NANOSLEEPest truele sommeil est tronqué à 24 jours, puis appelé dans une boucle. C'est le cas pour certaines (ou toutes?) Distributions Linux. Notez que ce wrapper peut ne pas être utilisé si un test configure -time réussit ( source ).

En particulier, ce serait 24 * 24 * 60 * 60 = 2073600 seconds(plus 999999999 nanosecondes); mais ceci est appelé dans une boucle afin de respecter le temps de sommeil total spécifié. Par conséquent, les conclusions précédentes restent valables.


En conclusion, le temps de sommeil qui en résulte n'est pas infini mais suffisamment élevé à toutes fins pratiques , même si le laps de temps réel qui en résulte n'est pas portable; cela dépend du système d'exploitation et de l'architecture.

Pour répondre à la question initiale, c'est évidemment suffisant, mais si pour une raison quelconque (un système très limité en ressources) vous voulez vraiment éviter un compte à rebours supplémentaire inutile, je suppose que l'alternative la plus correcte est d'utiliser la catméthode décrite dans d'autres réponses .

jp48
la source
1
Dans le prochain coreutils, sleep infinityva maintenant dormir pour toujours sans boucle: lists.gnu.org/archive/html/bug-gnulib/2020-02/msg00081.html
Vladimir Panteleev le
8

sleep infinitysemble le plus élégant, mais parfois cela ne fonctionne pas pour une raison quelconque. Dans ce cas, vous pouvez essayer d' autres commandes de blocage telles que cat, read, tail -f /dev/null, grep aetc.

Hui Zheng
la source
1
tail -f /dev/nullétait également une solution de travail pour moi sur une plate
schmunk
2
tail -f /dev/nulla également l'avantage de ne pas consommer stdin. Je l'ai utilisé pour cette raison.
Sudo Bash le
Ceux qui envisagent cette option devraient lire cette réponse pour en savoir plus sur les ramifications de cette option.
Shadow
6

Qu'en est-il de l'envoi d'un SIGSTOP à lui-même?

Cela devrait interrompre le processus jusqu'à ce que SIGCONT soit reçu. Ce qui est dans votre cas: jamais.

kill -STOP "$$";
# grace time for signal delivery
sleep 60;
Michuelnik
la source
6
Les signaux sont asynchrones. Ainsi, ce qui suit peut arriver: a) le shell appelle kill b) kill indique au noyau que le shell recevra le signal STOP c) kill se termine et retourne au shell d) le shell continue (peut-être se termine parce que le script se termine) e) le noyau trouve enfin le temps de livrer signaler STOP au shell
not-a-user
1
@temple Grande perspicacité, n'a pas pensé à la nature asynchrone des signaux. Merci!
michuelnik
4

Laissez-moi vous expliquer pourquoi sleep infinityfonctionne bien qu'il ne soit pas documenté. La réponse de jp48 est également utile.

Le plus important: en spécifiant infou infinity(tous deux insensibles à la casse), vous pouvez dormir le plus longtemps que votre implémentation le permet (c'est-à-dire la valeur la plus petite de HUGE_VALet TYPE_MAXIMUM(time_t)).

Passons maintenant aux détails. Le code source de la sleepcommande peut être lu depuis coreutils / src / sleep.c . Essentiellement, la fonction fait ceci:

double s; //seconds
xstrtod (argv[i], &p, &s, cl_strtod); //`p` is not essential (just used for error check).
xnanosleep (s);

Compréhension xstrtod (argv[i], &p, &s, cl_strtod)

xstrtod()

Selon gnulib / lib / xstrtod.c , l'appel de xstrtod()convertit la chaîne argv[i]en une valeur à virgule flottante et la stocke dans *s, en utilisant une fonction de conversion cl_strtod().

cl_strtod()

Comme on peut le voir dans coreutils / lib / cl-strtod.c , cl_strtod()convertit une chaîne en une valeur à virgule flottante, en utilisant strtod().

strtod()

Selon man 3 strtod, strtod()convertit une chaîne en une valeur de type double. La page de manuel dit

La forme attendue de la (partie initiale de la) chaîne est ... ou (iii) une infinité, ou ...

et une infinité est définie comme

Un infini est soit "INF" soit "INFINITY", sans tenir compte de la casse.

Bien que le document dise

Si la valeur correcte entraînerait un débordement, plus ou moins HUGE_VAL( HUGE_VALF, HUGE_VALL) est renvoyé

, on ne sait pas comment un infini est traité. Voyons donc le code source gnulib / lib / strtod.c . Ce que nous voulons lire c'est

else if (c_tolower (*s) == 'i'
         && c_tolower (s[1]) == 'n'
         && c_tolower (s[2]) == 'f')
  {
    s += 3;
    if (c_tolower (*s) == 'i'
        && c_tolower (s[1]) == 'n'
        && c_tolower (s[2]) == 'i'
        && c_tolower (s[3]) == 't'
        && c_tolower (s[4]) == 'y')
      s += 5;
    num = HUGE_VAL;
    errno = saved_errno;
  }

Ainsi, INFet INFINITY(tous deux insensibles à la casse) sont considérés comme HUGE_VAL.

HUGE_VAL famille

Utilisons N1570 comme norme C. HUGE_VAL, HUGE_VALFet les HUGE_VALLmacros sont définies au §7.12-3

La macro se
    HUGE_VAL
développe en une expression à double constante positive, pas nécessairement représentable comme un flottant. Les macros
    HUGE_VALF
    HUGE_VALL
sont respectivement float et long double analogues de HUGE_VAL.

HUGE_VAL, HUGE_VALFet HUGE_VALLpeut être des infinis positifs dans une implémentation qui prend en charge les infinis.

et au §7.12.1-5

Si un résultat flottant arrondi et déborde par défaut est en vigueur, la fonction retourne la valeur de la macro HUGE_VAL, HUGE_VALFou en HUGE_VALLfonction du type de retour

Compréhension xnanosleep (s)

Maintenant, nous comprenons toute l'essence de xstrtod(). D'après les explications ci-dessus, il est clair que xnanosleep(s)nous avons vu d'abord les moyens xnanosleep(HUGE_VALL).

xnanosleep()

Selon le code source gnulib / lib / xnanosleep.c , fait xnanosleep(s)essentiellement ceci:

struct timespec ts_sleep = dtotimespec (s);
nanosleep (&ts_sleep, NULL);

dtotimespec()

Cette fonction convertit un argument de type doubleen un objet de type struct timespec. Comme c'est très simple, permettez-moi de citer le code source gnulib / lib / dtotimespec.c . Tous les commentaires sont ajoutés par moi.

struct timespec
dtotimespec (double sec)
{
  if (! (TYPE_MINIMUM (time_t) < sec)) //underflow case
    return make_timespec (TYPE_MINIMUM (time_t), 0);
  else if (! (sec < 1.0 + TYPE_MAXIMUM (time_t))) //overflow case
    return make_timespec (TYPE_MAXIMUM (time_t), TIMESPEC_HZ - 1);
  else //normal case (looks complex but does nothing technical)
    {
      time_t s = sec;
      double frac = TIMESPEC_HZ * (sec - s);
      long ns = frac;
      ns += ns < frac;
      s += ns / TIMESPEC_HZ;
      ns %= TIMESPEC_HZ;

      if (ns < 0)
        {
          s--;
          ns += TIMESPEC_HZ;
        }

      return make_timespec (s, ns);
    }
}

Comme il time_test défini comme un type intégral (voir §7.27.1-3), il est naturel que nous supposions que la valeur maximale de type time_test inférieure à HUGE_VAL(de type double), ce qui signifie que nous entrons dans le cas de débordement. (En fait, cette hypothèse n'est pas nécessaire puisque, dans tous les cas, la procédure est essentiellement la même.)

make_timespec()

Le dernier mur que nous devons escalader est make_timespec(). Heureusement, c'est tellement simple que de citer le code source gnulib / lib / timespec.h suffit.

_GL_TIMESPEC_INLINE struct timespec
make_timespec (time_t s, long int ns)
{
  struct timespec r;
  r.tv_sec = s;
  r.tv_nsec = ns;
  return r;
}
ynn
la source
2

J'ai récemment eu besoin de le faire. J'ai mis au point la fonction suivante qui permettra à bash de dormir pour toujours sans appeler de programme externe:

snore()
{
    local IFS
    [[ -n "${_snore_fd:-}" ]] || { exec {_snore_fd}<> <(:); } 2>/dev/null ||
    {
        # workaround for MacOS and similar systems
        local fifo
        fifo=$(mktemp -u)
        mkfifo -m 700 "$fifo"
        exec {_snore_fd}<>"$fifo"
        rm "$fifo"
    }
    read ${1:+-t "$1"} -u $_snore_fd || :
}

REMARQUE: J'ai déjà publié une version de ceci qui ouvrirait et fermait le descripteur de fichier à chaque fois, mais j'ai trouvé que sur certains systèmes, faire cela des centaines de fois par seconde finirait par se bloquer. Ainsi, la nouvelle solution conserve le descripteur de fichier entre les appels à la fonction. Bash le nettoiera quand même à la sortie.

Cela peut être appelé comme / bin / sleep, et il dormira pendant le temps demandé. Appelé sans paramètres, il se bloquera pour toujours.

snore 0.1  # sleeps for 0.1 seconds
snore 10   # sleeps for 10 seconds
snore      # sleeps forever

Il y a un article avec des détails excessifs sur mon blog ici

boulon
la source
1

Cette approche ne consommera aucune ressource pour maintenir le processus en vie.

while :; do sleep 1; done & kill -STOP $! && wait $!

Panne

  • while :; do sleep 1; done & Crée un processus factice en arrière-plan
  • kill -STOP $! Arrête le processus d'arrière-plan
  • wait $! Attendez le processus d'arrière-plan, cela bloquera pour toujours, car le processus d'arrière-plan a été arrêté avant
qoomon
la source
0

Au lieu de tuer le gestionnaire de fenêtres, essayez d'exécuter le nouveau avec --replaceou -replacesi disponible.

Suspendu jusqu'à nouvel ordre.
la source
1
Si j'utilise, --replaceje reçois toujours un avertissement comme another window manager is already running. Cela n'a pas beaucoup de sens pour moi.
watain
-2
while :; do read; done

pas d'attente pour le processus de sommeil de l'enfant.

shuaiming
la source
1
Cela mange stdinsi cela se trouve toujours connecté au tty. Si vous l'exécutez avec < /dev/nulldes boucles occupées. Cela pourrait être utile dans certaines situations, je ne suis donc pas contre.
Tino
1
C'est une très mauvaise idée, cela ne consommera que tout le CPU.
Mohammed Noureldin