На користь читача цей рецепт тут
- може бути використаний повторно як 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 для збереження там точок stdout
{ 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 . Однак тоді ви не можете використовувати локальні змінні, і це стає надзвичайно негарно!
- Також зауважте, що
evals використовуються безпечно. Зазвичай evalвважається небезпечним. Однак у цьому випадку це не більше зла, ніж використання "$@"(для виконання довільних команд). Однак будь ласка, використовуйте точне та правильне цитування, як показано тут (інакше це стає дуже небезпечно ).
ERROR=$(./useless.sh | sed 's/Output/Useless/' 2>&1 1>/dev/ttyX)