тройник и статус выхода - PullRequest
14 голосов
/ 12 июня 2009

есть альтернатива "tee", которая захватывает STDOUT / STDERR выполняемой команды и выходит с тем же статусом выхода, что и обработанная команда. Что-то вроде следующего:

eet -a some.log -- mycommand --foo --bar

Где «eet» - это воображаемая альтернатива «tee» :) (-a означает «добавить», - отделяет захваченную команду) Не должно быть сложно взломать такую ​​команду, но, возможно, она уже существует, и я не в курсе?

Спасибо.

Ответы [ 8 ]

22 голосов
/ 16 июня 2009

Это работает с bash:

(
  set -o pipefail
  mycommand --foo --bar | tee some.log
)

Скобки предназначены для ограничения эффекта pipefail только одной командой.

Из справочной страницы bash (1):

Состояние возврата конвейера - это состояние выхода последней команды, если не включена опция pipefail. Если pipefail включено, статус возврата конвейера - это значение последней (самой правой) команды для выхода с ненулевым статусом или ноль, если все команды завершаются успешно.
9 голосов
/ 12 июня 2009

Наткнулся здесь на пару интересных решений http://www.perlmonks.org/?node_id=597613.

1) В bash доступна переменная $ PIPESTATUS:

   false | tee /dev/null
   [ $PIPESTATUS -eq 0 ] || exit $PIPESTATUS

2) И самый простой прототип eet в perl может выглядеть следующим образом:

   open MAKE, "command 2>&1 |" or die;
   open (LOGFILE, ">>some.log") or die;
   while (<MAKE>) { print LOGFILE $_; print }
   close MAKE; # to get $?
   my $exit = $? >> 8;
   close LOGFILE;
5 голосов
/ 17 июня 2009

Вот это eet. Работает с каждым Bash, который у меня есть, от 2.05b до 4.0.

