Трубный выход и состояние захвата выхода в Bash - PullRequest
375 голосов
/ 03 августа 2009

Я хочу выполнить длительную команду в Bash, и обе записать ее состояние выхода, а tee ее вывод.

Итак, я делаю это:

command | tee out.txt
ST=$?

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

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

Ответы [ 15 ]

2 голосов
/ 15 мая 2017

Самый простой способ сделать это в обычном 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

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

2 голосов
/ 17 февраля 2016

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

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

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

1 голос
/ 04 мая 2016

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

$ # 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

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

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

1 голос
/ 15 января 2016

Основываясь на ответе @ 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
1 голос
/ 31 марта 2015

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

% 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).

...