Chcę wykonać długo działającą komendę w Bash, i zarówno przechwytują jej status wyjścia, jak i tee wyjściowe.
Więc robię to:
command | tee out.txt
ST=$?
Problem polega na tym, że zmienna ST przechwytuje status wyjścia, tee
a nie polecenia. Jak mogę to rozwiązać?
Zauważ, że polecenie działa długo i przekierowanie wyjścia do pliku, aby wyświetlić go później, nie jest dla mnie dobrym rozwiązaniem.
bash
shell
error-handling
pipe
Lecieć jak po sznurku
źródło
źródło
Odpowiedzi:
Istnieje wewnętrzna zmienna Bash o nazwie
$PIPESTATUS
; jest to tablica, która przechowuje status wyjścia każdego polecenia w ostatnim szeregu poleceń na pierwszym planie.Lub inną alternatywą, która działa również z innymi powłokami (jak zsh) byłoby włączenie pipefail:
Pierwsza opcja nie działa z
zsh
powodu nieco innej składni.źródło
exit ${PIPESTATUS[0]}
.set -o pipefail
Pomocne jest używanie bashźródło
( set -o pipefail; command | tee out.txt ); ST=$?
set -o pipefail
a następnie wykonać polecenie, a następnie natychmiast zrobić,set +o pipefail
aby wyłączyć tę opcję.-o pipefail
wiedziałby, czy rura nie powiedzie się, ale jeśli zarówno „polecenia” i „tee” nie, mógłby otrzymać kod wyjścia z „tee”.Głupie rozwiązanie: łączenie ich przez nazwaną potok (mkfifo). Następnie polecenie można uruchomić na drugim miejscu.
źródło
mkfifo
i mogą wymagać,mknod -p
jeśli dobrze pamiętam.mkfifo
lubmknod -p
: w moim przypadku było właściwe polecenie utworzenia pliku potokumknod FILE_NAME p
.Istnieje tablica, która podaje status wyjścia każdego polecenia w potoku.
źródło
To rozwiązanie działa bez użycia specyficznych funkcji bash lub plików tymczasowych. Premia: w końcu status wyjścia jest tak naprawdę statusem wyjścia, a nie ciągiem znaków w pliku.
Sytuacja:
chcesz status wyjścia z
someprog
i wyjście zfilter
.Oto moje rozwiązanie:
Zobacz moją odpowiedź na to samo pytanie na unix.stackexchange.com, aby uzyskać szczegółowe wyjaśnienie i alternatywę bez podpowłoki i niektórych zastrzeżeń.
źródło
Łącząc
PIPESTATUS[0]
wynikexit
polecenia i jego wykonanie w podpowłoce, można uzyskać bezpośredni dostęp do zwracanej wartości początkowego polecenia:command | tee ; ( exit ${PIPESTATUS[0]} )
Oto przykład:
da tobie:
return value: 1
źródło
VALUE=$(might_fail | piping)
która nie ustawi PIPESTATUSA w powłoce master, ale ustawi poziom błędu. Korzystając z:VALUE=$(might_fail | piping; exit ${PIPESTATUS[0]})
Chcę dostać chciałem.command_might_fail | grep -v "line_pattern_to_exclude" || exit ${PIPESTATUS[0]}
w przypadku nie tee ale filtrowania grepChciałem więc udzielić odpowiedzi podobnej do lesmany, ale myślę, że moja jest może nieco prostsza i nieco bardziej korzystna dla powłoki z czystej powłoki Bourne'a:
Myślę, że najlepiej to wyjaśnić od środka - polecenie 1 wykona i wydrukuje swoje standardowe wyjście na stdout (deskryptor pliku 1), a następnie, gdy to zrobi, printf wykona i wydrukuje kod wyjścia icommand1 na swoim standardowym wyjściu, ale to standardowe wyjście jest przekierowane do deskryptor pliku 3.
Gdy polecenie1 jest uruchomione, jego standardowe wyjście jest przesyłane potokowo do polecenia2 (dane wyjściowe printf nigdy nie powodują przejścia do polecenia2, ponieważ wysyłamy go do deskryptora pliku 3 zamiast 1, co odczytuje potok). Następnie przekierowujemy dane wyjściowe polecenia 2 do deskryptora pliku 4, tak aby pozostało ono również poza deskryptorem pliku 1 - ponieważ chcemy, aby deskryptor pliku 1 był wolny przez nieco później, ponieważ sprowadzimy dane wyjściowe printf z deskryptora pliku 3 z powrotem do deskryptora pliku 1 - ponieważ to jest to, co przechwyci podstawienie polecenia (backticks) i to zostanie umieszczone w zmiennej.
Ostatnia odrobina magii jest pierwsza
exec 4>&1
zrobiliśmy to jako osobne polecenie - otwiera deskryptor pliku 4 jako kopię standardowego wejścia zewnętrznej powłoki. Podstawienie polecenia uchwyci wszystko, co jest napisane na zewnątrz z perspektywy poleceń w nim zawartych - ale ponieważ dane wyjściowe polecenia 2 przechodzą do deskryptora pliku 4, jeśli chodzi o podstawienie polecenia, podstawienie polecenia nie przechwytuje go - jednak raz pobiera „z” podstawienia polecenia, nadal skutecznie przechodzi do ogólnego deskryptora pliku skryptu 1.(
exec 4>&1
Musi to być osobne polecenie, ponieważ wielu powszechnym powłokom się to nie podoba, gdy próbujesz zapisać deskryptor pliku w podstawieniu polecenia, które jest otwierane w poleceniu „zewnętrznym”, które używa podstawienia. Więc to jest najprostszy przenośny sposób to zrobić.)Możesz na to spojrzeć w mniej techniczny i bardziej zabawny sposób, tak jakby wyjścia poleceń przeskakiwały między sobą: polecenie 1 potokuje do polecenia 2, następnie dane wyjściowe printf przeskakują nad poleceniem 2, aby polecenie 2 go nie złapało, a następnie dane wyjściowe polecenia 2 przeskakują w górę i w dół od podstawienia polecenia, tak jak printf ląduje w samą porę, aby zostać przechwyconym przez podstawienie, tak że kończy się w zmiennej, a wyjście polecenia 2 idzie wesoło zapisywane na standardowe wyjście, tak jak w normalnej rurze.
Ponadto, jak rozumiem,
$?
nadal będzie zawierał kod powrotu drugiego polecenia w potoku, ponieważ przypisania zmiennych, podstawienia poleceń i polecenia złożone są skutecznie przezroczyste dla kodu powrotu polecenia wewnątrz nich, więc status powrotu polecenie2 powinno zostać rozpowszechnione - to, i nie musi definiować dodatkowej funkcji, dlatego myślę, że może to być nieco lepsze rozwiązanie niż zaproponowane przez lesmana.Zgodnie ze wspomnianymi przez lesmana zastrzeżeniami możliwe jest, że polecenie1 kiedyś skończy z deskryptorami plików 3 lub 4, więc aby być bardziej niezawodnym, wykonaj następujące czynności:
Zauważ, że używam poleceń złożonych w moim przykładzie, ale podpowłoki (używanie
( )
zamiast{ }
będzie również działać, chociaż może być mniej wydajne).Polecenia dziedziczą deskryptory plików z procesu, który je uruchamia, więc cała druga linia odziedziczy deskryptor pliku czwarty, a następnie złożone polecenie
3>&1
odziedziczy deskryptor pliku trzy. Dlatego4>&-
upewnia się, że wewnętrzna komenda złożona nie odziedziczy deskryptora pliku cztery, i3>&-
nie odziedziczy deskryptora pliku trzy, więc polecenie1 otrzymuje „czystsze”, bardziej standardowe środowisko. Możesz także przesunąć wnętrze4>&-
obok3>&-
, ale myślę, że nie ograniczaj jego zakresu tak bardzo, jak to możliwe.Nie jestem pewien, jak często rzeczy bezpośrednio używają deskryptora pliku trzeciego i czwartego - myślę, że większość programów używa syscall, które zwracają nieużywane w danym momencie deskryptory plików, ale czasami kod zapisuje bezpośrednio do deskryptora pliku 3, ja zgadnij (mógłbym sobie wyobrazić program sprawdzający deskryptor pliku, aby sprawdzić, czy jest otwarty, i używający go, jeśli jest, lub zachowujący się odpowiednio inaczej, jeśli nie jest). To drugie jest więc prawdopodobnie najlepsze, o którym należy pamiętać i stosować w przypadkach ogólnego przeznaczenia.
źródło
W Ubuntu i Debian możesz
apt-get install moreutils
. Zawiera narzędzie zwane,mispipe
które zwraca status wyjścia pierwszego polecenia w potoku.źródło
W przeciwieństwie do odpowiedzi @ cODAR, zwraca oryginalny kod wyjścia pierwszego polecenia, a nie tylko 0 dla sukcesu i 127 dla niepowodzenia. Ale jak zauważył @Chaoran, możesz po prostu zadzwonić
${PIPESTATUS[0]}
. Ważne jest jednak, aby wszystko było ujęte w nawiasy.źródło
Poza bash możesz:
Jest to przydatne na przykład w skryptach ninja, w których powinna znajdować się powłoka
/bin/sh
.źródło
PIPESTATUS [@] należy skopiować do tablicy natychmiast po powrocie polecenia potoku. Wszelkie odczyty PIPESTATUS [@] kasują zawartość. Skopiuj go do innej tablicy, jeśli planujesz sprawdzić status wszystkich poleceń potoku. „$?” to ta sama wartość, co ostatni element „$ {PIPESTATUS [@]}”, a odczytanie go niszczy „$ {PIPESTATUS [@]}”, ale nie do końca to zweryfikowałem.
To nie zadziała, jeśli potok znajduje się w podpowłoce. Aby znaleźć rozwiązanie tego problemu,
zobacz bash pipestatus w poleceniu sprawdzonym wstecz?
źródło
Najprostszym sposobem na zrobienie tego w zwykłym bashu jest użycie podstawienia procesu zamiast potoku. Istnieje kilka różnic, ale prawdopodobnie nie mają one większego znaczenia dla Twojego przypadku użycia:
pipefail
Opcji orazPIPESTATUS
zmienne mają znaczenia dla zastąpienia procesu.Dzięki podstawieniu procesu bash po prostu uruchamia proces i zapomina o nim, nie jest nawet widoczny w
jobs
.Wspomniane różnice na bok
consumer < <(producer)
iproducer | consumer
są zasadniczo równoważne.Jeśli chcesz odwrócić, który z nich jest „głównym” procesem, po prostu odwróć polecenia i kierunek podstawienia na
producer > >(consumer)
. W Twoim przypadku:Przykład:
Jak powiedziałem, istnieją różnice w wyrażeniu potoku. Proces może nigdy nie zostać zatrzymany, chyba że jest wrażliwy na zamykanie się rury. W szczególności może ciągle pisać rzeczy na standardowe wyjście, co może być mylące.
źródło
Rozwiązanie Pure Shell:
A teraz z drugim
cat
zastąpionym przezfalse
:Pamiętaj, że pierwszy kot również zawiedzie, ponieważ jego stdout zostaje na nim zamknięty. Kolejność nieudanych poleceń w dzienniku jest poprawna w tym przykładzie, ale nie należy na nim polegać.
Ta metoda pozwala na przechwytywanie stdout i stderr dla poszczególnych poleceń, dzięki czemu możesz zrzucić to również do pliku dziennika, jeśli wystąpi błąd, lub po prostu go usunąć, jeśli nie wystąpi błąd (jak wyjście dd).
źródło
Oprzyj się na odpowiedzi @ brian-s-wilson; ta funkcja pomocnika bash:
używane w ten sposób:
1: get_bad_things musi odnieść sukces, ale nie powinno generować żadnych wyników; ale chcemy zobaczyć wyniki, które produkuje
2: cały potok musi się powieść
źródło
Czasami użycie polecenia zewnętrznego może być prostsze i bardziej zrozumiałe niż zagłębianie się w szczegóły bash. rurociąg , z minimalnym skryptowy proces języka execline , wyjścia z kodem powrotu drugiego polecenia *, podobnie jak
sh
rurociąg robi, ale w przeciwieństwiesh
, umożliwia zmianę kierunku rury, tak, że możemy uchwycić kod powrotny producenta proces (poniżej wszystko jest wsh
wierszu poleceń, ale zexecline
zainstalowanym):Użycie
pipeline
ma takie same różnice w stosunku do natywnych potoków bash, jak podstawienie procesu bash zastosowane w odpowiedzi # 43972501 .* Właściwie
pipeline
wcale nie wychodzi, chyba że wystąpi błąd. Wykonuje się na drugie polecenie, więc jest to drugie polecenie, które powraca.źródło