Как сохранить стандартную ошибку в переменной в скрипте Bash



допустим, у меня есть такой скрипт:



useless.sh



echo "This Is Error" 1>&2
echo "This Is Output"


и у меня есть другой скрипт:



alsoUseless.sh



./useless.sh | sed 's/Output/Useless/'


Я хочу захватить "это ошибка", или любой другой stderr от useless.sh, в переменную.
Назовем это ошибкой.



обратите внимание, что я использую stdout для чего-то. Я хочу продолжать использовать stdout, поэтому перенаправление stderr в stdout в этом случае не полезно.



так, в принципе, я хочу сделать



./useless.sh 2> $ERROR | ...


но это явно не работает.



Я также знаю, что я мог бы сделать



./useless.sh 2> /tmp/Error
ERROR=`cat /tmp/Error`


но это некрасиво и ненужно.



к сожалению, если здесь не будет ответов, это то, что мне придется сделать.



Я надеюсь, что есть другой путь.



у кого-нибудь есть лучше идеи?

1046   14  

14 ответов:

было бы аккуратнее захватить файл ошибки таким образом:

ERROR=$(</tmp/Error)

оболочка распознает это и не должна работать'cat ' для получения данных.

большой вопрос трудно. Я не думаю, что есть простой способ сделать это. Вам нужно будет построить весь конвейер в подшелле, в конечном итоге отправив его окончательный стандартный вывод в файл, чтобы вы могли перенаправить ошибки на стандартный вывод.

ERROR=$( { ./useless.sh | sed s/Output/Useless/ > outfile; } 2>&1 )

обратите внимание, что требуется точка с запятой (в классические снаряды-Борн, Корн-наверняка; вероятно, в Баш тоже). В '{} ' выполняет перенаправление ввода-вывода по вложенным командам. Как написано, он будет захватывать ошибки из sed тоже.

(формально непроверенный код-использование на свой страх и риск.)

alsoUseless.sh

это позволит вам передать вывод вашего useless.sh скрипт с помощью команды, такие как sed и сохранить stderr в переменной с именем error. Результат работы трубы отправляется на stdout для отображения или для передачи в другую команду.

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

#!/bin/bash

exec 3>&1 4>&2 #set up extra file descriptors

error=$( { ./useless.sh | sed 's/Output/Useless/' 2>&4 1>&3; } 2>&1 )

echo "The message is \"${error}.\""

exec 3>&- 4>&- # release the extra file descriptors

Перенаправленный stderr в stdout, stdout в /dev / null, а затем используйте обратные палочки или $() для захвата перенаправленного stderr:

ERROR=$(./useless.sh 2>&1 >/dev/null)

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

if result=$(useless.sh 2>&1); then
    stdout=$result
else
    rc=$?
    stderr=$result
fi

работает для общего сценария, где вы ожидаете либо правильный вывод в случае успеха, либо диагностическое сообщение на stderr в случае сбоя.

обратите внимание, что операторы управления оболочки уже проверяют $? под капот, так что все, что выглядит как

cmd
if [ $? -eq 0 ], then ...

это просто неуклюжий, однообразный способ сказать

if cmd; then ...
# command receives its input from stdin.
# command sends its output to stdout.
exec 3>&1
stderr="$(command </dev/stdin 2>&1 1>&3)"
exitcode="${?}"
echo "STDERR: $stderr"
exit ${exitcode}

вот как я это сделал:

#
#  - name of the (global) variable where the contents of stderr will be stored
#  - command to be executed
#
captureStderr()
{
    local tmpFile=$(mktemp)

     2> $tmpFile

    eval "=$(< $tmpFile)"

    rm $tmpFile
}

пример использования :

captureStderr err "./useless.sh"

echo -$err-

Это тут использовать временный файл. Но, по крайней мере, уродливый материал завернут в функцию.

Это интересная проблема, к которой я надеялся, что было элегантное решение. К сожалению, я получаю решение, подобное г-ну Леффлеру, но я добавлю, что вы можете вызвать бесполезную изнутри функцию Bash для улучшения читаемости:

#!/bin/bash

function useless {
    /tmp/useless.sh | sed 's/Output/Useless/'
}

ERROR=$(useless)
echo $ERROR

все другие виды перенаправления вывода должны быть поддержаны временным файлом.

$ b=$( ( a=$( (echo stdout;echo stderr >&2) ) ) 2>&1 )
$ echo "a=>$a b=>$b"
a=>stdout b=>stderr

