Для удобства читателя этот рецепт здесь
- может быть повторно использован как 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
-функцию (вероятно, требуется версия 3 и выше для local -n
):
: catch-stderr var cmd [args..]
catch-stderr() { local -n v="$1"; shift && { v="$("$@" 2>&1 1>&3 3>&-)"; } 3>&1; }
Разъяснения:
local -n
псевдонимы "$ 1" (переменная для catch-stderr
)
3>&1
использует файловый дескриптор 3 для сохранения там стандартных точек
{ 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 '"$@"' "$fd1>&1" "1>&$fd2" "$fd2>&-")";
eval exec "$fd2>&-";
}
Примечание по безопасности: Первые 3 аргумента catch-var-from-fd-by-fd
не должны приниматься у третьей стороны. Всегда передавайте их явно «статично».
Так нет-нет-нет catch-var-from-fd-by-fd $var $fda $fdb $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
- это просто способ предотвратить неприятные ошибки, если вы забудете указать правильное количество аргументов. Возможно, завершение оболочки будет другим способом (но это затрудняет тестирование из командной строки).
- Рутина была написана так, чтобы ее было легче понять. Можно переписать функцию так, чтобы она не нуждалась в
exec
, но тогда она становится действительно ужасной.
- Эта подпрограмма может быть переписана также для не
bash
, так что нет необходимости в local -n
. Однако, тогда вы не можете использовать локальные переменные, и это становится ужасно!
- Также обратите внимание, что
eval
используются безопасным образом. Обычно eval
считается опасным. Однако в этом случае это не более зло, чем использование "$@"
(для выполнения произвольных команд). Однако, пожалуйста, не забудьте использовать точное и правильное цитирование, как показано здесь (иначе это становится очень очень опасно ).