Jak przekierować wyjście całego skryptu powłoki w samym skrypcie?

205

Czy można przekierować gdzieś wszystkie wyniki skryptu powłoki Bourne'a, ale za pomocą poleceń powłoki w samym skrypcie?

Przekierowanie wyjścia pojedynczego polecenia jest łatwe, ale chcę czegoś takiego:

#!/bin/sh
if [ ! -t 0 ]; then
    # redirect all of my output to a file here
fi

# rest of script...

Znaczenie: jeśli skrypt jest uruchamiany nieinteraktywnie (na przykład cron), zapisz dane wyjściowe wszystkiego do pliku. Jeśli uruchamiany jest interaktywnie z powłoki, pozwól, aby wyjście przechodziło na standardowe wyjście jak zwykle.

Chcę to zrobić dla skryptu normalnie uruchamianego przez okresowe narzędzie FreeBSD. Jest to część codziennego biegu, którego zwykle nie obchodzi mnie codziennie w wiadomości e-mail, więc nie wysyłam go. Jeśli jednak coś w tym konkretnym skrypcie zawiedzie, jest to dla mnie ważne i chciałbym móc przechwytywać i wysyłać pocztą e-mail wyniki tej części codziennych zadań.

Aktualizacja: odpowiedź Jozuego jest natychmiastowa, ale chciałem również zapisać i przywrócić stdout i stderr dookoła całego skryptu, co odbywa się w następujący sposób:

# save stdout and stderr to file descriptors 3 and 4, then redirect them to "foo"
exec 3>&1 4>&2 >foo 2>&1

# ...

# restore stdout and stderr
exec 1>&3 2>&4
Steve Madsen
źródło
2
Testowanie na $ TERM nie jest najlepszym sposobem na testowanie w trybie interaktywnym. Zamiast tego sprawdź, czy stdin jest tty (test -t 0).
Chris Jester-Young,
2
Innymi słowy: jeśli [! -t 0]; następnie exec> somefile 2> & 1; fi
Chris Jester-Young
1
Zobacz tutaj całą dobroć: http://tldp.org/LDP/abs/html/io-redirection.html Zasadniczo to, co powiedział Joshua. plik exec> przekierowuje stdout do określonego pliku, plik exec <plik zastępuje stdin przez plik itp. Jest taki sam jak zwykle, ale używa exec (więcej szczegółów znajduje się w man exec).
Loki
W sekcji dotyczącej aktualizacji powinieneś także zamknąć FD 3 i 4, tak jak: exec 1>&3 2>&4 3>&- 4>&-
Gurjeet Singh

Odpowiedzi:

173

Odpowiedź na pytanie w formie zaktualizowanej.

#...part of script without redirection...

{
    #...part of script with redirection...
} > file1 2>file2 # ...and others as appropriate...

#...residue of script without redirection...

Nawiasy klamrowe „{...}” zapewniają jednostkę przekierowania we / wy. Nawiasy klamrowe muszą pojawiać się tam, gdzie może pojawić się polecenie - w uproszczeniu, na początku linii lub po średniku. ( Tak, można to sprecyzować; jeśli chcesz się spierać, daj mi znać. )

Masz rację, że możesz zachować oryginalny stdout i stderr z pokazanymi przekierowaniami, ale zwykle ludzie, którzy muszą później utrzymać skrypt, łatwiej zrozumieć, co się dzieje, jeśli zakodujesz przekierowany kod, jak pokazano powyżej.

Odpowiednie sekcje podręcznika Bash to polecenia grupujące i przekierowanie we / wy . Odpowiednie odcinki opisie powłoki POSIX są Compound poleceń i I / O przekierowania . Bash ma kilka dodatkowych notacji, ale poza tym jest podobny do specyfikacji powłoki POSIX.

Jonathan Leffler
źródło
3
Jest to o wiele wyraźniejsze niż zapisywanie oryginalnych deskryptorów i przywracanie ich później.
Steve Madsen
23
Musiałem zrobić Google, aby zrozumieć, co to naprawdę robi, więc chciałem się tym podzielić. Nawiasy klamrowe stają się „blokiem kodu” , który w efekcie tworzy anonimową funkcję . Wszystkie dane wyjściowe w bloku kodu można następnie przekierować (patrz przykład 3-2 z tego łącza). Należy również pamiętać, że nawiasy klamrowe nie uruchamiają podpowłoki , ale podobne przekierowania we / wy można wykonać za pomocą podpowłok za pomocą nawiasów.
Chris
3
Podoba mi się to rozwiązanie bardziej niż inne. Nawet osoba posiadająca tylko najbardziej podstawową wiedzę na temat przekierowania wejścia / wyjścia może zrozumieć, co się dzieje. Plus, to bardziej szczegółowe. Jako Pythoner uwielbiam gadatliwie.
John Red
Lepiej >>. Niektórzy ludzie mają nawyk >. Dołączanie jest zawsze bezpieczniejsze i bardziej zalecane niż przytłaczanie. Ktoś napisał aplikację, która używa standardowego polecenia kopiowania, aby wyeksportować niektóre dane do tego samego miejsca docelowego.
neverMind9
Ta aplikacja to ccc.bmw71 (.pro) (3C Battery Monitor Widget, dawniej „Battery Monitor Widget”) zawsze eksportuje historię baterii do /sdcard/bmw_history.txt. Jeśli już istnieje, zgadnij co, OVERWRITE! To spowodowało, że przypadkowo straciłem trochę historii baterii. Ustawiłem limit w dniach od 30 na bardzo wysoką liczbę, co unieważniło go i przywróciłem do 30. Chciałem wyeksportować bieżącą historię baterii przed zaimportowaniem istniejącej kopii zapasowej. To się stało.
neverMind9
175

