Vermeiden Sie es, ohne den Befehl sleep in der Bash zu warten

19

Ich weiß, dass ich warten kann, bis die Bedingung erfüllt ist, indem ich Folgendes tue:

while true; do
  test_condition && break
  sleep 1
done

Es wird jedoch bei jeder Iteration (Ruhezustand) ein Unterprozess erstellt. Ich könnte sie vermeiden, indem ich tue:

while true; do
  test_condition && break
done

Aber es verbraucht viel CPU (beschäftigt warten). Um Unterprozesse und zu lange Wartezeiten zu vermeiden, habe ich die folgende Lösung gefunden, die ich jedoch als hässlich empfinde:

my_tmp_dir=$(mktemp -d --tmpdir=/tmp)    # Create a unique tmp dir for the fifo.
mkfifo $my_tmp_dir/fifo                  # Create an empty fifo for sleep by read.
exec 3<> $my_tmp_dir/fifo                # Open the fifo for reading and writing.

while true; do
  test_condition && break
  read -t 1 -u 3 var                     # Same as sleep 1, but without sub-process.
done

exec 3<&-                                # Closing the fifo.
rm $my_tmp_dir/fifo; rmdir $my_tmp_dir   # Cleanup, could be done in a trap.

Hinweis: Im Allgemeinen kann ich nicht einfach verwenden read -t 1 var ohne das FIFO , da es stdin verbraucht und nicht funktioniert, wenn stdin kein Terminal oder keine Pipe ist.

Kann ich auf elegantere Weise Teilprozesse und eifriges Warten vermeiden?

jfg956
quelle
1
trueist ein Builtin und erzeugt in bash keinen Subprozess. Das Warten wird immer schlecht sein.
Jordanien
@joranm: Du hast Recht true, Frage aktualisiert.
jfg956
Warum nicht ohne fifo Einfach read -t 1 var.
ott--
@ott: du hast recht, aber das wird stdin verbrauchen. Es funktioniert auch nicht, wenn stdin kein Terminal oder keine Pipe ist.
jfg956
Wenn die Wartbarkeit ein Problem darstellt, würde ich dringend empfehlen, sleepwie im ersten Beispiel vorzugehen. Die zweite, auch wenn sie funktioniert, wird in Zukunft für niemanden leicht zu ändern sein. Einfacher Code hat auch ein größeres Potenzial für die Sicherheit.
Kusalananda

Antworten:

17

In neueren Versionen von bash(mindestens v2) können Builtins enable -f filename commandnamezur Laufzeit (über ) geladen werden. Eine Reihe solcher ladbarer Buildins wird ebenfalls mit den Bash-Quellen verteilt und sleepgehört dazu. Die Verfügbarkeit kann natürlich von Betriebssystem zu Betriebssystem (und sogar von Computer zu Computer) unterschiedlich sein. Unter openSUSE werden diese integrierten Funktionen beispielsweise über das Paket verteilt bash-loadables.

Bearbeiten: Paketnamen korrigieren, minimale Bash-Version hinzufügen.

Ansgar Esztermann
quelle
Wow, das ist, wonach ich suche, und ich lerne definitiv etwas über ladbares eingebautes: +1. Ich werde es versuchen, und doch ist es die beste Antwort.
jfg956
1
Es klappt ! Unter Debian ist das Paket bash-builtins. Es enthält nur Quellen und das Makefile muss bearbeitet werden, aber ich konnte es sleepals eingebautes installieren . Vielen Dank.
jfg956
9

In einer inneren Schleife ist es eine schlechte Sache, viele Unterprozesse zu erstellen. Einen erstellensleep Prozesses pro Sekunde ist in Ordnung. Es ist nichts falsch daran

while ! test_condition; do
  sleep 1
done

Wenn Sie den externen Prozess wirklich vermeiden möchten, müssen Sie das FIFO nicht offen halten.

my_tmpdir=$(mktemp -d)
trap 'rm -rf "$my_tmpdir"' 0
mkfifo "$my_tmpdir/f"

while ! test_condition; do
  read -t 1 <>"$my_tmpdir/f"
done
Gilles 'SO - hör auf böse zu sein'
quelle
Sie haben Recht damit, dass ein Prozess pro Sekunde Erdnüsse ist (aber meine Frage lautete, wie man ihn entfernen kann). Über die kürzere Version: Es ist schöner als meine, also +1 (aber ich habe das entfernt, mkdirwie es von gemacht wird mktemp(wenn nicht, ist es eine Rennbedingung)). Auch stimmt das while ! test_condition;was netter ist als meine Ausgangslösung.
jfg956
7

Ich hatte vor kurzem das Bedürfnis, dies zu tun. Ich habe mir die folgende Funktion ausgedacht, 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 gepostet, die den Dateideskriptor jedes Mal öffnete und schloss, aber ich stellte fest, dass sich auf einigen Systemen, die dies hunderte Male pro Sekunde taten, irgendwann einfrieren würde. Somit behält die neue Lösung den Dateideskriptor zwischen den Aufrufen der Funktion bei. Bash räumt es sowieso beim Beenden auf.

Dies kann genau wie / bin / sleep aufgerufen werden und es 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

