Bash: бесконечный сон (бесконечная блокировка)


158

Я использую startxдля запуска X, который будет оценивать мой .xinitrc. По моему .xinitrcя запускаю свой оконный менеджер, используя/usr/bin/mywm . Теперь, если я убью свой WM (чтобы проверить какой-то другой WM), X тоже прекратит работу, потому что .xinitrcсценарий достиг EOF. Поэтому я добавил это в конце моего .xinitrc:

while true; do sleep 10000; done

Таким образом, X не завершится, если я убью свой WM. Теперь мой вопрос: как я могу сделать бесконечный сон вместо зацикленного сна? Есть ли команда, которая вроде как заморозит скрипт?

С уважением

Ответы:


330

sleep infinity делает именно то, что предлагает и работает без злоупотребления кошкой.


16
Прохладно. К сожалению, мой busybox не понимает.
не пользователь

12
BSD (или, по крайней мере, OS X) тоже не понимает sleep infinity, хотя это было круто для изучения Linux. Тем не менее, while true; do sleep 86400; doneдолжно быть адекватной заменой.
Иван Икс

16
В связи с этим я провел исследование, которое задокументировал в отдельном ответе. Подводя итог: infinityпреобразуется в C из "строки" в double. Затем это doubleусекается до максимально допустимых значений timespec, что означает очень большое количество секунд (зависит от архитектуры), но, теоретически, конечно.
jp48

72

tail не блокирует

Как всегда: для всего есть ответ, который короток, прост для понимания, легок для понимания и совершенно неверен. Вот tail -f /dev/nullпопадает в эту категорию;)

Если вы посмотрите на это вместе, strace tail -f /dev/nullвы заметите, что это решение далеко не блокирует! Вероятно, это даже хуже, чем sleepрешение в вопросе, поскольку он использует (под Linux) драгоценные ресурсы, такие как inotifyсистема. Также другие процессы, которые пишут, чтобы /dev/nullсделать tailцикл. (На моем Ubuntu64 16.10 это добавляет несколько 10 системных вызовов в секунду в уже загруженной системе.)

Вопрос был для команды блокировки

К сожалению, нет такой вещи ..

Читайте: я не знаю, как архивировать это с помощью оболочки напрямую.

Все (даже sleep infinity) может быть прервано каким-то сигналом. Поэтому, если вы хотите быть действительно уверенным, что он не исключительно возвращается, он должен работать в цикле, как вы уже сделали для своего sleep. Обратите внимание, что (в Linux), по- /bin/sleepвидимому, ограничено 24 днями (посмотрите strace sleep infinity), поэтому лучшее, что вы можете сделать, это:

while :; do sleep 2073600; done

(Обратите внимание, что я считаю, что sleepциклы внутренне для более высоких значений, чем 24 дня, но это означает: это не блокирование, а очень медленное зацикливание. Так почему бы не переместить этот цикл наружу?)

.. но вы можете подойти довольно близко с неназванным fifo

Вы можете создать что-то, что действительно блокирует, пока нет никаких сигналов, посылаемых процессу. Следующее использование bash 4, 2 PID и 1 fifo:

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

Вы можете проверить, что это действительно блокирует, straceесли вам нравится:

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

Как это было построено

readблокирует, если нет входных данных (см. некоторые другие ответы). Однако tty(aka. stdin) Обычно не является хорошим источником, так как он закрывается, когда пользователь выходит из системы. Также это может украсть некоторый вклад от tty. Не хорошо.

Чтобы сделать readблок, нам нужно ждать чего-то вроде a, fifoкоторое никогда ничего не вернет. В bash 4есть команда , которая может точно предоставить нам такие fifo: coproc. Если мы также ждем блокировку read(которая является нашей coproc), мы сделали. К сожалению, для этого необходимо оставить открытыми два идентификатора PID и a fifo.

Вариант с именем fifo

Если вы не удосужились использовать именованные fifo, вы можете сделать это следующим образом:

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

Не использовать цикл при чтении немного небрежно, но вы можете использовать его fifoтак часто, как вам нравится, и заставить его readзавершить с помощью touch "$HOME/.pause.fifo"(если имеется более одного ожидания чтения, все завершаются одновременно).

Или используйте pause()системный вызов Linux

