Вопрос или проблема
Я читал документацию для 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
То есть:
- используйте
printf
вместо очень непортативной командыecho
. - запускайте
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
использует этот файл для создания архива.
Пояснение:
-
Перенаправление вывода: Команда
echo
выводит текст и перенаправляет его в файлDockerfile
. Это значит, что в момент выполнения этой команды содержимое файлаDockerfile
будет обновлено. -
Конвейер (pipe): Символ
|
указывает на то, что выходные данные одной команды могут быть переданы в качестве ввода для другой команды. В данном случае, это означает, что stdoutecho
должен передаваться на stdintar
. Однако стоит отметить, чтоtar
здесь не читает из стандартного ввода, так как он ожидает, что ему будет передан файлDockerfile
для упаковки. -
Файловое дескрипторное поведение: В некоторых оболочках, таких как
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
.
Таким образом, использование конвейеров в сочетании с перенаправлением вывода – это техника, которая требует понимания того, как работает оболочка и как она обрабатывает файловые дескрипторы. Выявление таких нюансов как раз и помогает избежать возможных ошибок в будущих написаниях скриптов на основе командной строки.