Bash: unendlicher Schlaf (unendliches Blockieren)

158

Ich benutze startx, um X zu starten, das meine auswertet .xinitrc. In meinem starte .xinitrcich meinen Fenstermanager mit /usr/bin/mywm. Wenn ich jetzt meine WM töte (um beispielsweise eine andere WM zu testen), wird X ebenfalls beendet, da das .xinitrcSkript EOF erreicht hat. Also habe ich dies am Ende meiner .xinitrc:

while true; do sleep 10000; done

Auf diese Weise wird X nicht beendet, wenn ich meine WM töte. Nun meine Frage: Wie kann ich einen unendlichen Schlaf machen, anstatt den Schlaf zu schleifen? Gibt es einen Befehl, mit dem das Skript eingefroren werden kann?

Freundliche Grüße

Watain
quelle

Antworten:

330

sleep infinity tut genau das, was es vorschlägt und funktioniert ohne Katzenmissbrauch.

Donarsson
quelle
16
Cool. Leider versteht meine Busybox nicht.
Nicht-Benutzer
12
BSD (oder zumindest OS X) versteht das auch nicht sleep infinity, obwohl es eine coole Sache war, etwas über Linux zu lernen. Sollte while true; do sleep 86400; donejedoch ein adäquater Ersatz sein.
Ivan X
16
Diesbezüglich habe ich einige Nachforschungen angestellt, die ich in einer separaten Antwort dokumentiert habe. Zusammenfassend: infinitywird in C von "string" in a konvertiert double. Dann doublewird das auf die maximal zulässigen Werte gekürzt timespec, was eine sehr große Anzahl von Sekunden (architekturabhängig) bedeutet, aber theoretisch endlich.
jp48
72

tail blockiert nicht

Wie immer: Für alles gibt es eine Antwort, die kurz, leicht verständlich, leicht zu befolgen und völlig falsch ist. Hier tail -f /dev/nullfällt in diese Kategorie;)

Wenn Sie es mit betrachten, werden strace tail -f /dev/nullSie feststellen, dass diese Lösung weit davon entfernt ist, zu blockieren! Es ist wahrscheinlich noch schlimmer als die sleepLösung in der Frage, da es (unter Linux) wertvolle Ressourcen wie das inotifySystem verwendet. Auch andere Prozesse, die schreiben, /dev/nullum tailSchleife zu machen . (Auf meinem Ubuntu64 16.10 werden auf einem bereits ausgelasteten System mehrere 10 Syscalls pro Sekunde hinzugefügt.)

Die Frage war nach einem Blockierungsbefehl

Leider gibt es so etwas nicht ..

Lesen Sie: Ich kenne keine Möglichkeit, dies direkt mit der Shell zu archivieren.

Alles (gerade sleep infinity) kann durch ein Signal unterbrochen werden. Wenn Sie also wirklich sicher sein möchten, dass es nicht ausnahmsweise zurückkehrt, muss es in einer Schleife ausgeführt werden, wie Sie es bereits für Ihre getan haben sleep. Bitte beachten Sie, dass (unter Linux) /bin/sleepanscheinend auf 24 Tage begrenzt ist (siehe strace sleep infinity). Daher ist das Beste, was Sie wahrscheinlich tun können, Folgendes:

while :; do sleep 2073600; done

(Beachten Sie, dass ich glaube, dass sleepSchleifen intern für höhere Werte als 24 Tage ausgeführt werden. Dies bedeutet jedoch: Sie blockieren nicht, sie werden sehr langsam wiederholt. Warum also nicht diese Schleife nach außen verschieben?)

.. aber Sie können mit einem unbenannten ganz nah kommen fifo

Sie können etwas erstellen, das wirklich blockiert, solange keine Signale an den Prozess gesendet werden. Folgende Verwendungen bash 4, 2 PIDs und 1 fifo:

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

Sie können überprüfen, ob dies wirklich blockiert, stracewenn Sie möchten:

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

Wie das aufgebaut war

readblockiert, wenn keine Eingabedaten vorhanden sind (siehe einige andere Antworten). Das tty(aka. stdin) Ist jedoch normalerweise keine gute Quelle, da es geschlossen wird, wenn sich der Benutzer abmeldet. Es könnte auch einige Eingaben von der stehlen tty. Nicht nett.

