Dla dobra czytelnika ten przepis tutaj
- może być ponownie użyty jako oneliner do przechwytywania stderr do zmiennej
- nadal daje dostęp do kodu zwrotnego polecenia
- Poświęca tymczasowy deskryptor pliku 3 (który możesz oczywiście zmienić)
- I nie ujawnia deskryptorów plików tymczasowych poleceniu wewnętrznemu
Jeśli chcesz złapać stderr
niektórych command
do var
można zrobić
{ var="$( { command; } 2>&1 1>&3 3>&- )"; } 3>&1;
Potem masz wszystko:
echo "command gives $? and stderr '$var'";
Jeśli command
jest to proste (nie coś w rodzaju a | b
), możesz zostawić wnętrze z {}
daleka:
{ var="$(command 2>&1 1>&3 3>&-)"; } 3>&1;
Zapakowany w łatwą funkcję wielokrotnego użytku bash
(prawdopodobnie potrzebuje wersji 3 i nowszych dla local -n
):
: catch-stderr var cmd [args..]
catch-stderr() { local -n v="$1"; shift && { v="$("$@" 2>&1 1>&3 3>&-)"; } 3>&1; }
Wyjaśnione:
local -n
aliasy „$ 1” (czyli zmienna dla catch-stderr
)
3>&1
używa deskryptora pliku 3, aby zapisać tam punkty wyjścia
{ command; }
(lub „$ @”) wykonuje polecenie w ramach przechwytywania danych wyjściowych $(..)
- Zwróć uwagę, że ważna jest tutaj dokładna kolejność (zrobienie tego w niewłaściwy sposób powoduje nieprawidłowe tasowanie deskryptorów plików):
2>&1
przekierowuje stderr
do przechwytywania danych wyjściowych$(..)
1>&3
przekierowuje stdout
z przechwytywania wyjścia z $(..)
powrotem do „zewnętrznego”, stdout
które zostało zapisane w deskryptorze pliku 3. Zauważ, że stderr
nadal odnosi się do miejsca, w którym wskazał wcześniej FD 1: Do przechwytywania wyjścia$(..)
3>&-
następnie zamyka deskryptor pliku 3, ponieważ nie jest już potrzebny, tak że command
nagle nie pojawia się jakiś nieznany deskryptor otwartego pliku. Zwróć uwagę, że zewnętrzna powłoka nadal ma otwarty FD 3, ale command
go nie zobaczy.
- To ostatnie jest ważne, ponieważ niektóre programy
lvm
narzekają na nieoczekiwane deskryptory plików. I lvm
narzeka stderr
- właśnie to, co uchwycimy!
Możesz złapać dowolny inny deskryptor pliku z tym przepisem, jeśli odpowiednio się dostosujesz. Z wyjątkiem oczywiście deskryptora pliku 1 (tutaj logika przekierowania byłaby błędna, ale dla deskryptora pliku 1 można po prostu użyć var=$(command)
jak zwykle).
Zauważ, że ten deskryptor pliku poświęca 3. Jeśli potrzebujesz tego deskryptora pliku, możesz zmienić jego numer. Należy jednak pamiętać, że niektóre powłoki (z lat 80. XX wieku) mogą być rozumiane 99>&1
jako argument, 9
po którym następuje 9>&1
(nie stanowi to problemu bash
).
Należy również zauważyć, że nie jest szczególnie łatwe skonfigurowanie tego FD 3 za pomocą zmiennej. To sprawia, że rzeczy są bardzo nieczytelne:
: 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>&-";
}
Uwaga dotycząca bezpieczeństwa: pierwsze 3 argumenty catch-var-from-fd-by-fd
nie mogą pochodzić od strony trzeciej. Zawsze podawaj je wyraźnie w „statyczny” sposób.
Więc nie catch-var-from-fd-by-fd $var $fda $fdb $command
, nie, nie, nigdy tego nie rób!
Jeśli zdarzy ci się przekazać zmienną nazwę zmiennej, przynajmniej zrób to w następujący sposób:
local -n var="$var"; catch-var-from-fd-by-fd var 3 5 $command
To nadal nie ochroni Cię przed każdym exploitem, ale przynajmniej pomoże wykryć i uniknąć typowych błędów skryptów.
Uwagi:
catch-var-from-fd-by-fd var 2 3 cmd..
jest taki sam jak catch-stderr var cmd..
shift || return
to tylko sposób na uniknięcie brzydkich błędów w przypadku, gdy zapomnisz podać poprawną liczbę argumentów. Być może zamknięcie powłoki byłoby innym sposobem (ale utrudnia to testowanie z wiersza poleceń).
- Rutyna została napisana tak, aby była łatwiejsza do zrozumienia. Można przepisać funkcję tak, że nie potrzebuje
exec
, ale wtedy robi się naprawdę brzydka.
- Ta procedura może zostać zmieniona
bash
tak dobrze, że nie ma takiej potrzeby local -n
. Jednak wtedy nie możesz używać zmiennych lokalnych i robi się to wyjątkowo brzydkie!
- Należy również pamiętać, że
eval
są one używane w bezpieczny sposób. Zwykle eval
jest uważany za niebezpieczny. Jednak w tym przypadku nie jest to bardziej złe niż używanie "$@"
(do wykonywania dowolnych poleceń). Jednak pamiętaj, aby używać dokładnych i poprawnych cytatów, jak pokazano tutaj (w przeciwnym razie staje się to bardzo niebezpieczne ).
ERROR=$(./useless.sh | sed 's/Output/Useless/' 2>&1 1>/dev/ttyX)