Zazwyczaj umieszczamy jeden z nich na górze skryptu lub w jego pobliżu. Skrypty, które analizują wiersze poleceń, dokonają przekierowania po analizie.

Wyślij stdout do pliku

exec > file

z stderr

exec > file                                                                      
exec 2>&1

dołącz do pliku zarówno stdout, jak i stderr

exec >> file
exec 2>&1

Jak wspomniał Jonathan Leffler w swoim komentarzu :

execma dwie osobne prace. Pierwszym z nich jest zastąpienie aktualnie wykonywanej powłoki (skryptu) nowym programem. Drugi polega na zmianie przekierowań we / wy w bieżącej powłoce. Wyróżnia się to brakiem argumentu exec.

Jozuego
źródło
7
Mówię też, że dodaj 2> i 1 na końcu, żeby stderr też został złapany. :-)
Chris Jester-Young,
7
Gdzie je umieszczasz? Na początku skryptu?
colan
4
W tym rozwiązaniu należy również zresetować przekierowanie, z którego wychodzi skrypt. Następna odpowiedź Jonathana Lefflera jest w tym sensie bardziej „odporna na błędy”.
Chuim
6
@JohnRed: execma dwa osobne zadania. Jednym z nich jest zastąpienie bieżącego skryptu innym poleceniem, przy użyciu tego samego procesu - określasz drugie polecenie jako argument exec(i możesz dostosowywać przekierowania we / wy podczas tej operacji ). Drugim zadaniem jest zmiana przekierowań we / wy w bieżącym skrypcie powłoki bez zastępowania go. Notacja ta wyróżnia się tym, że nie ma polecenia jako argumentu exec. Zapis w tej odpowiedzi dotyczy wariantu „Tylko we / wy” - zmienia tylko przekierowanie i nie zastępuje uruchomionego skryptu. ( setPolecenie jest podobnie wielofunkcyjne).
Jonathan Leffler
18
exec > >(tee -a "logs/logdata.log") 2>&1drukuje dzienniki na ekranie, a także zapisuje je w pliku
shriyog
32

Możesz ustawić cały skrypt w taki sposób:

main_function() {
  do_things_here
}

następnie na końcu skryptu:

if [ -z $TERM ]; then
  # if not run via terminal, log everything into a log file
  main_function 2>&1 >> /var/log/my_uber_script.log
else
  # run via terminal, only output to screen
  main_function
fi

Alternatywnie możesz zalogować wszystko do pliku dziennika przy każdym uruchomieniu i nadal wyprowadzać go na standardowe wyjście, po prostu wykonując:

# log everything, but also output to stdout
main_function 2>&1 | tee -a /var/log/my_uber_script.log
dbguy
źródło
Czy chodziło Ci o main_function >> /var/log/my_uber_script.log 2> & 1
Felipe Alvarez
Lubię używać main_function w takiej rurze. Ale w tym przypadku skrypt nie zwraca oryginalnej wartości zwracanej. W przypadku bash powinieneś wyjść, a następnie użyj „exit $ {PIPESTATUS [0]}”.
rudimeier
7

Aby zapisać oryginalny stdout i stderr, możesz użyć:

exec [fd number]<&1 
exec [fd number]<&2

Na przykład poniższy kod wypisze „walla1” i „walla2” do pliku dziennika ( a.txt), „walla3” na standardowe wyjście, „walla4” na standardowe wyjście.

#!/bin/bash

exec 5<&1
exec 6<&2

exec 1> ~/a.txt 2>&1

echo "walla1"
echo "walla2" >&2
echo "walla3" >&5
echo "walla4" >&6
Leshem do oczu
źródło
1
Zwykle lepiej byłoby użyć exec 5>&1i exec 6>&2, używając notacji przekierowania wyjścia, niż notacji przekierowania wejścia dla wyjść. Unikasz tego, ponieważ kiedy skrypt jest uruchamiany z terminala, standardowe dane wejściowe są również zapisywalne, a zarówno standardowe dane wyjściowe, jak i standardowy błąd są czytelne na podstawie (czy też „wice”?) Historycznego dziwactwa: terminal jest otwarty dla odczyt i zapis oraz ten sam opis otwartego pliku jest używany dla wszystkich trzech standardowych deskryptorów plików we / wy.
Jonathan Leffler
3
[ -t <&0 ] || exec >> test.log
Dimitar
źródło