Um zu readblockieren, müssen wir auf so etwas wie ein warten, fifodas niemals etwas zurückgibt. Darin bash 4befindet sich ein Befehl, der uns genau Folgendes liefern kann fifo: coproc. Wenn wir auch auf die Blockierung warten read(was unsere ist coproc), sind wir fertig. Leider muss dies zwei PIDs und eine offen halten fifo.

Variante mit einem Namen fifo

Wenn Sie sich nicht die Mühe machen, einen Namen zu verwenden fifo, können Sie dies wie folgt tun:

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

Das Nicht-Verwenden einer Schleife beim Lesen ist etwas schlampig, aber Sie können dies fifoso oft wiederverwenden, wie Sie möchten, und die reads-Terminierung verwenden touch "$HOME/.pause.fifo"(wenn mehr als ein einziger Lesevorgang wartet, werden alle auf einmal beendet).

Oder verwenden Sie den Linux- pause()Systemaufruf

Für die unendliche Blockierung gibt es einen Linux-Kernel-Aufruf namens pause(), der das tut, was wir wollen: Warten Sie für immer (bis ein Signal eintrifft). Es gibt jedoch (noch) kein Userspace-Programm dafür.

C.

Ein solches Programm zu erstellen ist einfach. Hier ist ein Ausschnitt ein sehr kleines Linux - Programm aufgerufen zu erstellen , pausedie auf unbestimmte Zeit pausiert (Bedürfnisse 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

Wenn Sie etwas nicht selbst kompilieren möchten, aber pythoninstalliert haben, können Sie dies unter Linux verwenden:

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

(Hinweis: exec python -c ...Zum Ersetzen der aktuellen Shell wird eine PID freigegeben. Die Lösung kann auch durch eine E / A-Umleitung verbessert werden, wodurch nicht verwendete FDs freigegeben werden. Dies liegt bei Ihnen.)

So funktioniert das (glaube ich): ctypes.CDLL(None)Lädt die Standard-C-Bibliothek und führt die darin enthaltene pause()Funktion in einer zusätzlichen Schleife aus. Weniger effizient als die C-Version, funktioniert aber.

Meine Empfehlung für Sie:

Bleib im Schlaf. Es ist leicht zu verstehen, sehr portabel und blockiert die meiste Zeit.

Tino
quelle
1
@ Andrew Normalerweise benötigen Sie weder den trap(der das Verhalten der Shell in Signale umwandelt) noch den Hintergrund (der es der Shell ermöglicht, Signale vom Terminal abzufangen, wie Strg + C). Also sleep infinityist genug (verhält sich wie exec sleep infinitywenn es die letzte Aussage ist. Um den Unterschied zu sehen strace -ffDI4 bash -c 'YOURCODEHERE'). Der Schleifenschlaf ist besser, da er sleepunter bestimmten Umständen zurückkehren kann. Zum Beispiel möchten Sie nicht, dass X11 auf einem plötzlich heruntergefahren wird killall sleep, nur weil es anstelle einer Schlafschleife .xstartupendet sleep infinity.
Tino
Mag ein wenig dunkel sein, ist aber s6-pauseein Userland-Befehl zum Ausführen pause(), der optional verschiedene Signale ignoriert.
Patrick
@Tino /bin/sleepist nicht auf 24 Tage begrenzt, wie Sie sagen. Es wäre schön, wenn Sie das aktualisieren könnten. Unter Linux ist dieser Code derzeit aktiv. Es begrenzt einzelne nanosleep()Systemaufrufe auf 24 Tage, ruft sie jedoch in einer Schleife auf. Sollte also sleep infinitynicht nach 24 Tagen beendet werden. Die doublepositive Unendlichkeit wird in a umgewandelt struct timespec. Betrachtet man rpl_nanosleepin GDB, infinitywird { tv_sec = 9223372036854775807, tv_nsec = 999999999 }auf Ubuntu 16.04 konvertiert .
nh2
@ nh2 Es wurde bereits im Text erwähnt, dass der Schlaf wahrscheinlich Schleifen bildet, anstatt vollständig zu blockieren. Ich habe es jetzt leicht bearbeitet, um diese Tatsache hoffentlich etwas klarer zu machen. Bitte beachten Sie dies " wahrscheinlich ", da straceich allein nicht beweisen kann, dass wirklich ein Schleifencode kompiliert ist sleep, und ich nicht 24 Tage warten möchte, nur um dies zu testen (oder zu dekompilieren /bin/sleep). Es ist immer besser, defensiv zu programmieren, wenn es keinen harten mathematischen Beweis gibt, dass etwas wirklich so ist, wie es scheint. Vertraue auch nie etwas:killall -9 sleep
Tino
Die Option pause () kann ziemlich einfach mit perl ausgeführt werden: perl -MPOSIX -e 'pause ()'
tgoodhart
70

Vielleicht scheint das hässlich, aber warum nicht einfach rennen catund es für immer auf Eingaben warten lassen?

Michał Trybus
quelle
4
Dies funktioniert nicht, wenn Sie kein hängendes Rohr zum Lesen haben. Bitte beraten.
Matt Joiner
2
@ Matt, vielleicht eine Pfeife machen und cates? mkfifo pipe && cat pipe
Michał Trybus
Was @twalberg sagt, sondern zusätzlich können Sie bis 3 und entkoppeln es sofort neu zuzuweisen, wie hier gezeigt: superuser.com/a/633185/762481
jp48
32

TL; DR: sleep infinityschläft tatsächlich die maximal zulässige Zeit, die endlich ist.

Als ich mich fragte, warum dies nirgendwo dokumentiert ist, machte ich mir die Mühe, die Quellen von GNU-Coreutils zu lesen, und stellte fest, dass es ungefähr Folgendes ausführt:

  1. Verwenden Sie strtodC stdlib für das erste Argument, um 'unendlich' in die doppelte Genauigkeit umzuwandeln. Unter der Annahme einer doppelten Genauigkeit von IEEE 754 wird der positive 64-Bit- Unendlichkeitswert in der secondsVariablen gespeichert .
  2. Invoke xnanosleep(seconds)( gefunden in gnulib ), dies ruft wiederum dtotimespec(seconds)( auch in gnulib ) auf, um von doublezu konvertieren struct timespec.
  3. struct timespecist nur ein Zahlenpaar: ganzzahliger Teil (in Sekunden) und gebrochener Teil (in Nanosekunden). Die naive Umwandlung der positiven Unendlichkeit in eine Ganzzahl würde zu einem undefinierten Verhalten führen (siehe §6.3.1.4 aus dem C-Standard), daher wird es stattdessen auf abgeschnitten TYPE_MAXIMUM (time_t).
  4. Der tatsächliche Wert von TYPE_MAXIMUM (time_t)ist nicht im Standard festgelegt (auch sizeof(time_t)nicht). Lassen Sie uns zum Beispiel x86-64 aus einem aktuellen Linux-Kernel auswählen.

Dies ist TIME_T_MAXim Linux-Kernel, der wie folgt definiert ist time.h:

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

Beachten Sie, dass time_tist __kernel_time_tund time_tist long; Das LP64-Datenmodell wird verwendet, also sizeof(long)8 (64 Bit).

Was zu : TIME_T_MAX = 9223372036854775807.

Das heißt: sleep infiniteergibt eine tatsächliche Schlafzeit von 9223372036854775807 Sekunden (10 ^ 11 Jahre). Und für 32-Bit-Linux-Systeme ( sizeof(long)4 (32 Bit)): 2147483647 Sekunden (68 Jahre; siehe auch Problem Jahr 2038 ).


Bearbeiten : Anscheinend ist die nanosecondsaufgerufene Funktion nicht direkt der Syscall, sondern ein OS-abhängiger Wrapper (auch in gnulib definiert ).

Es gibt einen zusätzlichen Schritt als Ergebnis: Bei einigen Systemen , wo HAVE_BUG_BIG_NANOSLEEPist trueder Schlaf wird abgeschnitten , bis 24 Tage und dann in einer Schleife aufgerufen. Dies ist bei einigen (oder allen?) Linux-Distributionen der Fall. Beachten Sie, dass dieser Wrapper möglicherweise nicht verwendet wird, wenn ein Test zur Konfigurationszeit erfolgreich ist ( Quelle ).

Dies wäre insbesondere 24 * 24 * 60 * 60 = 2073600 seconds(plus 999999999 Nanosekunden); Dies wird jedoch in einer Schleife aufgerufen, um die angegebene Gesamtschlafzeit zu berücksichtigen. Daher bleiben die bisherigen Schlussfolgerungen gültig.


Zusammenfassend ist die resultierende Schlafzeit nicht unendlich, sondern für alle praktischen Zwecke hoch genug , selbst wenn der resultierende tatsächliche Zeitraffer nicht tragbar ist; Das hängt vom Betriebssystem und der Architektur ab.

Um die ursprüngliche Frage zu beantworten, ist dies natürlich gut genug, aber wenn Sie aus irgendeinem Grund (einem sehr ressourcenbeschränkten System) wirklich einen nutzlosen zusätzlichen Countdown-Timer vermeiden möchten, ist es wahrscheinlich die richtigste Alternative, die catin anderen Antworten beschriebene Methode zu verwenden .

jp48
quelle
1
In den nächsten Coreutils sleep infinitywird nun tatsächlich für immer ohne Schleifen schlafen: lists.gnu.org/archive/html/bug-gnulib/2020-02/msg00081.html
Vladimir Panteleev
8

sleep infinitysieht am elegantesten aus, aber manchmal funktioniert es aus irgendeinem Grund nicht. In diesem Fall können Sie andere Sperrbefehle versuchen wie cat, read, tail -f /dev/null, grep ausw.

Hui Zheng
quelle
1
tail -f /dev/nullwar auch eine funktionierende Lösung für mich auf einer SaaS-Plattform
schmunk
2
tail -f /dev/nullhat auch den vorteil, stdin nicht zu konsumieren. Ich habe es aus diesem Grund benutzt.
Sudo Bash
Diejenigen, die diese Option in Betracht ziehen, sollten diese Antwort lesen , um mehr über die Auswirkungen dieser Option zu erfahren.
Schatten
6

Was ist mit dem Senden eines SIGSTOP an sich selbst?

Dies sollte den Vorgang anhalten, bis SIGCONT empfangen wird. Welches ist in Ihrem Fall: nie.

kill -STOP "$$";
# grace time for signal delivery
sleep 60;
michuelnik
quelle
6
Die Signale sind asynchron. Folgendes kann also passieren: a) Shell ruft kill auf b) kill teilt dem Kernel mit, dass die Shell das Signal STOP empfangen soll c) kill wird beendet und kehrt zur Shell zurück d) Shell wird fortgesetzt (möglicherweise beendet, weil das Skript endet) e) Kernel findet endlich die Zeit für die Zustellung Signal STOP to Shell
kein Benutzer
1
@temple Großartige Einsicht, habe nicht über die Asynchronität von Signalen nachgedacht. Vielen Dank!
michuelnik
4

