Unikanie zajętego czekania w mgnieniu oka, bez komendy uśpienia


19

Wiem, że mogę czekać na spełnienie warunku bash, wykonując:

while true; do
  test_condition && break
  sleep 1
done

Ale tworzy 1 podproces przy każdej iteracji (uśpieniu). Mogłem ich uniknąć, wykonując:

while true; do
  test_condition && break
done

Ale zużywa dużo procesora (zajęte oczekiwanie). Aby uniknąć podprocesów i zajętego czekaniem, wymyśliłem poniższe rozwiązanie, ale wydaje mi się brzydkie:

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.

Uwaga: w ogólnym przypadku nie mogę po prostu użyć read -t 1 varbez fifo, ponieważ zużyje standardowe wejście i nie będzie działać, jeśli standardowe wejście nie jest terminalem ani rurą.

Czy mogę uniknąć podprocesów i zajętego oczekiwania w bardziej elegancki sposób?


1
truejest wbudowany i nie tworzy podprocesu w bash. zajęty czekaniem zawsze będzie źle.
Jordan

@ joranm: masz rację true, pytanie zaktualizowane.
jfg956

Dlaczego nie bez FIFO? Po prostu read -t 1 var.
ott--

@ott: masz rację, ale to zużyje standardowe wejście. Nie zadziała również, jeśli stdin nie jest terminalem ani rurą.
jfg956

Jeśli problem z utrzymaniem jest problemem, zdecydowanie sugerowałbym pójście tak, sleepjak w pierwszym przykładzie. Drugi, choć może działać, nie będzie łatwo nikomu się dostosowywać w przyszłości. Prosty kod ma również większy potencjał bezpieczeństwa.
Kusalananda

Odpowiedzi:


17

W nowszych wersjach bash(przynajmniej v2) wbudowane mogą być ładowane (przez enable -f filename commandname) w czasie wykonywania. Wiele takich ładowalnych wbudowanych plików jest również dystrybuowanych ze źródłami bash i sleepjest wśród nich. Oczywiście dostępność może się różnić w zależności od systemu operacyjnego (a nawet komputera). Na przykład w openSUSE te wbudowane funkcje są dystrybuowane za pośrednictwem pakietu bash-loadables.

Edycja: napraw nazwę pakietu, dodaj minimalną wersję bash.


Wow, to jest to, czego szukam i zdecydowanie uczę się czegoś o ładowalnej wbudowanej funkcji: +1. Spróbuję tego, a jednak jest to najlepsza odpowiedź.
jfg956

1
To działa ! W Debianie pakiet jest wbudowany w bash. Zawiera tylko źródła i plik Makefile musi być edytowany, ale mogłem zainstalować sleepjako wbudowany. Dzięki.
jfg956

9

Tworzenie wielu podprocesów jest złą rzeczą w wewnętrznej pętli. Tworzenie jednego sleepprocesu na sekundę jest OK. Nie ma w tym nic złego

while ! test_condition; do
  sleep 1
done

Jeśli naprawdę chcesz uniknąć zewnętrznego procesu, nie musisz utrzymywać otwartego fifo.

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