#!/bin/bash
tee_args=()
while [[ $# > 0 && $1 != -- ]]; do
    tee_args=("${tee_args[@]}" "$1")
    shift
done
shift
# now ${tee_args[*]} has the arguments before --,
# and $* has the arguments after --

# redirect standard out through a pipe to tee
exec | tee "${tee_args[@]}"

# do the *real* exec of the desired program
exec "$@"

(pipefail и $PIPESTATUS - это хорошо, но я помню, что они были введены в версии 3.1 или около того.)

1 голос
/ 05 июня 2015

Это то, что я считаю лучшим решением с использованием оболочки Bourne-shell для использования в качестве основы, на которой вы можете построить свой «eet»:

# You want to pipe command1 through command2:
exec 4>&1
exitstatus=`{ { command1; printf $? 1>&3; } | command2 1>&4; } 3>&1`
# $exitstatus now has command1's exit status.

Я думаю, что это лучше всего объяснить изнутри - command1 выполнит и напечатает свой обычный вывод на stdout (дескриптор файла 1), затем, как только это будет сделано, printf выполнит и напечатает код выхода command1 на своем stdout, но этот stdout перенаправляется в файловый дескриптор 3.

Когда команда command1 выполняется, ее стандартный вывод передается по команде command2 (вывод printf никогда не попадает в command2, потому что мы отправляем его в файловый дескриптор 3 вместо 1, который читает канал). Затем мы перенаправляем вывод команды 2 в файловый дескриптор 4, чтобы он также не входил в файловый дескриптор 1 - потому что мы хотим, чтобы файловый дескриптор 1 был немного позже, потому что мы приведем вывод printf для файлового дескриптора 3 обратно в файловый дескриптор 1 - потому что это то, что команда замещения (обратные метки), захватит, и это то, что будет помещено в переменную.

Последнее волшебство в том, что сначала exec 4>&1 мы сделали отдельной командой - он открывает файловый дескриптор 4 как копию стандартного вывода внешней оболочки. Подстановка команд будет захватывать все, что написано в стандарте, с точки зрения команд внутри него - но, поскольку выходные данные команды 2 собираются в файловом дескрипторе 4, что касается подстановки команд, подстановка команд не захватывает это - однако, как только он «выходит» из подстановки команд, он по-прежнему фактически обращается к общему дескриптору файла скрипта 1.

(exec 4>&1 должна быть отдельной командой, потому что многим распространенным оболочкам не нравится, когда вы пытаетесь записать в файловый дескриптор внутри подстановки команды, которая открывается во «внешней» команде, использующей замена. Так что это самый простой переносимый способ сделать это.)

Вы можете посмотреть на это менее технически и более игриво, как если бы выходы команд перепрыгивали друг друга: команда1 перенаправляет на команду2, затем вывод printf перепрыгивает через команду 2, так что команда2 не может ее перехватить , а затем выходные данные команды 2 перепрыгивают из подстановки команд, точно так же, как printf приземляется как раз вовремя, чтобы быть захваченным подстановкой, так что она попадает в переменную, а выходные данные команды 2 идут своим веселым путем, записываясь в стандартный вывод как в обычной трубе.

Кроме того, насколько я понимаю, $? будет по-прежнему содержать код возврата второй команды в конвейере, поскольку назначения переменных, подстановки команд и составные команды эффективно прозрачны для кода возврата команды внутри них. поэтому возвращаемый статус command2 должен распространяться.

Предостережение заключается в том, что возможно, что command1 в какой-то момент закончится использованием файловых дескрипторов 3 или 4, или что command2 или любая из более поздних команд будут использовать файловый дескриптор 4, поэтому для большей устойчивости вы должны сделать:

exec 4>&1
exitstatus=`{ { command1 3>&-; printf $? 1>&3; } 4>&- | command2 1>&4; } 3>&1`
exec 4>&-

Обратите внимание, что я использую составные команды в моем примере, но подоболочки (использование ( ) вместо { } также будет работать, хотя, возможно, будет менее эффективным.)

Команды наследуют файловые дескрипторы от процесса, который их запускает, поэтому вся вторая строка будет наследовать файловый дескриптор четыре, а составная команда, за которой следует 3>&1, будет наследовать файловый дескриптор три. Таким образом, 4>&- гарантирует, что внутренняя составная команда не будет наследовать дескриптор файла четыре, а 3>&- не будет наследовать дескриптор файла три, поэтому команда1 получает «более чистую», более стандартную среду. Вы также можете переместить внутренний 4>&- рядом с 3>&-, но я понимаю, почему бы просто не ограничить его область настолько, насколько это возможно.

Я нетНе знаю, как часто вещи используют файловый дескриптор 3 и 4 напрямую - я думаю, что в большинстве случаев программы используют системные вызовы, которые возвращают неиспользуемые в данный момент файловые дескрипторы, но иногда, я думаю, код записывает данные непосредственно в файловый дескриптор 3 (я мог бы представить себе программу, проверяющую дескриптор файла, чтобы увидеть, открыт ли он, и использующую его, если он есть, или, соответственно, ведущий себя иначе, если это не так). Поэтому последнее, вероятно, лучше всего учитывать и использовать в случаях общего назначения.

--- УСТАРЕВШИЙ КОНТЕНТ ПОД ЭТОЙ ЛИНИЙ ---

По историческим причинам, вот мой оригинальный ответ, который нельзя переносить на все оболочки:

[РЕДАКТИРОВАТЬ] Мое плохое, это не работает с bash, потому что bash требует дополнительной обработки при работе с файловыми дескрипторами, я обновлю это, как только смогу. [/ EDIT] * +1039 *

Чистый раствор оболочки Борн:

exitstatus=`{ 3>&- command1; } 1>&3; printf $?` 3>&1 | command2
# $exitstatus now has command1's exit status.

Это основа, на которой вы могли бы построить свой "eet". Подберите какой-нибудь аргумент командной строки и все такое, превратите command2 в "tee" с соответствующими параметрами и т. Д.

ОЧЕНЬ подробное объяснение следующее:

На верхнем уровне оператор - это просто канал между двумя командами:

commandA | command2

команда A, в свою очередь, разбивается на одну команду с перенаправлением дескриптора файла 3 на дескриптор файла 1 (стандартный вывод):

commandB 3>&1

Это означает, что оболочка будет ожидать, что команда B запишет что-то в дескриптор файла 3 - если дескриптор файла 3 никогда не открывается, это будет ошибкой. Это также означает, что command2 будет получать любые выходные данные commandB для обоих файловых дескрипторов 1 (stdout) и 3.

Команда B, в свою очередь, представляет собой присвоение переменной с использованием подстановки команд:

VAR_FOO=`commandC`

Мы знаем, что назначения переменных ничего не печатают ни в каких файловых дескрипторах (и стандартный вывод commandC захватывается для подстановки), поэтому мы знаем, что команда B в целом не будет ничего выводить на стандартный вывод. Таким образом, command2 будет видеть только то, что commandC записывает в дескриптор файла 3.

И команда C - это две команды, где вторая команда печатает состояние выхода первой:

commandD ; printf $?

Итак, теперь мы знаем, что присвоение переменной на последнем шаге будет содержать состояние завершения команды D.

Теперь commandD разлагается на другое базовое перенаправление - стандартный вывод коммандера на дескриптор файла 3:

commandE 1>&3

Итак, теперь мы знаем, что запись в файловый дескриптор 3 и, следовательно, в конечном итоге в command2, является stdout команды E.

Наконец, commandE - это «составная команда» (здесь вы также можете использовать подоболочку, но она не так эффективна), охватывающую еще один менее распространенный тип «перенаправления»:

{ 3>&- command1; }

(Это 3>&- немного сложно, поэтому мы вернемся к нему в конце.) Таким образом, составные команды делают эту точку с запятой обязательной, когда последняя команда и последняя фигурная скобка находятся в одной строке, поэтому там. Итак, мы знаем, что составные команды возвращают код завершения своей последней команды, и они наследуют файловые дескрипторы, как и все остальное, поэтому теперь мы знаем, что стандартный вывод command1 вытекает из составной команды, перенаправляет на файловый дескриптор 3, чтобы избежать перехвата подстановкой команды между тем подстановка команд перехватывает оставшийся стандартный вывод printf, который отображает состояние завершения команды command1, как только это будет сделано.

А теперь для хитрости: 3>&- говорит "закрыть дескриптор файла 3". Вы можете подумать: «Почему вы закрываете его, когда просто перенаправляете выходные данные команды command1?» Что ж, если вы посмотрите внимательно, вы увидите, что команда спецэффектов только команда1 внутри составной команды (внутри фигурных скобок) специально, а перенаправление влияет на всю составную команду.

Итак, вот что происходит: к моменту запуска отдельных команд составной команды оболочка открывает файловый дескриптор 3. Процессы наследуют файловые дескрипторы, поэтому команда command1 по умолчанию будет работать с открытым файловым дескриптором 3 и указывать на тот же место тоже Это плохо, потому что иногда программы ожидают, что определенные файловые дескрипторы будут означать особые вещи - они могут вести себя по-разному при запуске с открытым файловым дескриптором 3. Наиболее надежное решение - просто закрыть дескриптор файла 3 (или любой другой номер, который вы используете) только для command1, чтобы он работал так, как если бы дескриптор файла 3 никогда не открывался.

1 голос
/ 19 июля 2011

оболочка Корн, ВСЕ в 1 строке:

foo; RET_VAL=$?; if test ${RET_VAL} != 0;then echo $RET_VAL; echo Error occurred!>/tmp/out.err;exit 2;fi |tee >>/tmp/out.err ; if test ${RET_VAL} != 0;then exit $RET_VAL;fi
0 голосов
/ 12 июня 2009
{ mycommand --foo --bar 2>&1; ret=$?; } | tee -a some.log; (exit $ret)
0 голосов
/ 12 июня 2009
#!/bin/sh
logfile="$1"
shift
exec 2>&1
exec "$@" | tee "$logfile"

Надеюсь, это работает для вас.

0 голосов
/ 12 июня 2009

G'day,

Предполагая bash или zsh,

my_command >>my_log 2>&1

N.B. Последовательность перенаправления и дублирования STDERR на STDOUT значительна!

Редактировать: Упс. Не понимал, что ты хотел видеть вывод на экране также. Это, конечно, перенаправит весь вывод в файл my_log.

НТН

ура

...