Lassen Sie mich erklären, warum dies sleep infinityfunktioniert, obwohl es nicht dokumentiert ist. jp48s antwort ist ebenfalls nützlich.

Das Wichtigste: Durch Angabe von infoder infinity(beide ohne Berücksichtigung der Groß- und Kleinschreibung) können Sie so lange schlafen, wie es Ihre Implementierung zulässt (dh den kleineren Wert von HUGE_VALund TYPE_MAXIMUM(time_t)).

Lassen Sie uns nun die Details untersuchen. Der Quellbefehlscodesleep kann aus coreutils / src / sleep.c gelesen werden . Im Wesentlichen macht die Funktion dies:

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

Verstehen xstrtod (argv[i], &p, &s, cl_strtod)

xstrtod()

Laut gnulib / lib / xstrtod.cxstrtod() konvertiert der Aufruf von string argv[i]in einen Gleitkommawert und speichert ihn *smithilfe einer Konvertierungsfunktion in cl_strtod().

cl_strtod()

Wie aus coreutils / lib / cl-strtod.c ersichtlich , cl_strtod()wird eine Zeichenfolge mithilfe von in einen Gleitkommawert konvertiert strtod().

strtod()

Entsprechend man 3 strtod, strtod()wandelt eine Zeichenkette auf einen Wert von Typ double. Die Manpage sagt

