Вопрос или проблема
для следующей команды
output=$(cat $file | docker exec -i CONTAINER COMMAND 2>&1 | tee /dev/stderr)
какое значение и использование
2>&1 | tee /dev/stderr? Я поискал в Google и обнаружил, что 2>&1 означает “объединить stderr и stdout в поток stdout.” tee означает “читает из стандартного ввода и одновременно записывает в стандартный вывод и в один или несколько файлов.” Но я не могу понять, если объединить
2>&1 | tee /dev/stderr
это объединяет stderr и stdout в поток stdout, но затем снова записывает его в /dev/stderr (&2)?
зачем объединять в stdout, а затем перенаправлять обратно в stderr?
и после записи в /dev/stderr, будет ли сообщение сохранено в переменной output в
output=$(cat $file | docker exec -i CONTAINER COMMAND 2>&1 | tee /dev/stderr)
?
Это попытка отправить вывод и ошибки docker как в stderr, так и сохранить их в переменной $output, но этот нецитируемый $file, отсутствие — (и возможно UUOC), это использование /dev/stderr (по крайней мере на Linux или Cygwin) все неверны. Статус выхода docker также теряется. Чтобы сделать это правильно, лучше перейти на zsh и выполнить:
output=$(<$file docker exec -i CONTAINER COMMAND >&1 >&2 2>&1)
В zsh, когда файловый дескриптор (здесь 1) переадресуется более одного раза для вывода, он вместо этого переадресуется в канал к внутреннему процессу, подобному tee. Проходя через эти переадресации, мы имеем:
>&1 (коротко для 1>&1) ничего не делает, но там, чтобы мы могли иметь более одной переадресации файлового дескриптора 1. На этом этапе файловый дескриптор 1 все еще указывает на канал, созданный заменой команды, которая используется для заполнения $output.
>&2 (коротко для 1>&2). Это вторая переадресация файлового дескриптора 1. Поскольку файловый дескриптор 1 был уже перенаправлен ранее (выше), он становится пишущим концом канала. А на другом конце этого канала, у нас есть этот внутренний процесс, подобный tee, который распределяет то, что он читает из этого канала, на то, что было изначально на файловом дескрипторе 1 (канал к $output) и на то, что открыто на stderr¹
2>&1, файловый дескриптор 2 идет в тот же канал, что и выше.
Подозреваю, что намерение автора могло быть таковым: чтобы вывод и ошибки docker шли туда, куда они должны были идти, и в то же время сохранялись оба в $output, в таком случае, это выглядело бы так (все еще в zsh):
3>&1 output=$(
<$file docker exec -i CONTAINER COMMAND 2>&2 2>&1 >&1 >&3 3>&-
)
Где и файловый дескриптор 1, и 2 в конечном итоге отправляются отдельно через tee:
2 в оригинальный stderr и в $output
1 в $output и в оригинальный stdout, который мы делаем доступным внутри подстановки команды, используя этот файловый дескриптор 3.
¹ это может быть канал, терминальное устройство, если выполняется из терминала, это может быть сокет (как это обычно бывает для процессов, запускаемых systemd, где stderr обычно является сокетом Unix domain, подключенным к journald), обычный файл, открытый в режиме дополнения или нет. В Linux, использование tee /dev/stderr, когда stderr открыт на сокете, приведет к ошибке, так как нельзя открыть сокет с помощью open(), а для обычного файла (например, лог-файла) он обнулит его, потеряв все его предыдущие содержимое!
Значение:
2>&1 перенаправляет STDERR в STDOUT
| перенаправляет STDOUT предыдущей команды в STDIN следующей команды
tee /dev/stderr получает STDIN и отправляет его в STDERR (устройство /dev/stderr) и в STDOUT
В итоге: объединяет STDERR и STDOUT и отправляет объединенный поток в STDERR и STDOUT.
Ответ или решение
Теория
Команда 2>&1 | tee /dev/stderr
является общепринятым методом управления потоками ввода-вывода в Unix-подобных системах и используется для одновременного перенаправления стандартного вывода (STDOUT) и стандартного вывода ошибок (STDERR). Чтобы полностью понять её суть, давайте разберём каждый элемент этой команды.
-
2>&1
: Это перенаправление STDERR в STDOUT. В Unix-подобных системах потоки ввода/вывода представляются через файловые дескрипторы:0
— стандартный ввод (STDIN),1
— стандартный вывод (STDOUT),2
— стандартный вывод ошибок (STDERR). Выражение2>&1
говорит системе о том, что STDERR должен быть перенаправлен на ту же "дорогу", что и STDOUT, т.е. ошибки будут выводиться в тот же поток, что и стандартный вывод. -
|
: Это конвейер, который берёт STDOUT предыдущей команды и направляет его на STDIN следующей. Это один из столпов командной линии Unix, позволяющий создавать сложные цепочки команд. -
tee /dev/stderr
: Командаtee
читает из стандартного ввода (который здесь поступает из предыдущего конвейера) и записывает одновременно в стандартный вывод и в файл или устройство, указанное как аргумент. В данном случае,/dev/stderr
— это специальное устройство в Unix-системах, обращение к которому позволяет писать в стандартный вывод ошибок текущего процесса.
Пример
Рассмотрим пример использования команды:
output=$(cat $file | docker exec -i CONTAINER COMMAND 2>&1 | tee /dev/stderr)
В этом случае команда последовательно выполняется следующим образом:
cat $file
— считывает содержимое файла и передаёт его в STDIN для следующей команды.docker exec -i CONTAINER COMMAND
— выполняет указанныйCOMMAND
внутри контейнера Docker, получая STDIN из предыдущегоcat
.2>&1
— перенаправляет STDERR этой команды в STDOUT.| tee /dev/stderr
— берёт объединённый поток STDOUT и STDERR, переданный изdocker exec
, и направляет его одновременно в STDERR и STDOUT.
Применение
Цель такого конвейера — одновременно отображать вывод команды в реальном времени на стандартное устройство ошибок и одновременно сохранять этот вывод в переменной. Это может быть полезно в нескольких сценариях:
-
Мониторинг: отладка программ, работающих в контейнере, позволяет моментально увидеть ошибки или сообщения в реальном времени, без необходимости ожидать окончания выполнения.
-
Логирование: сохранение данных в переменной
output
позволяет затем анализировать или обрабатывать вывод программmatically, без необходимости повторного выполнения команды.
Заключение
Итак, основная цель этой техники заключается в одновременном захвате вывода в переменную и его транспарантного отображения на устройстве вывода ошибок. Это делает процесс более гибким и позволяет решать сразу несколько задач: отладка, логирование и анализ данных в одном потоке выполнения. Использование таких приемов в Unix-подобных системах позволяет эффективно управлять потоками данных и улучшать качество написания скриптов для администраторов и разработчиков.