этот пост помог мне придумать аналогичное решение для своих собственных целей:

MESSAGE=`{ echo $ERROR_MESSAGE | format_logs.py --level=ERROR; } 2>&1`

затем, пока наше сообщение не является пустой строкой, мы передаем его другим вещам. Это позволит нам знать, если наши format_logs.py не удалось с каким-то исключением python.

захват и печать stderr

ERROR=$( ./useless.sh 3>&1 1>&2 2>&3 | tee /dev/fd/2 )

распад

можно использовать $() для захвата stdout, но вы хотите захватить stderr вместо этого. Итак, вы меняете stdout и stderr. Использование fd 3 в качестве временного хранилища в стандартном алгоритме подкачки.

если вы хотите захватить и распечатать использовать tee сделать дубликат. В этом случае вывод tee будет захвачен $() вместо того, чтобы перейти к консоли, но stderr (of tee) все равно будет идти на консоль, поэтому мы используем это в качестве второго выхода для tee через специальный файл /dev/fd/2 С tee ожидает путь к файлу, а не номер fd.

Примечание: это очень много перенаправлений в одной строке и порядок имеет значение. $() захватывает stdout из tee в конце трубопровода и самого трубопровода маршруты stdout из ./useless.sh к stdin tee после того, как мы поменяли stdin и stdout на ./useless.sh.

использование stdout of ./useless.sh

ОП сказал, что он все еще хотел использовать (а не просто печатать) stdout, например ./useless.sh | sed 's/Output/Useless/'.

нет проблем просто сделать это перед заменой stdout и stderr. Я рекомендую переместить его в функцию или файл (also-useless.sh) и называя это вместо ./useless.sh в строке выше.

однако, если вы хотите захватить stdout и stderr, то я думаю, что вы должны вернуться к временным файлам потому что $() будет делать только по одному за раз, и это делает подобласть, из которой вы не можете возвращать переменные.

если вы хотите обойти использование временного файла, вы можете использовать подстановку процесса. Я еще не совсем понял, как это работает. Это была моя первая попытка:

$ .useless.sh 2> >( ERROR=$(<) )
-bash: command substitution: line 42: syntax error near unexpected token `)'
-bash: command substitution: line 42: `<)'

тогда я попробовал

$ ./useless.sh 2> >( ERROR=$( cat <() )  )
This Is Output
$ echo $ERROR   # $ERROR is empty
$ ./useless.sh 2> >( cat <() > asdf.txt )
This Is Output
$ cat asdf.txt
This Is Error

так что процесс подстановки делает вообще правильно... к сожалению, всякий раз, когда я обернуть STDIN внутри >( ) С $() в попытке захватить это в переменную, я теряю содержание $(). Я думаю, что это потому, что $() запускает подпроцесс, который больше не имеет доступа к файловому дескриптору в /dev/fd, который принадлежит родительскому процессу.

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

в zsh:

{ . ./useless.sh > /dev/tty } 2>&1 | read ERROR
$ echo $ERROR
( your message )

POSIX

STDERR можно захватить с помощью магии перенаправления:

$ { error=$( { { ls -ld /XXXX /bin | tr o Z ; } 1>&3 ; } 2>&1); } 3>&1
lrwxrwxrwx 1 rZZt rZZt 7 Aug 22 15:44 /bin -> usr/bin/

$ echo $error
ls: cannot access '/XXXX': No such file or directory

обратите внимание, что трубопровод STDOUT команды (здесь ls) делается внутри самого сокровенного {}. Если вы выполняете простую команду (например, не трубу), вы можете удалить эти внутренние скобки.

вы не можете трубить вне команды, поскольку трубопровод делает подоболочку в bash и zsh, и присвоение переменной в подобласти не будет доступно для текущая оболочка.

Баш

на bash, было бы лучше не предполагать, что файловый дескриптор 3 не используется:

{ error=$( { { ls -ld /XXXX /bin | tr o Z ; } 1>&$tmp ; } 2>&1); } {tmp}>&1; 
exec {tmp}>&-  # With this syntax the FD stays open

обратите внимание, что это не работает в zsh.


спасибо ответ для общей идеи.

на ошибок команды:

execute [INVOKING-FUNCTION] [COMMAND]

execute () {
    error=$( 2>&1 >/dev/null)

    if [ $? -ne 0 ]; then
        echo ": $error"
        exit 1
    fi
}

вдохновение в бережливом производстве:

Comments

    Ничего не найдено.