Сценарий bash не выполняет все команды сна

Вам необходимо иметь шебанг, чтобы выполнить скрипт, указав его путь. В противном случае операционная система попытается выполнить его, но потерпит неудачу, поскольку это не двоичный исполняемый файл.

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

fish myscript.sh

1
10.04.2021, 21:27
3 ответа
(echo ${test};...) | date

Что ты здесь пытаешься сделать? Вы передаете данные на стандартный ввод date, но dateне читает никаких входных данных. Он просто печатает дату и выходит.

После выхода из dateканал закрывается, после чего любые напечатанные в него данные некуда деваться, а процесс, записывающий в канал, подоболочка (echo; sleep; echo; sleep), получает сигнал SIGPIPE и умирает.

Вот как в целом работают конвейеры. Если левая -сторона — это что-то, что может производить массу вывода, возможно, даже произвольное количество, сигнал — это то, что говорит ему остановиться после того, как правая -сторона теряет интерес.

. в чем-то вроде cat /dev/zero | head -c128 > /dev/nullсигнал в конечном итоге убивает cat, поэтому он не остается на неопределенный срок. Ни оболочка, ни catне выводят сообщение об ошибке. Это просто часть нормальной работы, когда трубы работают так. Аналогично для рассматриваемого здесь случая. (Однако в некоторых случаях вы получаете сообщение об ошибке,просто не ожидайте, что всегда получите один за это.)

(Внешний цикл не влияет на результаты, поэтому я его исключил. Вы можете использовать time (... ) |...для измерения времени работы конвейера.)

В Bash можно проверить статус выхода команд в конвейере из массива PIPESTATUS:

$ ( echo ${test}; sleep 4s; echo hop2; sleep 50s; echo hop3; ) |
    date;  declare -p PIPESTATUS
Sat Apr 10 21:34:56 EEST 2021
declare -a PIPESTATUS=([0]="141" [1]="0")

SIGPIPE имеет номер 13 по крайней мере в Linux, поэтому он соответствует показанному статусу выхода 141 = 128 + номер сигнала. (Процесс также мог нормально завершиться со статусом >= 128, но здесь это не так.)

Или вы могли бы straceпосмотреть, что происходит:

$ strace -f bash -c '( echo ${test}; sleep 4s; echo hop2; 
                       sleep 50s; echo hop3; ) | date'
...
[pid 31647] write(1, "hop2\n", 5)       = -1 EPIPE (Broken pipe)
[pid 31647] --- SIGPIPE {si_signo=SIGPIPE, si_code=SI_USER, si_pid=31647, si_uid=1000} ---
[pid 31647] +++ killed by SIGPIPE +++
...

С другой стороны, если бы было что-то, что считывает ввод с правой стороны канала, не было бы SIGPIPE, и весь конвейер заснул бы в общей сложности на 54 секунды:

$ time ( echo ${test}; sleep 4s; echo hop2; sleep 50s; echo hop3; ) |
       cat > /dev/null 

real    0m54.005s
user    0m0.000s
sys     0m0.000s

Или, если игнорировать SIGPIPE на стороне записи:

$ time ( trap '' PIPE; echo foo; sleep 4s; echo hop2; sleep 10s; 
         echo hop3; ) | date 
Sat Apr 10 21:57:07 EEST 2021
bash: echo: write error: Broken pipe
bash: echo: write error: Broken pipe

real    0m14.006s
user    0m0.004s
sys     0m0.000s

При игнорировании SIGPIPE оболочка получает регулярный возврат ошибки при записи в закрытый канал, и для этого она печатает ошибку.


Обратите внимание, что здесь также существует проблема синхронизации между левой -стороной канала, записывающей в канал, и правой -стороной, закрывающей его.

Если я сделаю что-то подобное:

( echo foo; echo bar; sleep 4s; ) | date;

sleepработает. Но если я заменю два echoна for i in {1..100}; do echo foo; done;, LHS умрет, не дойдя до sleep. (Гонка здесь будет зависеть от системы.)

И относительно случая с другим dateпервым делом на LHS:

(date; echo ${test}; sleep 50s; echo hop2; sleep 50s; echo hop3) | date

Это также связано с проблемами синхронизации. Поскольку dateявляется внешней командой, оболочка может запускать ее медленнее, чем просто внутренне обрабатывать echo(, поскольку она встроена во все оболочки, в конце концов, ). Это повышает вероятность того, что dateс правой стороны -выиграет гонку и закроет канал до того, как первый echoполучит возможность писать в него.


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

9
28.04.2021, 22:53

Чтобы прояснить это, позвольте мне добавить отладочный вывод в stderr (в обход конвейера ), до и после эха "hop2":

$ for test in test1 test2 test3; do 
      (echo ${test}; sleep 4s; echo before hop2 >&2;
       echo hop2; echo after hop2 >&2; sleep 4s; echo hop3) | date;
  done
Sat Apr 10 11:29:46 PDT 2021
before hop2
Sat Apr 10 11:29:50 PDT 2021
before hop2
Sat Apr 10 11:29:54 PDT 2021
before hop2

