Итак, я хотел бы дать ответ, подобный ответу Лесманы, но я думаю, что мой, пожалуй, немного проще и немного более выгодно решение с чистой оболочкой Борна:
# 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 выполнит и напечатает код выхода icommand1 на своем 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 должен распространяться - это, и не нужно определять дополнительную функцию, поэтому я думаю, что это могло бы быть несколько лучшим решением, чем предложенное lesmana.
В соответствии с предостережениями, которые упоминает лесмена, вполне возможно, что в какой-то момент команда1 будет использовать файловые дескрипторы 3 или 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 Я полагаю, непосредственно (я мог бы представить программу, проверяющую дескриптор файла, чтобы увидеть, открыт ли он, и использующую его, если он есть, или соответствующим образом ведущий себя иначе, если это не так). Поэтому последнее, вероятно, лучше всего учитывать и использовать в случаях общего назначения.