Для бесконечной блокировки есть вызов ядра Linux, который называется pause(), который делает то, что мы хотим: ждать вечно (пока не придет сигнал). Однако для этого пока нет программы для пользователя.

С

Создать такую ​​программу легко. Вот фрагмент кода для создания очень маленькой программы для Linux, которая называется на pauseнеопределенный срок (нужды dietи gccт. Д.):

printf '#include <unistd.h>\nint main(){for(;;)pause();}' > pause.c;
diet -Os cc pause.c -o pause;
strip -s pause;
ls -al pause

python

Если вы не хотите что-то компилировать самостоятельно, но уже pythonустановили, вы можете использовать это под Linux:

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

(Примечание: использовать exec python -c ... для замены текущей оболочки, это освобождает один PID. Решение может быть улучшено также путем некоторого перенаправления ввода-вывода, освобождая неиспользуемые FD. Это ваше дело.)

Как это работает (я думаю): ctypes.CDLL(None)загружает стандартную библиотеку C и запускает в ней pause()функцию в каком-то дополнительном цикле. Менее эффективен, чем версия C, но работает.

Моя рекомендация для вас:

Оставайтесь в дремлющем сне. Это легко понять, очень переносимо и блокирует большую часть времени.


1
@Andrew Обычно вам не нужны ни trap(который изменяет поведение оболочки для сигналов), ни фон (который позволяет оболочке перехватывать сигналы от терминала, например, Strg + C). Так sleep infinityчто достаточно (ведет себя так, как exec sleep infinityбудто это последнее утверждение. Чтобы увидеть разницу использовать strace -ffDI4 bash -c 'YOURCODEHERE'). Цикл сна лучше, потому что sleepможет вернуться при определенных обстоятельствах. Например, вы не хотите, чтобы X11 внезапно закрывался на a killall sleep, потому что он .xstartupзаканчивается sleep infinityвместо цикла ожидания.
Тино

Может быть немного неясным, но s6-pauseэто команда пользователя для запуска pause(), опционально игнорируя различные сигналы.
Патрик

@ Тино /bin/sleepне ограничен 24 днями, как вы говорите. Было бы хорошо, если бы вы могли обновить это. В Linux сейчас этот код активен. Он ограничивает отдельные nanosleep()системные вызовы до 24 дней, но вызывает их в цикле. Поэтому sleep infinityне следует выходить через 24 дня. doubleПоложительная бесконечность преобразуется к struct timespec. Глядя rpl_nanosleepв GDB, infinityпреобразуется в { tv_sec = 9223372036854775807, tv_nsec = 999999999 }Ubuntu 16.04.
nh2

@ nh2 В тексте уже упоминалось, что сон скорее всего зацикливается, а не полностью блокируется. Я немного отредактировал его, чтобы, надеюсь, сделать этот факт немного более понятным. Пожалуйста, обратите внимание на это « вероятно », потому что в straceодиночку я не могу доказать факт, что действительно есть некоторый зацикленный код sleep, и я не хочу ждать 24 дня, чтобы проверить это (или декомпилировать /bin/sleep). Всегда лучше программировать в обороне, если нет серьезных математических доказательств того, что что-то действительно так, как кажется. Также никогда не доверяйте ничему:killall -9 sleep
Тино

Опцию pause () можно сделать довольно легко с помощью perl: perl -MPOSIX -e 'pause ()'
tgoodhart

70

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


4
Это не работает, если у вас нет висящей трубы, из которой можно читать. Пожалуйста, порекомендуйте.
Matt Joiner

2
@ Matt, может быть, сделать трубу и catэто? mkfifo pipe && cat pipe
Михал Трибус

Что говорит @twalberg, но кроме того, вы можете немедленно переназначить 3 и отменить связь, как показано здесь: superuser.com/a/633185/762481
jp48

32

TL; DR: sleep infinityфактически спит максимально допустимое время, которое является конечным.

