Przekieruj stderr wszystkich kolejnych poleceń za pomocą exec

43

Mam plik bash, który muszę przekierować wszystkie dane wyjściowe do jednego pliku, dziennika debugowania, a także do terminala. Muszę przekierować stdout i stderr do debugowania i zarejestrować je dla wszystkich poleceń w skrypcie.

Nie chcę dodawać 2>&1 | tee -a $DEBUGdo każdego polecenia w pliku. Mógłbym z tym żyć | tee -a $DEBUG.

Pamiętam, że był na to sposób z czymś takim exec 2>&1.

Obecnie używam czegoś takiego:

#!/bin/bash
DEBUGLOG=/tmp/debug
exec 2>&1
somecommand | tee -a $DEBUGLOG
somecommand2 | tee -a $DEBUGLOG
somecommand3 | tee -a $DEBUGLOG

ale to nie działa. Czy ktoś ma rozwiązanie / może wyjaśnić przyczynę?

Avi
źródło
1
W niektórych powłokach |&działa jako skrót 2>&1 |, jest co najmniej nieco wygodniejszy.
Kevin

Odpowiedzi:

39

Jeśli chodzi o rozwiązanie przekierowywania wielu poleceń jednocześnie:

#!/bin/bash
{
    somecommand 
    somecommand2
    somecommand3
} 2>&1 | tee -a $DEBUGLOG

Dlaczego oryginalne rozwiązanie nie działa: exec 2> i 1 przekieruje standardowe wyjście błędu na standardowe wyjście powłoki, która, jeśli uruchomisz skrypt z konsoli, będzie twoją konsolą. przekierowanie potoku dla poleceń przekieruje jedynie standardowe wyjście polecenia.

Z punktu widzenia somecommandstandardowego wyjścia przechodzi do podłączonego potoku, teea standardowy błąd trafia do tego samego pliku / pseudopliku, co standardowy błąd powłoki, który przekierowuje się do standardowego wyjścia powłoki, którym będzie konsoli, jeśli uruchamiasz program z konsoli.

Jedynym prawdziwym sposobem na wyjaśnienie tego jest zobaczenie, co się naprawdę dzieje:

Oryginalne środowisko twojej powłoki może wyglądać tak, jeśli uruchomisz ją z terminala:

stdin -> /dev/pts/42
stdout -> /dev/pts/42
stderr -> /dev/pts/42

Po przekierowaniu standardowego błędu do standardowego wyjścia ( exec 2>&1), ... zasadniczo nic nie zmieniasz. Ale jeśli przekierujesz standardowe wyjście skryptu do pliku, uzyskasz takie środowisko:

stdin -> /dev/pts/42
stdout -> /your/file
stderr -> /dev/pts/42

Następnie przekierowanie standardowego błędu powłoki na standardowe wyjście skończyłoby się tak:

stdin -> /dev/pts/42
stdout -> /your/file
stderr -> /your/file

Uruchomienie polecenia odziedziczy to środowisko. Jeśli uruchomisz polecenie i potokujesz je do tee, środowisko polecenia to:

stdin -> /dev/pts/42
stdout -> pipe:[4242]
stderr -> /your/file

Zatem standardowy błąd polecenia nadal dotyczy tego, co powłoka używa jako standardowego błędu.

Środowisko polecenia można zobaczyć, patrząc /proc/[pid]/fd: użyj, ls -laby wyświetlić także zawartość dowiązania symbolicznego. 0Plik tutaj jest standardowe wejście, 1to standardowe wyjście i 2jest błąd standardowy. Jeśli polecenie otworzy więcej plików (a większość programów tak robi), zobaczysz je również. Program może również wybrać przekierowanie lub zamknięcie swojego standardowego wejścia / wyjścia i ponowne użycie 0, 1oraz 2.

BatchyX
źródło
41

Możesz użyć exec w następujący sposób na górze skryptu:

exec > >(tee "$HOME/somefile.log") 2>&1

Na przykład:

#!/bin/bash -

exec > >(tee "$HOME/somefile.log") 2>&1

echo "$HOME"
echo hi
date
date +%F
echo bye 1>&2

Daje mi dane wyjściowe do pliku $HOME/somefile.logi terminala w następujący sposób:

/home/saml
hi
Sun Jan 20 13:54:17 EST 2013
2013-01-20
bye
slm
źródło
2
Zauważ, że używa to bashizmów - może nie działać w innych powłokach (np. Myślnik). Ale ponieważ w pytaniu określono bash, +1.
Richard Hansen
8
@RichardHansen, podstawianie procesów to funkcja, która została wprowadzona przez ksh, a nie bash i jest również obsługiwana przez zsh, więc nie nazwałbym tego bashizmem .
Stéphane Chazelas
6
@StephaneChazelas: Masz rację. Chciałem tylko zauważyć, że składnia nie jest obsługiwana przez standard POSIX, a zatem nie będzie działać w /bin/shskryptach (wiele osób błędnie używa składni bash w /bin/shskryptach).
Richard Hansen
Dla mnie to daje /dev/fd/62: Operation not supportedjakieś wskazówki?
Eun
1
Czy istnieje sposób, aby nie przekierowywać stderr oprócz pliku dziennika? Jeśli oryginalny skrypt jest myscripti uruchamiam ./myscript > /dev/null, nadal powinienem zobaczyć, byektóry pochodzi echo bye >&2.
Martin Jambon,
0

Zapisz stderr i stdout do pliku, wyświetl stderr na ekranie (na stdout)

exec 2> >(tee -a -i "$HOME/somefile.log")
exec >> "$HOME/somefile.log"

Przydatne w przypadku cronów, dzięki czemu możesz otrzymywać błędy (i tylko błędy) pocztą

Lluís
źródło