bash pipestatus в команде с обратным каналом?

В bash, если я выполню пару команд, соединенных внутри обратных тиков, как я могу узнать состояние выхода первой команды?

т.е. в этом случае я пытаюсь получить "1". который я могу получить через PIPESTATUS[0], если я не использую обратные пометки, но который, кажется, не работает, когда я хочу сохранить вывод:

## PIPESTATUS[0] works to give me the exit status of 'false':
$ false | true;
$ echo $? ${PIPESTATUS[0]} ${PIPESTATUS[1]};
0 1 0

## doesn't work:
$ a=`false | true`;
$ echo $? ${PIPESTATUS[0]} ${PIPESTATUS[1]};
0 0

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

$ myvar=` ./someprogram | tail -1 `;
$ if [ "what do i put here" ]; then echo "program failed!"; fi

В идеале я также хотел бы понять, что происходит, а не только каков ответ.

Благодарю.

3 ответа

Решение

Попробуйте установить pipefail вариант. Возвращает последнюю команду конвейера, которая потерпела неудачу. Один пример:

Сначала я отключаю это:

set +o pipefail

Создать perl сценарий (script.pl) проверить трубопровод:

#!/usr/bin/env perl

use warnings;
use strict;

if ( @ARGV ) { 
    die "Line1\nLine2\nLine3\n";
}
else {
    print "Line1\nLine2\nLine3\n";
}

Запустите в командной строке:

myvar=`perl script.pl | tail -1`
echo $? "$myvar"

Это дает:

0 Line3

Вроде правильно, давай посмотрим с pipefail включено:

set -o pipefail

И запустите команду:

myvar=`perl script.pl fail 2>&1 | tail -1`
echo $? "$myvar"

Это дает:

255 Line3

Мое решение заключалось в использовании fifos и встроенного в bash "coproc" для получения сообщений и статуса от каждой команды в конвейере. Я никогда не использовал FIFO раньше. (о боже, в следующий раз я использую BashEclipse на Fedora). Он превратился в обобщенный механизм управления любой командой pipe. Я решил проблему, но не в 10 или 20 строк кода. больше как 200 за надежное решение для повторного использования (мне понадобилось три дня).

Я делюсь своими заметками:

* stderr for all pipe commands goes to the fifos.  
  If you want to get messages from stdout, you must redirect '1>&2', like this:  
  PIPE_ARRAY=("cat ${IMG}.md5" "cut -f1 -d\" \" 1>&2")  
  You must put "2>/fifo" first. Otherwise it won\'t work. example:  
    cat ${IMG}.md5 | cut -f1 -d' ' 1>&2  
  becomes:  
    cat ${IMG}.md5 2>/tmp/fifo_s0 | cut -f1 -d" " 2>/tmp/fifo_s1 1>&2 ; PSA=( "${PIPESTATUS[@]}" )

* With more tha one fifo, I found that you must read each fifo in turn.  
  When "fifo1" gets written to, "fifo0" reads are blocked until you read "fifo1"  
  I did\'nt use any special tricks like "sleep", "cat", or extra file descriptors  
  to keep the fifos open.  

* PIPESTATUS[@] must be copied to an array immediately after the pipe command returns.  
  _Any_ reads of PIPESTATUS[@] will erase the contents. Super volatile !  
  "manage_pipe()" appends '; PSA=( "${PIPESTATUS[@]}" )' to the pipe command string  
  for this reason. "$?" is the same as the last element of "${PIPESTATUS[@]}",  
  and reading it seems to destroy "${PIPESTATUS[@]}", but it's not absolutly verifed.

run_pipe_cmd() {  
  declare -a PIPE_ARRAY MSGS  
  PIPE_ARRAY=("dd if=${gDEVICE} bs=512 count=63" "md5sum -b >${gBASENAME}.md5")  
  manage_pipe PIPE_ARRAY[@] "MSGS"  # (pass MSGS name, not the array) 
}  
manage_pipe () {
  # input  - $1 pipe cmds array, $2 msg retvar name
  # output - fifo msg retvar
  # create fifos, fifo name array, build cnd string from $1 (re-order redirection if needed)
  # run coprocess 'coproc execute_pipe FIFO[@] "$CMDSTR"'
  # call 'read_fifos FIFO[@] "M" "S"' (pass names, not arrays for $2 and $3)
  # calc last_error, call _error, _errorf
  # set msg retvar values (eval ${2}[${i}]='"${Msg[${i}]}"')
}
read_fifos() {  
  # input  - $1 fifo array, $2 msg retvar name, $3 status retvar name  
  # output - msg, status retvars  
  # init local fifo_name, pipe_cmd_status, msg arrays  
  # do read loop until all 'quit' msgs are received
  # set msg, status retvar values (i.e. eval ${3}[${i}]='"${Status[${i}]}"' 
}
execute_pipe() {  
  # $1 fifo array, $2 cmdstr, $3 msg retvar, $4 status retvar   
  # init local fifo_name, pipe_cmd_status arrays
  # execute command string, get pipestaus  (eval "$_CMDSTR" 1>&2)
  # set fifo statuses from copy of PIPESTATUS
  # write 'status', 'quit' msgs to fifo  
}

Проблема в том, что backticks запускают вложенную оболочку. У вашей суб-оболочки есть свой ${PIPESTATUS[@]} массив, но это не сохраняется в родительской оболочке. Вот трюк, чтобы засунуть его в выходную переменную $a а затем получить его в новый массив с именем ${PIPESTATUS2[@]}:

## PIPESTATUS[0] works to give me the exit status of 'false':
$ false | true
$ echo $? ${PIPESTATUS[0]} ${PIPESTATUS[1]}
0 1 0

## Populate a $PIPESTATUS2 array:
$ a=`false | true; printf :%s "${PIPESTATUS[*]}"`
$ ANS=$?; PIPESTATUS2=(${a##*:})
$ [ -n "${a%:*}" ] && a="${a%:*}" && a="${a%$'\n'}" || a=""
$ echo $ANS ${PIPESTATUS2[0]} ${PIPESTATUS2[1]};
0 1 0

Это экономит суб-оболочки ${PIPESTATUS[@]} массив как разделенный пробелами список значений в конце $a и затем извлекает его, используя удаление подстроки переменной оболочки (см. более длинный пример и описание, которое я дал этому похожему вопросу). Третья строка нужна только если вы действительно хотите сохранить значение $a без дополнительных статусов (как если бы он был запущен как false | true в этом примере).

Другие вопросы по тегам