Die erwartete Form des (anfänglichen Teils der) Zeichenfolge ist ... oder (iii) eine Unendlichkeit oder ...

und eine Unendlichkeit ist definiert als

Eine Unendlichkeit ist entweder "INF" oder "INFINITY", unabhängig vom Fall.

Obwohl das Dokument erzählt

Wenn der richtige Wert einen Überlauf verursachen würde, wird plus oder minus HUGE_VAL( HUGE_VALF, HUGE_VALL) zurückgegeben

Es ist nicht klar, wie eine Unendlichkeit behandelt wird. Sehen wir uns also den Quellcode gnulib / lib / strtod.c an . Was wir lesen wollen, ist

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;
  }

Somit werden INFund INFINITY(beide ohne Berücksichtigung der Groß- und Kleinschreibung) als betrachtet HUGE_VAL.

HUGE_VAL Familie

Verwenden wir N1570 als C-Standard. HUGE_VAL, HUGE_VALFUnd HUGE_VALLsind Makros definiert in §7.12-3

Das Makro wird
    HUGE_VAL
zu einem positiven Ausdruck mit doppelter Konstante erweitert, der nicht unbedingt als Float dargestellt werden kann. Die Makros
    HUGE_VALF
    HUGE_VALL
sind jeweils Float- und Long-Double-Analoga von HUGE_VAL.

