существует альтернатива "tee", которая захватывает STDOUT/STDERR исполняемой команды и выходит с тем же статусом выхода, что и обработанная команда. Что-то вроде следующего:
Где "eet" - это воображаемая альтернатива "tee":) (-a означает append, - отделяет захваченную команду). Не должно быть сложно взломать такую команду, но, возможно, она уже существует, и я не знал об этом?
Спасибо.
Ответ 5
Это то, что я считаю лучшим чистым решением 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 exit status.
Я думаю, что это лучше всего объяснить изнутри - command1 выполнит и напечатает свой обычный вывод на stdout (дескриптор файла 1), а затем, как только это будет сделано, printf выполнит и распечатает код выхода command1 на своем stdout, но этот stdout перенаправляется в дескриптор файла 3.
В то время как command1 запущен, его stdout передается по команде в command2 (вывод printf никогда не приводит к команде2, потому что мы отправляем его в дескриптор файла 3 вместо 1, что и читает труба). Затем мы перенаправляем вывод command2 в дескриптор файла 4, так что он также остается вне файлового дескриптора 1 - потому что мы хотим, чтобы дескриптор файла 1 был освобожден немного позже, потому что мы будем выводить вывод printf на дескриптор файла 3 обратно в дескриптор файла 1 - потому что то, что подменю команды (backticks), будет захватывать, и то, что будет помещено в переменную.
Последний бит магии состоит в том, что первый exec 4>&1
мы сделали как отдельную команду - он открывает файловый дескриптор 4 как копию внешнего командного файла stdout. Подстановка команд будет захватывать все, что написано на стандартном уровне с точки зрения команд внутри него, но, поскольку вывод command2 будет обрабатывать дескриптор 4 до подстановки команды, подстановка команды не захватывает его, после того, как он "выйдет" из подстановки команды, он по-прежнему остается в общем дескрипторе файла script.
(exec 4>&1
должна быть отдельной командой, потому что многим обычным оболочкам это не нравится, когда вы пытаетесь записать в дескриптор файла внутри подстановки команд, который открывается во внешней команде, которая использует подстановка. Так что это самый простой переносной способ сделать это.)
Вы можете смотреть на него менее техничным и более игривым способом, как если бы выходы команд перескакивали друг с другом: command1 pipe to command2, то вывод printf перескакивает по команде 2, так что command2 не поймает его, а затем команда 2 выводит переходы и вытеснения из командной подстановки так же, как printf приземляется как раз вовремя, чтобы получить захват подстановкой, так что она попадает в переменную, а вывод command2 продолжает свой веселый путь, записываемый на стандартный вывод, как и в обычной трубе.
Кроме того, как я понимаю, $?
будет по-прежнему содержать код возврата второй команды в канале, поскольку назначения переменных, подстановки команд и составные команды эффективно прозрачны для кода возврата команды внутри них, поэтому статус возврата команды2 должен быть распространен.
Предостережение состоит в том, что возможно, что команда1 в какой-то момент закончит использование файловых дескрипторов 3 или 4 или что команда2 или любая из последующих команд будет использовать файловый дескриптор 4, поэтому для обеспечения более надежной работы выполните следующие действия:
exec 4>&1
exitstatus=`{ { command1 3>&-; printf $? 1>&3; } 4>&- | command2 1>&4; } 3>&1`
exec 4>&-
Обратите внимание, что я использую составные команды в моем примере, но подоболочки (используя ( )
вместо { }
также будут работать, но могут быть менее эффективными.)
Команды наследуют дескрипторы файлов из процесса, который запускает их, поэтому вся вторая строка наследует дескриптор файла четыре, а составная команда, за которой следует 3>&1
, наследует дескриптор файла три. Таким образом, 4>&-
гарантирует, что внутренняя команда соединения не будет наследовать файловый дескриптор четыре, а 3>&-
не будет наследовать дескриптор файла три, поэтому command1 получает "более чистую", более стандартную среду. Вы также можете перемещать внутренний 4>&-
рядом с 3>&-
, но я думаю, почему бы не ограничить его область как можно больше.
Я не уверен, как часто вещи используют дескриптор файлов три и четыре напрямую - я думаю, что большинство программ времени используют системные вызовы, которые возвращают дескрипторы файлов, не используемых в момент времени, но иногда код записывает в дескриптор файла 3 (я мог бы представить себе программу, проверяющую файловый дескриптор, чтобы увидеть, открыта ли она, и использовать ее, если она есть, или вести себя иначе, если это не так). Поэтому последнее, вероятно, лучше всего учитывать и использовать для случаев общего назначения.
--- НАХОДНЫЙ СОДЕРЖАНИЕ НИЖЕ ЭТОЙ ЛИНИИ ---
По историческим причинам, вот мой исходный, не-портативный для всех shells ответ:
[EDIT] Неплохо, это не работает с bash, потому что bash нуждается в дополнительном усердии при работе с файловыми дескрипторами, я обновлю это, как только смогу. [/EDIT]
Решение Pure Bourne:
exitstatus=`{ 3>&- command1; } 1>&3; printf $?` 3>&1 | command2
# $exitstatus now has command1 exit status.
Это база, на которой вы можете создать свой "eet". Постучите в синтаксическом разборе аргументов командной строки и все это превратите команду2 в "tee" с соответствующими параметрами и т.д.
ОЧЕНЬ подробное объяснение выглядит следующим образом:
На верхнем уровне оператор представляет собой только трубку между двумя командами:
commandA | command2
commandA в свою очередь разбивается на одну команду с перенаправлением дескриптора файла 3 на дескриптор 1 файла (stdout):
commandB 3>&1
Это означает, что оболочка будет ожидать, что commandB что-то напишет в дескриптор файла 3 - если файловый дескриптор 3 никогда не открывается, это будет ошибка. Это также означает, что command2 будет получать любые выходные данные командыB в обоих дескрипторах файлов 1 (stdout) и 3.
commandB, в свою очередь, является присвоением переменной с использованием подстановки команды:
VAR_FOO=`commandC`
Мы знаем, что присваивания переменных ничего не печатают ни в каких дескрипторах файлов (и commandc stdout захвачен для подстановки), поэтому мы знаем, что commandB в целом ничего не выводит на stdout. Таким образом, command2 будет видеть только то, что commandC записывает в дескриптор файла 3.
И commandC - это две команды, где вторая команда печатает статус выхода первого:
commandD ; printf $?
Итак, теперь мы знаем, что присвоение переменной на последнем шаге будет содержать статус выхода commandD.
Теперь командаD распадается на другое базовое перенаправление, в файле comman stdout для дескриптора файла 3:
commandE 1>&3
Итак, теперь мы знаем, что вещь, записывающая в дескриптор файла 3 и, следовательно, в конечном итоге команду command2, является commandE stdout.
Наконец: commandE - это "составная команда" (здесь вы также можете использовать подоболочку, но это не так эффективно), обертывая другой менее широко известный тип "перенаправления":
{ 3>&- command1; }
(Это 3>&-
немного сложно, поэтому мы вернемся к нему в конце.) Таким образом, составные команды делают эту точку с запятой обязательной, когда последняя команда и последняя скобка находятся в одной строке, поэтому это там. Поэтому мы знаем, что составные команды возвращают код выхода из их последней команды, и они наследуют файловые дескрипторы, как и все остальное, поэтому теперь мы знаем, что command1 stdout вытекает из составной команды, перенаправляет на дескриптор файла 3, чтобы избежать попадания в подстановку команд, тогда подменю команды улавливает оставшуюся строку вывода printf, которая отгоняет статус выхода команды1 после ее завершения.
И теперь для сложного бита: 3>&-
говорит "закрыть дескриптор файла 3". Вы можете подумать: "Почему вы закрываете его, когда вы просто перенаправляете ему вывод команды 1?" Ну, если вы посмотрите внимательно, вы увидите, что эффект close только команды 1 внутри составной команды (внутри фигурных скобок), в то время как перенаправление воздействует на всю составную команду.
Итак, вот что происходит: к моменту запуска отдельных команд составной команды оболочка открыла дескриптор файла 3. Процессы наследуют файловые дескрипторы, поэтому command1 по умолчанию будет запускаться с открытым файловым дескриптором 3 и указывать на такой же место тоже. Это плохо, потому что изредка программы на самом деле ожидают, что конкретные дескрипторы файлов означают особые вещи - они могут вести себя по-разному при запуске с открытым файловым дескриптором 3. Наиболее надежным решением является просто закрыть дескриптор файла 3 (или любой номер, который вы используете) только для команды 1, поэтому он работает так, как если бы файловый дескриптор 3 никогда не был открыт.