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.