In meinem Blog gibt es eine Zusammenfassung mit übermäßigen Details

Bolzen
quelle
1
Ausgezeichneter Blogeintrag. Ich suchte jedoch nach einer Erklärung, warum ich read -t 10 < <(:)sofort zurückkomme, während read -t 10 <> <(:)ich die vollen 10 Sekunden warte, aber ich verstehe es immer noch nicht.
Amir
In read -t 10 <> <(:)welcher nicht <>stehen?
CodeMedic
<> öffnet den Dateideskriptor zum Lesen und Schreiben, obwohl die zugrunde liegende Prozessersetzung <(:) nur das Lesen zulässt. Dies ist ein Hack, der Linux und speziell Linux dazu veranlasst, davon auszugehen, dass jemand darauf schreibt. Daher bleibt read hängen und wartet auf Eingaben, die niemals eintreffen. Auf BSD-Systemen wird dies nicht ausgeführt. In diesem Fall wird die Problemumgehung aktiviert.
Riegel
3

In ksh93oder mksh, sleepist eine Shell - builtin, also eine Alternative könnte sein , diese Schalen zu verwenden , anstatt vonbash .

zshhat auch ein zselecteingebautes (geladen mit zmodload zsh/zselect), das für eine bestimmte Anzahl von Hundertstelsekunden mit schlafen kann zselect -t <n>.

Scrutinizer
quelle
2

Wie Benutzer yoi sagte, wenn in Ihrem Skript stdin geöffnet ist, können Sie anstelle von sleep 1 einfach Folgendes verwenden:

read -t 1 3<&- 3<&0 <&3

Ab Bash-Version 4.1 können Sie float number verwenden, z read -t 0.3 ...

Wenn in einem Skript stdin geschlossen ist (Skript wird aufgerufen my_script.sh < /dev/null &), müssen Sie einen anderen geöffneten Deskriptor verwenden, der beim Lesen keine Ausgabe erzeugt , z. Standard :

read -t 1 <&1 3<&- 3<&0 <&3

Wenn in einem Skript alle Deskriptoren geschlossen sind ( stdin , stdout , stderr ) (z. B. weil es als Daemon bezeichnet wird), müssen Sie eine vorhandene Datei finden, die keine Ausgabe erzeugt:

read -t 1 </dev/tty10 3<&- 3<&0 <&3
Mysak
quelle
read -t 1 3<&- 3<&0 <&3ist das gleiche wie read -t 0. Es liest nur von stdin mit Timeout.
Stéphane Chazelas
1

Dies funktioniert sowohl über eine Login-Shell als auch über eine nicht interaktive Shell.

#!/bin/sh

# to avoid starting /bin/sleep each time we call sleep, 
# make our own using the read built in function
xsleep()
{
  read -t $1 -u 1
}

# usage
xsleep 3
Omar
quelle
Dies funktionierte auch unter Mac OS X 10.12.6
b01
1
Dies wird nicht empfohlen. Wenn dies von mehreren Skripten gleichzeitig verwendet wird, werden alle SIGSTOP'ed, da alle versuchen, stdin zu lesen. Ihr stdin wird blockiert, während dies wartet. Verwenden Sie dazu kein stdin. Sie möchten neue unterschiedliche Dateideskriptoren.
Normadize
1
@Normadize Hier gibt es eine andere Antwort ( unix.stackexchange.com/a/407383/147685 ), die sich mit der Frage der Verwendung kostenloser Dateideskriptoren befasst. Seine absolute Minimalversion ist read -t 10 <> <(:).
Amir
0

Brauchst du wirklich einen Fifo? Das Umleiten von stdin zu einem anderen Dateideskriptor sollte ebenfalls funktionieren.

{
echo line | while read line; do
   read -t 1 <&3
   echo "$line"
done
} 3<&- 3<&0

Inspiriert von: Liest die Eingabe in Bash in einer While-Schleife

yoi
quelle
1
Das macht keinen Schlaf, das verbraucht immer noch etwas vom Terminal.
jfg956
0

Eine leichte Verbesserung gegenüber den oben genannten Lösungen (auf denen ich dies aufgebaut habe).

bash_sleep() {
    read -rt "${1?Specify sleep interval in seconds}" -u 1 <<<"" || :;
}

# sleep for 10 seconds
bash_sleep 10

Reduziert die Notwendigkeit für ein FIFA und damit keine Reinigung zu tun.

CodeMedic
quelle
1
Dies wird nicht empfohlen. Wenn dies von mehreren Skripten gleichzeitig verwendet wird, werden alle SIGSTOP'ed, da alle versuchen, stdin zu lesen. Ihr stdin wird blockiert, während dies wartet. Verwenden Sie dazu kein stdin. Sie möchten neue unterschiedliche Dateideskriptoren.
Normadize
@Normadize Daran habe ich noch nie gedacht. Können Sie mich bitte näher erläutern oder auf eine Ressource verweisen, in der ich mehr darüber lesen kann?
CodeMedic
@CodeMedic Hier gibt es eine andere Antwort ( unix.stackexchange.com/a/407383/147685 ), die sich mit der Verwendung kostenloser Dateideskriptoren befasst. Seine absolute Minimalversion ist read -t 10 <> <(:).
Amir