HUGE_VAL, HUGE_VALFUnd HUGE_VALLkann positiver infinities in einer Implementierung , die Abstützungen infinities sein.

und in §7.12.1-5

Wenn ein schwimmendes Ergebnis überläuft und Standardrundungs in Kraft ist, dann gibt der Funktion den Wert des Makros HUGE_VAL, HUGE_VALFoder HUGE_VALLnach dem Rückgabetyp

Verstehen xnanosleep (s)

Jetzt verstehen wir alles Wesentliche von xstrtod(). Aus den obigen Erklärungen ist kristallklar, dass xnanosleep(s)wir zuerst gesehen haben, was tatsächlich bedeutet xnanosleep(HUGE_VALL).

xnanosleep()

Nach dem Quellcode gnulib / lib / xnanosleep.c , xnanosleep(s)tut im Wesentlichen diese:

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

dtotimespec()

Diese Funktion konvertiert ein Argument vom Typ doublein ein Objekt vom Typ struct timespec. Da es sehr einfach ist, möchte ich den Quellcode gnulib / lib / dtotimespec.c zitieren . Alle Kommentare wurden von mir hinzugefügt.

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);
    }
}

Da time_tes sich um einen integralen Typ handelt (siehe §7.27.1-3), nehmen wir natürlich an, dass der Maximalwert des Typs time_tkleiner als HUGE_VAL(des Typs double) ist, was bedeutet, dass wir in den Überlauffall eintreten. (Diese Annahme ist eigentlich nicht erforderlich, da das Verfahren in allen Fällen im Wesentlichen dasselbe ist.)

make_timespec()

Die letzte Wand, die wir erklimmen müssen, ist make_timespec(). Zum Glück ist es so einfach, dass das Zitieren des Quellcodes gnulib / lib / timespec.h ausreicht.

_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
quelle
2

Ich hatte vor kurzem die Notwendigkeit, dies zu tun. Ich habe die folgende Funktion entwickelt, mit der bash für immer schlafen kann, ohne ein externes Programm aufzurufen:

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 || :
}

ANMERKUNG: Ich habe zuvor eine Version davon veröffentlicht, die den Dateideskriptor jedes Mal öffnet und schließt, aber ich habe festgestellt, dass auf einigen Systemen, die dies hunderte Male pro Sekunde tun, irgendwann blockiert wird. Somit behält die neue Lösung den Dateideskriptor zwischen den Aufrufen der Funktion bei. Bash wird es sowieso beim Verlassen aufräumen.

Dies kann genau wie / bin / sleep aufgerufen werden und wird für die angeforderte Zeit schlafen. Ohne Parameter aufgerufen, bleibt es für immer hängen.

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

Es gibt eine Beschreibung mit übermäßigen Details in meinem Blog hier

Bolzen
quelle
1

Dieser Ansatz verbraucht keine Ressourcen, um den Prozess am Leben zu erhalten.

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

Nervenzusammenbruch

  • while :; do sleep 1; done & Erstellt einen Dummy-Prozess im Hintergrund
  • kill -STOP $! Stoppt den Hintergrundprozess
  • wait $! Warten Sie auf den Hintergrundprozess, dieser wird für immer blockiert, da der Hintergrundprozess zuvor gestoppt wurde
Qoomon
quelle
0

Versuchen Sie, den neuen Manager mit --replaceoder, -replacefalls verfügbar , auszuführen, anstatt den Fenstermanager zu beenden .

Bis auf weiteres angehalten.
quelle
1
Wenn ich benutze, --replacebekomme ich immer eine Warnung wie another window manager is already running. Das macht für mich aber nicht viel Sinn.
Watain
-2
while :; do read; done

Kein Warten auf den Schlafprozess des Kindes.

shuaiming
quelle
1
Dies frisst, stdinwenn dies noch mit dem verbunden ist tty. Wenn Sie es mit < /dev/nullBusy-Loops ausführen . Es kann in bestimmten Situationen von Nutzen sein, daher stimme ich nicht ab.
Tino
1
Dies ist eine sehr schlechte Idee, es wird nur viel CPU verbrauchen.
Mohammed Noureldin