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

Скажем, у меня есть script, как показано ниже:

useless.sh

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

И у меня есть еще одна оболочка script:

alsoUseless.sh

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

Я хочу записать "This Is Error" или любой другой stderr из useless.sh в переменную. Позвольте называть его ОШИБКОЙ.

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

Итак, в основном, я хочу сделать

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

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

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

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

но это уродливое и ненужное.

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

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

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

Ответ 1

Было бы лучше записать файл ошибок так:

ERROR=$(</tmp/Error)

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

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

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

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

ВНИМАНИЕ: Формально непроверенный код - используйте на свой страх и риск.

Ответ 2

alsoUseless.sh

Это позволит вам вывести вывод вашего useless.sh script с помощью команды типа 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

Ответ 3

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

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

Ответ 4

Для этого вопроса много дубликатов, многие из которых имеют несколько более простой сценарий использования, когда вы не хотите одновременно записывать 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 ...

Ответ 5

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

Ответ 6

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

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

    $2 2> $tmpFile

    eval "$1=$(< $tmpFile)"

    rm $tmpFile
}

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

captureStderr err "./useless.sh"

echo -$err-

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

Ответ 7

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

#!/bin/bash

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

ERROR=$(useless)
echo $ERROR

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

Ответ 8

Для удобства читателя этот рецепт здесь

  • может быть повторно использован как oneliner для захвата stderr в переменную
  • по-прежнему дает доступ к коду возврата команды
  • Жертва временного файлового дескриптора 3 (который вы, конечно, можете изменить)
  • И не выставляет этот временный файловый дескриптор внутренней команде

Если вы хотите перехватить stderr какой-либо command в var вы можете сделать

{ var="$( { command; } 2>&1 1>&3 3>&- )"; } 3>&1;

После этого у вас есть все:

echo "command gives $? and stderr '$var'";

Если command простая (а не что-то вроде a | b), вы можете оставить внутреннее {} стороне:

{ var="$(command 2>&1 1>&3 3>&-)"; } 3>&1;

Обернутый в простой многоразовый bash -function (вероятно, требуется версия 3 и выше для local -n):

: catch-stderr var cmd [args..]
catch-stderr() { local -n v="$1"; shift && { v="$("[email protected]" 2>&1 1>&3 3>&-)"; } 3>&1; }

Разъяснение:

  • local -n псевдонимы local -n "$ 1" (это переменная для catch-stderr)
  • 3>&1 использует файловый дескриптор 3 для сохранения там стандартных точек
  • { command; } { command; } (или "$ @") затем выполняет команду в выходных данных захвата $(..)
  • Обратите внимание, что здесь важен точный порядок (неправильный способ неправильно перемешивает дескрипторы файлов):
    • 2>&1 перенаправляет stderr на вывод, захватывая $(..)
    • 1>&3 перенаправляет стандартный stdout с вывода, захватывающего $(..) на "внешний" stdout который был сохранен в файловом дескрипторе 3. Обратите внимание, что stderr прежнему ссылается на то место, на которое указывал FD 1: на вывод захвата $(..)
    • 3>&- затем закрывает файловый дескриптор 3, так как он больше не нужен, так что command внезапно не обнаруживает какой-то неизвестный открытый файловый дескриптор. Обратите внимание, что внешняя оболочка все еще имеет открытый FD 3, но command ее не увидит.
    • Последнее важно, потому что некоторые программы, такие как lvm жалуются на неожиданные файловые дескрипторы. И lvm жалуется на stderr - именно то, что мы собираемся захватить!

Вы можете поймать любой другой дескриптор файла с этим рецептом, если вы адаптируетесь соответственно. Конечно, кроме файлового дескриптора 1 (здесь логика перенаправления будет неправильной, но для файлового дескриптора 1 вы можете просто использовать var=$(command) как обычно).

Обратите внимание, что это жертвует файловым дескриптором 3. Если вам понадобится этот файловый дескриптор, смело меняйте номер. Но учтите, что некоторые оболочки (начиная с 1980-х годов) могут понимать 99>&1 как аргумент 9 за которым следуют 9>&1 (это не проблема для bash).

Также обратите внимание, что не так легко сделать этот FD 3 настраиваемым через переменную. Это делает вещи очень нечитаемыми:

: catch-var-from-fd-by-fd variable fd-to-catch fd-to-sacrifice command [args..]
catch-var-from-fd-by-fd()
{
local -n v="$1";
local fd1="$2" fd2="$3";
shift 3 || return;

eval exec "$fd2>&1";
v="$(eval '"[email protected]"' "$fd1>&1" "1>&$fd2" "$fd2>&-")";
eval exec "$fd2>&-";
}

Примечание по безопасности: Первые 3 аргумента для catch-var-from-fd-by-fd не должны приниматься сторонними разработчиками. Всегда дайте их явно "статичным" способом.

Так что никакая команда -n o -n o catch-var-from-fd-by-fd $var $fda $fdb $command никогда не делает этого!

Если вам случится передать имя переменной переменной, по крайней мере сделайте это следующим образом: local -n var="$var"; catch-var-from-fd-by-fd var 3 5 $command local -n var="$var"; catch-var-from-fd-by-fd var 3 5 $command

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

Заметки:

  • catch-var-from-fd-by-fd var 2 3 cmd.. совпадает с catch-stderr var cmd..
  • shift || return shift || return - это просто способ предотвратить неприятные ошибки, если вы забудете указать правильное количество аргументов. Возможно, завершение оболочки будет другим способом (но это затрудняет тестирование из командной строки).
  • Рутина была написана так, чтобы ее было легче понять. Можно переписать функцию так, чтобы она не нуждалась в exec, но тогда она становится действительно уродливой.
  • Эта процедура также может быть переписана для non- bash так что нет необходимости в local -n. Однако тогда вы не можете использовать локальные переменные, и это становится ужасно!
  • Также обратите внимание, что eval используются безопасным образом. Обычно eval считается опасным. Однако в этом случае это не более зло, чем использование "[email protected]" (для выполнения произвольных команд). Однако, пожалуйста, не забудьте использовать точное и правильное цитирование, как показано здесь (иначе это становится очень и очень опасным).

Ответ 9

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

Ответ 10

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

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

Тогда, пока наше СООБЩЕНИЕ не является пустой строкой, мы передаем ее другим вещам. Это сообщит нам, если наш format_logs.py не удалось с каким-то исключением python.

Ответ 11

Capture AND Print stderr

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

Сломать

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

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

ПРИМЕЧАНИЕ. Это очень много перенаправлений в одной строке, и порядок имеет значение. $() захватывает stdout tee в конце конвейера, и сам трубопровод направляет stdout ./useless.sh на stdin tee мы заменили stdin и stdout для ./useless.sh.

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

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

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

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

Ответ 12

Простое решение

{ ERROR=$(./useless.sh 2>&1 1>&$out); } {out}>&1
echo "-"
echo $ERROR

Будет производить:

This Is Output
-
This Is Error

Ответ 13

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

$ .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, к сожалению, я, похоже, не могу манипулировать им так, как хочу.

Ответ 14

В zsh:

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

Ответ 15

Для проверки ошибок ваших команд:

execute [INVOKING-FUNCTION] [COMMAND]

execute () {
    function="${1}"
    command="${2}"
    error=$(eval "${command}" 2>&1 >"/dev/null")

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

Вдохновленный в Бережливом производстве:

Ответ 16

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.


Благодаря этому ответу на общую идею.