Вопрос

Я хочу выполнить длительно выполняющуюся команду в Bash, и оба фиксируют ее статус выхода, и тройник его вывод.

Поэтому я делаю это:

command | tee out.txt
ST=$?

Проблема в том, что переменная ST фиксирует состояние выхода tee и не по приказу.Как я могу решить эту проблему?

Обратите внимание, что команда выполняется долго, и перенаправление выходных данных в файл для последующего просмотра для меня не является хорошим решением.

Это было полезно?

Решение

Существует внутренняя переменная Bash с именем $PIPESTATUS; это & # 8217; это массив, в котором хранится состояние выхода каждой команды в вашем последнем переднем конвейере команд.

<command> | tee out.txt ; test ${PIPESTATUS[0]} -eq 0

Или другой вариант, который также работает с другими оболочками (например, zsh), - включить pipefail:

set -o pipefail
...

Первый вариант не работает с zsh из-за немного другого синтаксиса.

Другие советы

полезно использовать bash's set -o pipefail

  

pipefail: возвращаемое значение конвейера - это состояние      последняя команда для выхода с ненулевым статусом,      или ноль, если ни одна команда не вышла с ненулевым статусом

Тупое решение: соединить их через именованный канал (mkfifo). Тогда команда может быть запущена второй.

 mkfifo pipe
 tee out.txt < pipe &
 command > pipe
 echo $?

Есть массив, который дает вам статус выхода каждой команды в канале.

$ cat x| sed 's///'
cat: x: No such file or directory
$ echo $?
0
$ cat x| sed 's///'
cat: x: No such file or directory
$ echo ${PIPESTATUS[*]}
1 0
$ touch x
$ cat x| sed 's'
sed: 1: "s": substitute pattern can not be delimited by newline or backslash
$ echo ${PIPESTATUS[*]}
0 1

Это решение работает без использования специфичных для bash функций или временных файлов.Бонус:в конце концов, статус выхода на самом деле является статусом выхода, а не какой-то строкой в файле.

Ситуация:

someprog | filter

вам нужен статус выхода из someprog и вывод из filter.

Вот мое решение:

((((someprog; echo $? >&3) | filter >&4) 3>&1) | (read xs; exit $xs)) 4>&1

echo $?

Видишь мой ответ на тот же вопрос по unix.stackexchange.com для получения подробного объяснения и альтернативы без подоболочек и некоторых оговорок.

Путем объединения PIPESTATUS[0] и результат выполнения exit команда в подоболочке вы можете напрямую получить доступ к возвращаемому значению вашей начальной команды:

command | tee ; ( exit ${PIPESTATUS[0]} )

Вот пример:

# the "false" shell built-in command returns 1
false | tee ; ( exit ${PIPESTATUS[0]} )
echo "return value: $?"

даст тебе:

return value: 1

Итак, я хотел бы дать ответ, подобный ответу Лесманы, но я думаю, что мой, возможно, немного проще и немного более выгоден для решения с использованием чистой оболочки Борна:

# 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, если речь идет о подстановке команд, подстановка команд не захватывает это - однако, как только это произойдет получает " out " после подстановки команд он все еще идет к общему дескриптору файла скрипта 1.

