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ć stderrniektórych commanddo varmożna zrobić
{ var="$( { command; } 2>&1 1>&3 3>&- )"; } 3>&1;
Potem masz wszystko:
echo "command gives $? and stderr '$var'";
Jeśli commandjest 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 -naliasy „$ 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>&1przekierowuje stderrdo przechwytywania danych wyjściowych$(..)
1>&3przekierowuje stdoutz przechwytywania wyjścia z $(..)powrotem do „zewnętrznego”, stdoutktóre zostało zapisane w deskryptorze pliku 3. Zauważ, że stderrnadal 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 commandnagle nie pojawia się jakiś nieznany deskryptor otwartego pliku. Zwróć uwagę, że zewnętrzna powłoka nadal ma otwarty FD 3, ale commandgo nie zobaczy.
- To ostatnie jest ważne, ponieważ niektóre programy
lvmnarzekają na nieoczekiwane deskryptory plików. I lvmnarzeka 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>&1jako argument, 9po 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-fdnie 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 || returnto 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
bashtak 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
evalsą one używane w bezpieczny sposób. Zwykle evaljest 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)