Удивляясь, почему это нигде не задокументировано, я потрудился прочитать исходники из GNU coreutils и обнаружил, что он выполняет примерно следующее:

  1. Используйте strtodиз C stdlib первый аргумент, чтобы преобразовать бесконечность в двойную точность. Таким образом, при условии двойной точности IEEE 754 64-разрядное положительное значение бесконечности сохраняется в secondsпеременной.
  2. Invoke xnanosleep(seconds)( находится в gnulib ), это, в свою очередь, вызывает dtotimespec(seconds)( также в gnulib ) для преобразования из doubleв struct timespec.
  3. struct timespecэто просто пара чисел: целая часть (в секундах) и дробная часть (в наносекундах). Наивное преобразование положительной бесконечности в целое число приведет к неопределенному поведению (см. §6.3.1.4 из стандарта C), поэтому вместо этого оно усекается TYPE_MAXIMUM (time_t).
  4. Фактическое значение TYPE_MAXIMUM (time_t)не установлено в стандарте (даже sizeof(time_t)нет); Итак, для примера давайте выберем x86-64 из недавнего ядра Linux.

Это TIME_T_MAXв ядре Linux, которое определяется ( time.h) как:

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

Обратите внимание, что time_tесть __kernel_time_tи time_tесть long; используется модель данных LP64, а также sizeof(long)8 (64 бита).

Какие результаты в: TIME_T_MAX = 9223372036854775807.

То есть: sleep infiniteприводит к фактическому времени сна 9223372036854775807 секунд (10 ^ 11 лет). А для 32-разрядных систем Linux ( sizeof(long)4 (32 бита)): 2147483647 секунд (68 лет; см. Также проблему 2038 года ).


Редактировать : очевидно, что nanosecondsвызываемая функция - это не системный вызов, а зависимая от ОС оболочка (также определенная в gnulib ).

Там это дополнительный шаг в результате: для некоторых систем , в которых HAVE_BUG_BIG_NANOSLEEPявляется trueсон сокращается до 24 дней , а затем вызывается в цикле. Это касается некоторых (или всех?) Дистрибутивов Linux. Обратите внимание, что эта обертка может не использоваться, если тест настройки времени успешен ( источник ).

В частности, это будет 24 * 24 * 60 * 60 = 2073600 seconds(плюс 999999999 наносекунд); но это вызывается в цикле для соблюдения указанного общего времени ожидания. Поэтому предыдущие выводы остаются в силе.


В заключение, результирующее время сна не бесконечно, но достаточно велико для всех практических целей , даже если результирующий фактический промежуток времени не является переносимым; это зависит от ОС и архитектуры.

Чтобы ответить на исходный вопрос, это, очевидно, достаточно хорошо, но если по какой-то причине (система с очень ограниченными ресурсами) вы действительно хотите избежать бесполезного дополнительного таймера обратного отсчета, я думаю, что наиболее правильной альтернативой является использование catметода, описанного в других ответах. ,


1
В следующем coreutils sleep infinityтеперь будет фактически спать вечно без зацикливания: lists.gnu.org/archive/html/bug-gnulib/2020-02/msg00081.html
Владимир Пантелеев

8

sleep infinityвыглядит наиболее элегантно, но иногда это не работает по какой-то причине. В этом случае, вы можете попробовать другие команды блокировки , такие как cat, read, tail -f /dev/null, и grep aт.д.


1
tail -f /dev/nullбыло также для меня рабочим решением на платформе SaaS
schmunk

2
tail -f /dev/nullтакже имеет то преимущество, что не потребляет стандартный ввод. Я использовал это по этой причине.
Судо Баш

Те, кто рассматривает этот вариант, должны прочитать этот ответ, чтобы узнать о последствиях этого варианта.
Тень

6

Как насчет отправки SIGSTOP самому себе?

Это должно приостановить процесс до получения SIGCONT. Что в вашем случае: никогда.

kill -STOP "$$";
# grace time for signal delivery
sleep 60;

6
Сигналы асинхронные. Таким образом, может произойти следующее: a) вызовы оболочки kill b) kill сообщает ядру, что оболочка должна получить сигнал STOP c) kill завершается и возвращается в оболочку d) оболочка продолжается (возможно, завершается после завершения сценария) e) ядро ​​наконец находит время для доставки сигнал STOP для оболочки
не пользователь

1
@temple Отличное понимание, не думал об асинхронной природе сигналов. Спасибо!
michuelnik

4

Позвольте мне объяснить, почему sleep infinityработает, хотя это не задокументировано. Ответ jp48 также полезен.

Самая важная вещь: указав infили infinity(независимо от регистра), вы можете спать дольше, чем позволяет ваша реализация (т.е. меньшее значение HUGE_VALи TYPE_MAXIMUM(time_t)).