($? должна быть отдельной командой, потому что многим распространенным оболочкам она не нравится, когда вы пытаетесь записать в файловый дескриптор внутри подстановки команды, которая открывается в " external quot; команда, использующая подстановку. Так что это самый простой переносимый способ сделать это.)

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

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

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

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

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

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

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

В Ubuntu и Debian вы можете apt-get install moreutils. Он содержит утилиту с именем mispipe, которая возвращает состояние выхода первой команды в канале.

PIPESTATUS [@] должен быть скопирован в массив сразу после возврата команды pipe. Любые чтения PIPESTATUS [@] сотрут содержимое. Скопируйте его в другой массив, если вы планируете проверить состояние всех команд конвейера. & Quot;? $ Quot &; то же значение, что и последний элемент " $ {PIPESTATUS [@]} " ;, и чтение его, похоже, разрушает " $ {PIPESTATUS [@]} " но я не совсем это подтвердил.

declare -a PSA  
cmd1 | cmd2 | cmd3  
PSA=( "${PIPESTATUS[@]}" )

Это не будет работать, если канал находится в под-оболочке. Для решения этой проблемы,
см. bip pipestatus в команде с обратным символом?

(command | tee out.txt; exit ${PIPESTATUS[0]})

В отличие от ответа @ cODAR, он возвращает исходный код завершения первой команды, а не только 0 для успеха и 127 для ошибки. Но, как заметил @Chaoran, вы можете просто позвонить ${PIPESTATUS[0]}. Однако важно, чтобы все было заключено в квадратные скобки.

Вне bash вы можете сделать:

bash -o pipefail  -c "command1 | tee output"

Это полезно, например, в скриптах ниндзя, где ожидается, что оболочка будет /bin/sh.

Самый простой способ сделать это в обычном bash - использовать замена процесса вместо конвейера.Есть несколько отличий, но они, вероятно, не имеют большого значения для вашего варианта использования:

  • При запуске конвейера bash ожидает завершения всех процессов.
  • Отправка Ctrl-C в bash приводит к остановке всех процессов конвейера, а не только основного.
  • Тот Самый pipefail вариант и PIPESTATUS переменные не имеют отношения к процессу подстановки.
  • Возможно, больше

При замене процесса bash просто запускает процесс и забывает о нем, он даже не виден в jobs.

Оставим в стороне упомянутые различия, consumer < <(producer) и producer | consumer по существу эквивалентны.

Если вы хотите изменить, какой из них является "основным" процессом, вы просто меняете команды и направление подстановки на producer > >(consumer).В вашем случае:

command > >(tee out.txt)

Пример:

$ { echo "hello world"; false; } > >(tee out.txt)
hello world
$ echo $?
1
$ cat out.txt
hello world

$ echo "hello world" > >(tee out.txt)
hello world
$ echo $?
0
$ cat out.txt
hello world

Как я уже сказал, есть отличия от выражения pipe.Процесс может никогда не прекращаться, если только он не чувствителен к закрытию трубы.В частности, он может продолжать записывать что-то в ваш стандартный вывод, что может сбивать с толку.

Чистое решение для оболочки:

% rm -f error.flag; echo hello world \
| (cat || echo "First command failed: $?" >> error.flag) \
| (cat || echo "Second command failed: $?" >> error.flag) \
| (cat || echo "Third command failed: $?" >> error.flag) \
; test -s error.flag  && (echo Some command failed: ; cat error.flag)
hello world

А теперь, когда второй cat заменен на false:

% rm -f error.flag; echo hello world \
| (cat || echo "First command failed: $?" >> error.flag) \
| (false || echo "Second command failed: $?" >> error.flag) \
| (cat || echo "Third command failed: $?" >> error.flag) \
; test -s error.flag  && (echo Some command failed: ; cat error.flag)
Some command failed:
Second command failed: 1
First command failed: 141

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

Этот метод позволяет захватывать stdout и stderr для отдельных команд, чтобы вы могли затем скопировать их в файл журнала, если возникла ошибка, или просто удалить ее, если ошибки нет (например, вывод dd).

Основываясь на ответе @ brian-s-wilson; эта вспомогательная функция bash:

pipestatus() {
  local S=("${PIPESTATUS[@]}")

  if test -n "$*"
  then test "$*" = "${S[*]}"
  else ! [[ "${S[@]}" =~ [^0\ ] ]]
  fi
}

используется таким образом:

1: get_bad_things должен быть успешным, но он не должен выводить ничего; но мы хотим видеть вывод, который он производит

get_bad_things | grep '^'
pipeinfo 0 1 || return

2: все конвейеры должны быть успешными

thing | something -q | thingy
pipeinfo || return

Иногда может быть проще и понятнее использовать внешнюю команду, чем копаться в деталях bash. конвейер из минимального языка сценариев процесса execline , завершается с кодом возврата второй команды *, как это делает конвейер sh, но в отличие от execline , он позволяет изменить направление канала, чтобы мы могли зафиксировать код возврата процесса производителя (ниже приведено все в командной строке pipeline, но с установленным <=>):

$ # using the full execline grammar with the execlineb parser:
$ execlineb -c 'pipeline { echo "hello world" } tee out.txt'
hello world
$ cat out.txt
hello world

$ # for these simple examples, one can forego the parser and just use "" as a separator
$ # traditional order
$ pipeline echo "hello world" "" tee out.txt 
hello world

$ # "write" order (second command writes rather than reads)
$ pipeline -w tee out.txt "" echo "hello world"
hello world

$ # pipeline execs into the second command, so that's the RC we get
$ pipeline -w tee out.txt "" false; echo $?
1

$ pipeline -w tee out.txt "" true; echo $?
0

$ # output and exit status
$ pipeline -w tee out.txt "" sh -c "echo 'hello world'; exit 42"; echo "RC: $?"
hello world
RC: 42
$ cat out.txt
hello world

Использование <=> имеет те же отличия от собственных конвейеров bash, что и замена процесса bash, использованная в ответе # 43972501 .

* На самом деле <=> не завершается вообще, если только не произошла ошибка. Он выполняется во второй команде, поэтому это вторая команда, которая выполняет возврат.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top