Masz rację, że proces na sekundę to orzeszki ziemne (ale moje pytanie dotyczyło znalezienia sposobu na jego usunięcie). O krótszej wersji, jest ładniejsza niż moja, więc +1 (ale usunąłem mkdirją tak, jak to zrobiono mktemp(jeśli nie, jest to warunek wyścigu). Dotyczy to również tego, while ! test_condition;co jest lepsze niż moje początkowe rozwiązanie.
jfg956

7

Ostatnio musiałem to zrobić. Wymyśliłem następującą funkcję, która pozwoli bashowi spać na zawsze bez wywoływania jakiegokolwiek programu zewnętrznego:

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

UWAGA: Wcześniej opublikowałem wersję, która za każdym razem otwierałaby i zamykała deskryptor pliku, ale zauważyłem, że w niektórych systemach, robiąc to setki razy, sekunda w końcu się blokuje. Zatem nowe rozwiązanie utrzymuje deskryptor pliku między wywołaniami funkcji. Bash i tak wyczyści go przy wyjściu.

Można to nazwać tak jak / bin / sleep i będzie ono spało przez żądany czas. Wywołany bez parametrów, zawiesi się na zawsze.

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

Tutaj jest mój blog z nadmiernymi szczegółami


1
Doskonały wpis na blogu. Poszedłem jednak tam, szukając wyjaśnienia, dlaczego read -t 10 < <(:)wraca natychmiast, read -t 10 <> <(:)czekając pełne 10 sekund, ale wciąż nie rozumiem.
Amir

W read -t 10 <> <(:)czym oznacza <>?
CodeMedic

<> otwiera deskryptor pliku do odczytu i zapisu, mimo że podstawienie procesu <(:) pozwala tylko na odczyt. Jest to hack, który powoduje, że Linux, a szczególnie Linux, zakłada, że ​​ktoś może do niego napisać, więc odczyt zawiesi się, czekając na dane wejściowe, które nigdy nie dotrą. Nie zrobi tego w systemach BSD, w którym to przypadku obejdzie się obejście.
śruba

3

W ksh93lub mksh, sleepjest wbudowane powłoki, tak Alternatywą może być zastosowanie tych skorup, a nie bash.

zshma również zselectwbudowane (ładowane zmodload zsh/zselect), które mogą spać przez określoną liczbę setnych sekund z zselect -t <n>.


2

Jak powiedział użytkownik yoi , jeśli w twoim skrypcie jest otwarte stdin , to zamiast uśpienia 1 możesz po prostu użyć:

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

W wersji Bash 4.1 i nowszych możesz użyć liczby zmiennoprzecinkowej, np read -t 0.3 ...

Jeśli w skrypcie stdin jest zamknięte (skrypt jest wywoływany my_script.sh < /dev/null &), musisz użyć innego otwartego deskryptora, który nie generuje danych wyjściowych po wykonaniu odczytu , np. standardowe :

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

Jeśli w skrypcie cały deskryptor jest zamknięty ( stdin , stdout , stderr ) (np. Ponieważ jest wywoływany jako demon), to musisz znaleźć dowolny plik, który nie generuje danych wyjściowych:

read -t 1 </dev/tty10 3<&- 3<&0 <&3

read -t 1 3<&- 3<&0 <&3jest taki sam jak read -t 0. To tylko czytanie ze standardowego limitu czasu.
Stéphane Chazelas

1

Działa to zarówno z powłoki logowania, jak i powłoki nieinteraktywnej.

#!/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

Działa to również w systemie Mac OS X 10.12.6
b01

1
Nie jest to zalecane. Jeśli wiele skryptów korzysta z tego w tym samym czasie, wszystkie otrzymują SIGSTOP, gdy wszyscy próbują odczytać standardowe wejście. Twoje standardowe wejście zostanie zablokowane na czas oczekiwania. Nie używaj do tego standardowego wejścia. Chcesz nowych deskryptorów plików.
Normadize

1
@Normadize Tu jest inna odpowiedź ( unix.stackexchange.com/a/407383/147685 ), która dotyczy problemu korzystania z darmowych deskryptorów plików. Jego minimalna wersja to read -t 10 <> <(:).
Amir

0

Czy naprawdę potrzebujesz fifo? Powinno również działać przekierowanie standardowego wejścia na inny deskryptor pliku.

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

Inspirowane przez: Przeczytaj dane wejściowe w bash w pętli while


1
To nie jest sen, to wciąż zużywa standardowe wejście z terminala.
jfg956

0

Nieznaczna poprawa wyżej wymienionych rozwiązań (na których to oparłem).

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

# sleep for 10 seconds
bash_sleep 10

Zmniejszyło potrzebę użycia fifo, a zatem nie ma potrzeby sprzątania.


1
Nie jest to zalecane. Jeśli wiele skryptów korzysta z tego w tym samym czasie, wszystkie otrzymują SIGSTOP, gdy wszyscy próbują odczytać standardowe wejście. Twoje standardowe wejście zostanie zablokowane na czas oczekiwania. Nie używaj do tego standardowego wejścia. Chcesz nowych deskryptorów plików.
Normadize

@Normadize Nigdy o tym nie myślałem; proszę, możesz opracować lub wskazać mi zasób, w którym mogę przeczytać więcej na ten temat.
CodeMedic

@CodeMedic Tu jest inna odpowiedź ( unix.stackexchange.com/a/407383/147685 ), która dotyczy problemu używania darmowych deskryptorów plików. Jego minimalna wersja to read -t 10 <> <(:).
Amir
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.