Какова цель трубы после перенаправления stdout?

Вопрос или проблема

Я читал документацию для Kaniko и наткнулся на конструкцию с перенаправлением-да-пайпом, которую я раньше не видел:

echo -e 'FROM alpine \nRUN echo "created from standard input"' > Dockerfile | tar -cf - Dockerfile

Вывод echo перенаправляется в Dockerfile, а затем идет | к tar, который, насколько я вижу, всегда будет пустым. В таких случаях я всегда комбинировал команды с && или ||, поэтому мне стало интересно, есть ли специальная цель в использовании пайпа-пустоты в этом случае?

cmd > file | other-cmd

Имеет смысл только в оболочке zsh, где, когда дескриптор файла (здесь 1, стандартный вывод cmd) перенаправляется более одного раза для вывода, оболочка реализует внутренний диспетчер, похожий на tee, который передает его всем целям (это поведение можно отключить с помощью set +o multios):

$ zsh
% echo test > a > b | sed s/t/b/
best
% cat a
test
% cat b
test

Однако, я сомневаюсь, что код подразумевал zsh, поскольку tar -cf - Dockerfile не читает из своего stdin (пайпа), он читает вместо этого из файла Dockerfile и записывает архив tar в stdout.

echo и tar выполняются параллельно, stdout echo будет Dockerfile, и в оболочках, отличных от zsh, stdin tar будет концом чтения сломанного пайпа, так как stdout echo был перенаправлен от конца записи, так что писателя не осталось (это не имеет значения, так как tar не читает из него).

Это в принципе то же самое, что и запуск:

echo... & tar...

Кроме того, что в нескольких оболочках, в echo... | tar..., обе команды echo и tar ожидаются, тогда как в echo... & tar... ожидается только tar.

Эта команда неверна, так как, поскольку echo и tar выполняются параллельно, нет гарантии, что на момент, когда tar читает Dockerfile, echo уже записал его вывод в него.

Должно было быть:

echo ... > Dockerfile; tar -cf - Dockerfile

так что echo и tar выполняются последовательно. Или еще лучше:

printf '%s\n' 'FROM alpine ' 'RUN echo "created from standard input"' > Dockerfile &&
  tar -cf - Dockerfile

То есть:

  1. используйте printf вместо очень непортативной команды echo.
  2. запускайте tar после printf, но только если printf завершился успешно.

Командная строка корректна, и это не опечатка.

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

docker run -it --rm alpine sh -c 'echo -e "FROM alpine \nRUN echo \"created from standard input\"" > Dockerfile | tar -cf - Dockerfile'

Такое же поведение можно наблюдать с командой cat:

echo -e 'FROM alpine \nRUN echo "created from standard input"' > Dockerfile | cat Dockerfile

Я предполагаю, что вы, вероятно, неправильно поняли параметры, переданные tar. Он не читает из stdin, а скорее записывает в stdout.

Объяснение

Оболочка создает первый процесс (echo), вместе с его stdout (Dockerfile), затем создается следующий процесс (tar). Оба процесса начинают выполняться, и поскольку Dockerfile уже существует, tar может начать его чтение и записывает вывод в stdout.

В некоторых случаях stdout является tty (как отмечали некоторые комментарии), поэтому вам нужно заменить stdout на что-то, куда tar может записать, например, файл (Dockerfile.tar). Без изменения первоначальной команды просто добавьте перенаправление вывода к команде tar, чтобы записать в файл вместо этого, и вы сможете наблюдать те же результаты.

echo -e 'FROM alpine \nRUN echo "created from standard input"' >Dockerfile | tar -cf - Dockerfile >Dockerfile.tar

Ответ или решение

Команда, которую вы привели, действительно необычна на первый взгляд, но давайте рассмотрим её более подробно, чтобы понять, как она работает и какую цель преследует.

Строка команды:

echo -e 'FROM alpine \nRUN echo "created from standard input"' > Dockerfile | tar -cf - Dockerfile

здесь сначала осуществляется перенаправление вывода команды echo в файл Dockerfile, а затем команда tar использует этот файл для создания архива.

Пояснение:

  1. Перенаправление вывода: Команда echo выводит текст и перенаправляет его в файл Dockerfile. Это значит, что в момент выполнения этой команды содержимое файла Dockerfile будет обновлено.

  2. Конвейер (pipe): Символ | указывает на то, что выходные данные одной команды могут быть переданы в качестве ввода для другой команды. В данном случае, это означает, что stdout echo должен передаваться на stdin tar. Однако стоит отметить, что tar здесь не читает из стандартного ввода, так как он ожидает, что ему будет передан файл Dockerfile для упаковки.

  3. Файловое дескрипторное поведение: В некоторых оболочках, таких как zsh, существует специальное поведение, позволяющее одновременно перенаправлять вывод в файл и передавать его через конвейер. Однако в оболочках, таких как bash, поведение будет иным. В большинстве случаев tar, в данном случае, будет просто ожидать файл и не будет заниматься чтением из stdin, потому что он не настроен для этого.

Ваши вопросы о корректности:

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

Оптимизация:

Чтобы избежать путаницы, можно изменять эту команду, делая её более предсказуемой и безопасной. Примером замены может быть:

echo -e 'FROM alpine \nRUN echo "created from standard input"' > Dockerfile && tar -cf - Dockerfile

В таком случае, команда tar будет выполняться только после успешного завершения команды echo.

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

Оцените материал
Добавить комментарий

Капча загружается...