Избежание ожидания в bash без команды sleep

19

Я знаю, что могу подождать, пока условие станет истинным в bash, выполнив:

while true; do
  test_condition && break
  sleep 1
done

Но он создает 1 подпроцесс на каждую итерацию (сон). Я мог бы избежать их, сделав:

while true; do
  test_condition && break
done

Но он использует много процессора (занят ожидания). Чтобы избежать подпроцессов и напряженного ожидания, я придумал решение, приведенное ниже, но нахожу его уродливым:

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.

Примечание: в общем случае я не могу просто использовать read -t 1 varбез fifo, потому что он будет использовать stdin и не будет работать, если stdin не является терминалом или каналом.

Могу ли я избежать подпроцессов и занятого ожидания более элегантным способом?

jfg956
источник
1
trueявляется встроенным и не создает подпроцесс в Bash. Ожидание всегда будет плохо.
jordanm
@joranm: вы правы true, вопрос обновлен.
jfg956
Почему не без fifo? Просто read -t 1 var.
ot--
@ott: вы правы, но это будет потреблять стандартный ввод. Кроме того, он не будет работать, если stdin не является терминалом или каналом.
jfg956
Если ремонтопригодность является проблемой, я бы настоятельно рекомендовал перейти к тому же sleepпринципу, что и в первом примере. Второй способ, хотя он и может работать, не будет легко адаптироваться в будущем. Простой код также имеет больший потенциал для безопасности.
Кусалананда

Ответы:

17

В более новых версиях bash(по крайней мере v2) встроенные функции могут быть загружены (через enable -f filename commandname) во время выполнения. Ряд таких загружаемых встроенных программ также распространяется вместе с исходными кодами bash и sleepвходит в их число. Конечно, доступность зависит от ОС к ОС (и даже от машины к машине). Например, в openSUSE эти встроенные функции распространяются через пакет bash-loadables.

Редактировать: исправить имя пакета, добавить минимальную версию bash.

Ансгар Эстерманн
источник
Вау, это то, что я ищу, и я определенно узнаю кое-что о загружаемом встроенном: +1. Я попробую это, и все же это лучший ответ.
jfg956
1
Оно работает ! В Debian пакет представляет собой bash-buildins. Он включает только исходники, и Makefile должен быть отредактирован, но я смог установить его sleepкак встроенный. Благодарю.
jfg956
9

Создание большого количества подпроцессов - плохая вещь во внутреннем цикле. Создание одного sleepпроцесса в секунду - это нормально. Там нет ничего плохого в

while ! test_condition; do
  sleep 1
done

Если вы действительно хотите избежать внешнего процесса, вам не нужно держать 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
Жиль "ТАК - прекрати быть злым"
источник
Вы правы насчет арахиса в секунду (но мой вопрос был о том, как найти способ его удалить). Что касается более короткой версии, это лучше, чем моя, так что +1 (но я удалил, mkdirкак это делается mktemp(если нет, это условие гонки)). Также верно в отношении того, while ! test_condition;что лучше, чем мое первоначальное решение.
jfg956
7

У меня недавно была необходимость сделать это. Я придумал следующую функцию, которая позволит bash спать вечно без вызова какой-либо внешней программы:

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

ПРИМЕЧАНИЕ. Ранее я публиковал эту версию, в которой каждый раз открывался и закрывался дескриптор файла, но обнаружил, что в некоторых системах, выполняющих эту операцию сотни раз в секунду, в конечном итоге происходит блокировка. Таким образом, новое решение сохраняет дескриптор файла между вызовами функции. Bash все равно очистит его при выходе.

Это можно вызвать так же, как / bin / sleep, и он будет спать в течение запрошенного времени. Вызывается без параметров, он будет висеть вечно.

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

В моем блоге есть рецензия с чрезмерными подробностями

болт
источник
1
Отличная запись в блоге. Тем не менее, я пошел туда в поисках объяснения, почему read -t 10 < <(:)возвращается сразу, пока read -t 10 <> <(:)ждет полных 10 секунд, но я все еще не понимаю.
Амир
В read -t 10 <> <(:)чем же <>стоять?
CodeMedic
<> открывает дескриптор файла для чтения и записи, хотя подстановка основного процесса <(:) позволяет только чтение. Это хак, который заставляет Linux и Linux, в частности, предполагать, что кто-то может писать в него, поэтому read будет зависать в ожидании ввода, который никогда не поступит. Это не будет делать это в системах BSD, и в этом случае обойдется обходной путь.
Болт
3

В ksh93 или mksh, sleepэто встроенная команда оболочки, поэтому альтернативой может быть использование этих оболочек вместоbash .

zshтакже имеет zselectвстроенный (загруженный zmodload zsh/zselect), который может спать в течение определенного количества сотых секунд сzselect -t <n> .

Scrutinizer
источник
2

Как сказал пользователь yoi , если в вашем скрипте открыт stdin , то вместо sleep 1 вы можете просто использовать:

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

В Bash версии 4.1 и новее вы можете использовать число с плавающей точкой, например read -t 0.3 ...

Если в скрипте stdin закрыт (скрипт вызывается my_script.sh < /dev/null &), то вам нужно использовать другой открытый дескриптор, который не выдает вывод при выполнении чтения , например. стандартный вывод :

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

Если в скрипте весь дескриптор закрыт ( stdin , stdout , stderr ) (например, потому что он называется daemon), то вам нужно найти любой существующий файл, который не производит вывод:

read -t 1 </dev/tty10 3<&- 3<&0 <&3
Mysak
источник
read -t 1 3<&- 3<&0 <&3так же, как read -t 0. Это просто чтение из stdin с таймаутом.
Стефан Шазелас
1

Это работает из оболочки входа в систему, а также из неинтерактивной оболочки.

#!/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
Омар
источник
Это также работало на Mac OS X v10.12.6
b01
1
Это не рекомендуется. Если несколько сценариев используют это одновременно, то все они получают SIGSTOP, поскольку все они пытаются читать stdin. Ваш stdin заблокирован, пока это ждет. Не используйте stdin для этого. Вы хотите новые различные файловые дескрипторы.
Нормализовать
1
@Normadize Здесь есть еще один ответ ( unix.stackexchange.com/a/407383/147685 ), который касается использования бесплатных файловых дескрипторов. Его минимальная версия есть read -t 10 <> <(:).
Амир
0

Тебе действительно нужна пятерка? Перенаправление stdin на другой дескриптор файла также должно работать.

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

Вдохновлено: чтение ввода в bash внутри цикла while

YOI
источник
1
Это не делает сон, это все еще потребляет стандартный ввод из терминала.
jfg956
0

Небольшое улучшение вышеупомянутых решений (на которых я это основал).

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

# sleep for 10 seconds
bash_sleep 10

Уменьшена потребность в fifo и, следовательно, нет необходимости делать уборку.

CodeMedic
источник
1
Это не рекомендуется. Если несколько сценариев используют это одновременно, то все они получают SIGSTOP, поскольку все они пытаются читать stdin. Ваш stdin заблокирован, пока это ждет. Не используйте stdin для этого. Вы хотите новые различные файловые дескрипторы.
Нормализовать
@Normadize Никогда не думал об этом; пожалуйста, вы можете уточнить или указать мне ресурс, где я могу прочитать больше об этом.
CodeMedic
@CodeMedic Здесь есть еще один ответ ( unix.stackexchange.com/a/407383/147685 ), касающийся использования бесплатных файловых дескрипторов. Его минимальная версия есть read -t 10 <> <(:).
Амир