Обратите внимание, чтоecho after hop2 >&2никогда не выполняет , а также команды после него :, второй sleepи echo hop3.

Насколько я понимаю, происходит вот что. В цикле два отдельных процесса выполняются параллельно, при этом выходные данные первого передаются на вход второго. Два процесса выполняются:

echo ${test}
sleep 4s
echo before hop2 >&2
echo hop2
echo after hop2 >&2
sleep 4s
echo hop3

и

date

Вот приблизительная последовательность выполнения (точная последовательность первых 3 шагов и начало шага 4 будет несколько случайной):

  1. Процесс 1 выполняется echo ${test}; это записывает «test1» (и новую строку )в канал, где он буферизуется, чтобы его можно было прочитать позже.
  2. Процесс 2 выполняет date, выводя текущую дату на терминал.
  3. Процесс 2 завершает работу, закрывая конец конвейера.
  4. Процесс 1 выполняется sleep 4s.
  5. Через 4 секунды процесс 1 выполняет echo before hop2 >&2, выводя «before hop2» на терминал.
  6. Процесс 1 пытается выполнить echo hop2, но поскольку единственный считыватель канала закрыл его, он получает ошибку SIGPIPE. Очевидно, это приводит к завершению всего процесса подоболочки (, а не только команды echo).

Обратите внимание, что это происходит только потому, что echoявляется встроенной оболочкой; если вы использовали/bin/echo hop2(внешнюю команду, вместо echoвстроенной )оболочки она выполнит вторую sleep, как вы и ожидали.

Кстати, это относительно согласуется между различными оболочками. Я получаю те же результаты, запуская это в bash, zsh, dash и ksh (в интерактивном режиме ). ksh в скрипте немного отличается, потому что он, по-видимому, не ждет завершения процесса 1, прежде чем продолжить, поэтому все dateвыполняются немедленно,за (через 4 секунды )последовала серия строк «before hop2».

17
28.04.2021, 22:53

Нечего отправлять на date, так как он не получает данных на стандартный ввод.

Таким образом, использование |просто некорректно. Я считаю, что вместо этого вам нужно использовать &&.

Но вопрос:

Почему временной интервал между каждым выходом составляет четыре секунды вместо восьми ?

По-прежнему действует, но сбивает с толку. Короткий ответ: команды после echo hop2никогда не выполняются. Второй сон никогда не используется.

$ (echo test; sleep 4s; echo hop2; sleep 4s; echo hop3) | date; date
Sun 11 Apr 2021 05:30:28 AM UTC
Sun 11 Apr 2021 05:30:32 AM UTC

Если мы используем stderrвместо stdout, все команды выполняются:

$ (echo test >&2; sleep 4s; echo hop2 >&2; sleep 4s; echo hop3 >&2) | date; date

test
Sun 11 Apr 2021 05:32:36 AM UTC
hop2
hop3
Sun 11 Apr 2021 05:32:44 AM UTC

Причина в том, что dateне получает ввод в стандартный ввод, любая попытка записи в него будет сопровождаться SIGPIPE и всей оболочкой (bash )(слева от|)закроется. Прерывание любой другой команды в списке.

Причина успеха самого первого echoв вашем примере зависит от времени:

  1. В начале работающая оболочка установит канал |.
  2. Для этого открывается дочерний элемент, в котором запускается команда date.
  3. Не дожидаясь ничего, оболочка возвращается на другую сторону канала и (в другом дочернем )просит его выполнить echo ${test}.
  4. Поскольку канал все еще открыт,(dateне закрыл его ), вывод эха буферизуется и выполняется команда sleep 4s.
  5. Команда dateполучает достаточно времени, чтобы закончить и закрыть канал.
  6. Теперь при закрытом канале следующий echoне сможет писать в него. Это вызовет SIGPIPE.
  7. При получении SIGPIPE дочерняя оболочка в левой части |полностью завершает работу (эхо является встроенной функцией, а ошибки встроенных -ins вызывают сбой всей оболочки ).
  8. Больше никаких команд в канале выполняться не будет.

Вот почему это:

$ (sleep 1; echo test; sleep 4s; echo hop2 >&2; ) | date ; date 

Sun 11 Apr 2021 06:10:04 AM UTC
Sun 11 Apr 2021 06:10:05 AM UTC

Даже первое эхо не печатается. Время может быть всего 0,1 секунды. Этого достаточно, чтобы позволить dateзакончиться.

Это может быть даже вызов внешней команды, которая задерживает echoдостаточно:

(/usr/bin/true; echo test; sleep 4s; echo confirm >&2 ) | date; date
Sun 11 Apr 2021 06:27:01 AM UTC
Sun 11 Apr 2021 06:27:01 AM UTC

Ваша петля for— это повторение вышеописанного трижды. Таким образом, между каждым циклом всего четыре секунды.

4
28.04.2021, 22:53

Теги

Похожие вопросы