Теперь давайте углубимся в детали. Исходный код sleepкоманды можно прочитать из coreutils / src / sleep.c . По сути, функция делает это:

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

Понимание xstrtod (argv[i], &p, &s, cl_strtod)

xstrtod()

Согласно gnulib / lib / xstrtod.c , вызов xstrtod()преобразования преобразует строку argv[i]в значение с плавающей запятой и сохраняет ее *s, используя функцию преобразования cl_strtod().

cl_strtod()

Как видно из coreutils / lib / cl-strtod.c , cl_strtod()преобразует строку в значение с плавающей запятой, используя strtod().

strtod()

Согласно man 3 strtod, strtod()преобразует строку в значение типа double. На странице написано

Ожидаемая форма (начальной части) строки - это ... или (iii) бесконечность, или ...

и бесконечность определяется как

Бесконечность - это либо «INF», либо «INFINITY», независимо от регистра.

Хотя в документе говорится

Если правильное значение вызовет переполнение, возвращается плюс или минус HUGE_VAL( HUGE_VALF, HUGE_VALL)

, не понятно, как лечится бесконечность. Итак, давайте посмотрим на исходный код gnulib / lib / strtod.c . То, что мы хотим прочитать, это

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

Таким образом, INFи INFINITY(оба без учета регистра) рассматриваются как HUGE_VAL.

HUGE_VAL семья

Давайте использовать N1570 в качестве стандарта C. HUGE_VAL, HUGE_VALFИ HUGE_VALLмакросы определены в §7.12-3

Макрос
    HUGE_VAL
расширяется до положительного двойного константного выражения, не обязательно представляемого как число с плавающей точкой. Макросы
    HUGE_VALF
    HUGE_VALL
являются соответственно плавающими и длинными двойными аналогами HUGE_VAL.

HUGE_VAL, HUGE_VALFи HUGE_VALLмогут быть положительными бесконечностями в реализации, которая поддерживает бесконечности.

и в §7.12.1-5

Если плавающий результат переливается и по умолчанию округление в действительности, то функция возвращает значение макроса HUGE_VAL, HUGE_VALFили в HUGE_VALLсоответствии с типом возвращаемого значения

Понимание xnanosleep (s)

Теперь мы понимаем всю суть xstrtod(). Из приведенных выше объяснений совершенно ясно, что xnanosleep(s)мы впервые увидели, что означает xnanosleep(HUGE_VALL).

xnanosleep()

Согласно исходному коду gnulib / lib / xnanosleep.c , по xnanosleep(s)сути это:

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

dtotimespec()

Эта функция преобразует аргумент типа doubleв объект типа struct timespec. Поскольку это очень просто, позвольте мне привести исходный код gnulib / lib / dtotimespec.c . Все комментарии добавлены мной.

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

Поскольку time_tопределяется как целочисленный тип (см. §7.27.1-3), естественно, мы предполагаем, что максимальное значение типа time_tменьше, чем HUGE_VAL(типа double), что означает, что мы входим в случай переполнения. (На самом деле это предположение не является необходимым, поскольку во всех случаях процедура по существу одинакова.)

make_timespec()

Последняя стена, на которую мы должны подняться, это make_timespec(). К счастью, это так просто, что достаточно сослаться на исходный код gnulib / lib / timespec.h .

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

2

У меня недавно была необходимость сделать это. Я придумал следующую функцию, которая позволит 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

Этот подход не потребляет ресурсов для поддержки процесса.

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

Сломать

  • while :; do sleep 1; done & Создает фиктивный процесс в фоновом режиме
  • kill -STOP $! Останавливает фоновый процесс
  • wait $! Подождите, пока фоновый процесс, это будет блокировать навсегда, потому что фоновый процесс был остановлен до

0

Вместо того, чтобы убивать оконный менеджер, попробуйте запустить новый с --replaceили, -replaceесли доступно.


1
Если я использую, --replaceя всегда получаю предупреждение, как another window manager is already running. Это не имеет большого смысла для меня, хотя.
Watain

-2
while :; do read; done

не дожидаясь детского процесса сна.


1
Это съедает, stdinесли это все еще случается, связано с tty. Если вы запускаете его с < /dev/nullзанятыми циклами. Это может быть полезно в определенных ситуациях, поэтому я не буду понижать голос.
Тино

1
Это очень плохая идея, она просто потребляет много процессора.
Мохаммед Нурельдин
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.