тройник и статус выхода

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

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

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

Спасибо.


person pachanga    schedule 12.06.2009    source источник
comment
Я предполагаю, что реальный вопрос здесь заключается в том, как вывести вывод и зафиксировать статус выхода. Если это так: возможный дубликат bash: вывод тройника И статус выхода   -  person lesmana    schedule 13.05.2013


Ответы (8)


Вот 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 или около того.)

person ephemient    schedule 17.06.2009
comment
Странно, но у меня это не работает: jirka@debian:~/monitor$ exec | wc -c \\ 0 \\ jirka@debian:~/monitor$ exec echo a \\ a (\\ означает новую строку) - person jpalecek; 10.03.2010

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

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

Круглые скобки нужны для того, чтобы ограничить эффект pipefail только одной командой.

На справочной странице bash(1):

The return status of a pipeline is the exit status of the last command, unless the pipefail option is enabled. If pipefail is enabled, the pipeline's return status is the value of the last (rightmost) command to exit with a non-zero status, or zero if all commands exit successfully.
person Ville Laurikari    schedule 16.06.2009


Это то, что я считаю лучшим решением для чистой оболочки Bourne, которое можно использовать в качестве основы, на которой вы могли бы построить свой «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 выполнит и напечатает свой обычный вывод на стандартный вывод (файловый дескриптор 1), затем, как только это будет сделано, printf выполнит и напечатает код выхода command1 на свой стандартный вывод, но этот стандартный вывод перенаправляется на файловый дескриптор 3.

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

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

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

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

Кроме того, насколько я понимаю, $? по-прежнему будет содержать код возврата второй команды в канале, потому что присваивание переменных, подстановка команд и составные команды эффективно прозрачны для кода возврата команды внутри них, поэтому статус возврата of 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 напрямую, я угадать (я мог бы представить себе программу, проверяющую дескриптор файла, чтобы узнать, открыт ли он, и используя его, если это так, или соответственно вести себя по-другому, если это не так). Так что последнее, вероятно, лучше всего иметь в виду и использовать для случаев общего назначения.

---УСТАРЕВШИЙ КОНТЕНТ НИЖЕ ЭТОЙ СТРОКИ---

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

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

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

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

Это основа, на которой вы могли бы построить свой «eet». Включите анализ аргументов командной строки и все такое, превратите command2 в «тройник» с соответствующими параметрами и т. д.

ОЧЕНЬ подробное объяснение выглядит следующим образом:

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

commandA | command2

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

commandB 3>&1

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

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

VAR_FOO=`commandC`

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

А commandC — это две команды, где вторая команда выводит статус выхода первой:

commandD ; printf $?

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

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

commandE 1>&3

Итак, теперь мы знаем, что то, что пишет в файловый дескриптор 3 и, следовательно, в command2, — это стандартный вывод commandE.

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

{ 3>&- command1; }

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

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

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

person mtraceur    schedule 05.06.2015

Корн шелл, ВСЕ в 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
person sal    schedule 19.07.2011

#!/bin/sh
logfile="$1"
shift
exec 2>&1
exec "$@" | tee "$logfile"

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

person Marko Teiste    schedule 12.06.2009
comment
Запустите это с аргументами foo false -- должно вернуться с кодом выхода 1 (из false), но я получаю 0 (предположительно из tee). - person Steve Madsen; 12.06.2009
comment
Виноват. Приходится делать это по старинке. PIPE=/tmp/$$.pipe; mkfifo $PIPE; лог-файл=$1; сдвиг; тройник $logfile ‹$PIPE &; $@ 2›&1 ›$PIPE; статус=$?; м $ ТРУБА; выйти из $статуса - person Marko Teiste; 14.06.2009

Добрый день,

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

my_command >>my_log 2>&1

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

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

ХТН

ваше здоровье,

person Rob Wells    schedule 12.06.2009
comment
Это отправляет весь вывод в my_log, но, в отличие от tee, он также не отображается на консоли. - person Steve Madsen; 12.06.2009

person    schedule
comment
По крайней мере, в bash $ret недоступен за пределами {list}, так что это не работает. - person Steve Madsen; 12.06.2009