Вам необходимо иметь шебанг, чтобы выполнить скрипт, указав его путь. В противном случае операционная система попытается выполнить его, но потерпит неудачу, поскольку это не двоичный исполняемый файл.
Если вы не хотите использовать шебанг, вам нужно указать, что должно выполнять скрипт. В вашем случае это должно быть fish
.
fish myscript.sh
(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.
Чтобы прояснить это, позвольте мне добавить отладочный вывод в 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 будет несколько случайной):
echo ${test}
; это записывает «test1» (и новую строку )в канал, где он буферизуется, чтобы его можно было прочитать позже. date
, выводя текущую дату на терминал. sleep 4s
. echo before hop2 >&2
, выводя «before hop2» на терминал. echo hop2
, но поскольку единственный считыватель канала закрыл его, он получает ошибку SIGPIPE. Очевидно, это приводит к завершению всего процесса подоболочки (, а не только команды echo
). Обратите внимание, что это происходит только потому, что echo
является встроенной оболочкой; если вы использовали/bin/echo hop2
(внешнюю команду, вместо echo
встроенной )оболочки она выполнит вторую sleep
, как вы и ожидали.
Кстати, это относительно согласуется между различными оболочками. Я получаю те же результаты, запуская это в bash, zsh, dash и ksh (в интерактивном режиме ). ksh в скрипте немного отличается, потому что он, по-видимому, не ждет завершения процесса 1, прежде чем продолжить, поэтому все date
выполняются немедленно,за (через 4 секунды )последовала серия строк «before hop2».
Нечего отправлять на 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
в вашем примере зависит от времени:
|
. date
. echo ${test}
. date
не закрыл его ), вывод эха буферизуется и выполняется команда sleep 4s
.date
получает достаточно времени, чтобы закончить и закрыть канал. echo
не сможет писать в него. Это вызовет SIGPIPE. |
полностью завершает работу (эхо является встроенной функцией, а ошибки встроенных -ins вызывают сбой всей оболочки ). Вот почему это:
$ (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
— это повторение вышеописанного трижды. Таким образом, между каждым циклом всего четыре секунды.