Wymuś użycie skryptu bash do używania tee bez przesyłania z linii poleceń


20

Mam skrypt bash z dużą ilością danych wyjściowych i chciałbym zmienić ten skrypt (nie tworzyć nowego), aby skopiować wszystkie dane wyjściowe do pliku, tak jak by to było, gdybym przepuszczał go przez tee.

Mam plik script.sh :

#!/bin/bash
init
do_something_that_outputs_stuff_to_STDOUT
launch_applications_that_output_to_STDOUT
fini

i chciałbym zrobić kopię STDOUT do pliku script.log bez konieczności pisania za ./script.sh | tee script.logkażdym razem.

Dzięki, Tom


Przykro mi, że nie powiedziałem tego wcześniej, ale skrypt jest dość długi, dlatego szukam prostego rozwiązania. Dołączanie | tee do każdej linii jest trochę za dużo.
Tom

Odpowiedzi:


15

Nie mogłem zmusić do działania bardzo prostego liniowca Dennisa, więc oto o wiele bardziej skomplikowana metoda. Spróbuję jego pierwszego.

Jak wspomniano, możesz użyć exec do przekierowania standardowego błędu i standardowego wyjścia dla całego skryptu. W ten sposób:
exec > $LOGFILE 2>&1 Spowoduje to wyświetlenie wszystkich stderr i stdout do $ LOGFILE.

Teraz, ponieważ chcesz wyświetlać to na konsoli, a także plik dziennika, będziesz musiał także użyć nazwanego potoku dla exec do pisania i tee do odczytu.
(One-liner Dennisa technicznie też to robi, choć oczywiście w inny sposób). Za pomocą tej rury jest tworzona mkfifo $PIPEFILE. Następnie wykonaj następujące czynności.

# Rozpocznij pisanie tee do pliku dziennika, ale pobieraj dane wejściowe z naszego nazwanego potoku.
koszulka $ LOGFILE <$ PIPEFILE &

# capture ID procesu tee dla polecenia wait.
TEEPID = $!

# przekieruj resztę stderr i stdout do naszej nazwanej potoku.
exec> $ PIPEFILE 2> i 1

echo „Wprowadź tutaj swoje polecenia”
echo „Wszystkie ich standardowe wyjścia będą miały teed”.
echo „Podobnie będzie z ich standardowym błędem”> i 2

# zamknij deskryptory plików stderr i stdout.
exec 1> i - 2> i -

# Poczekaj, aż trójnik skończy, ponieważ teraz drugi koniec rury został zamknięty.
poczekaj $ TEEPID

Jeśli chcesz być dokładny, możesz utworzyć i zniszczyć nazwany plik potoku na początku i na końcu skryptu.

Dla przypomnienia, większość tego zebrałem z bardzo pouczającego posta na blogu przypadkowego faceta: ( Wersja zarchiwizowana )


To nie wydaje się działać w cygwin. :-(
docwhat

Ten post ma dobrą robotę edukacyjną. Dziękuję Ci bardzo.
Felipe Alvarez

27

Po prostu dodaj to na początku skryptu:

exec > >(tee -ia script.log)

To doda wszystkie dane wyjściowe wysłane do standardowego pliku script.log, pozostawiając poprzednią zawartość na miejscu. Jeśli chcesz zacząć od nowa za każdym razem, gdy skrypt jest uruchamiany, po prostu dodaj rm script.logbezpośrednio przed tym execpoleceniem lub usuń je -az teepolecenia.

Ta -iopcja powoduje teeignorowanie sygnałów przerwania i może pozwolić teena złapanie pełniejszego zestawu wyjściowego.

Dodaj ten wiersz, aby złapać wszystkie błędy (w osobnym pliku):

exec 2> >(tee -ia scripterr.out)

>Ważne są odstępy między wieloma symbolami.


Bardzo interesujące rozwiązanie.
ℝaphink

2
Jest to naprawdę elegancki sposób na zrobienie tego w porównaniu do wszystkich bzdur, które opublikowałem. Ale kiedy tworzę skrypt z tymi dwiema liniami, zawiesza się i nigdy nie kończy poprawnie.
Christopher Karel,

Jaka wersja Bash?
Wstrzymano do odwołania.

GNU bash, wersja 3.2.25 (1) jako część instalacji RHEL5.3. Wygląda na to, że jest to najnowsza wersja z oficjalnych repozytoriów.
Christopher Karel,

Właśnie wypróbowałem to pod cygwin w wydaniu Bash 3.2.49 (23) i dostałem błędy deskryptora pliku. Pracuję jednak w wersji Bash 4.0.33 (1) w Ubuntu 9.10, więc może to być Bash 4.
Wstrzymano do odwołania.

6

To jest połączona wersja odpowiedzi opublikowanej wcześniej przez Dennisa Williamsona. Dołącza zarówno err i std out do script.log we właściwej kolejności.

Dodaj tę linię na początku skryptu:

exec > >(tee -a script.log) 2>&1

Zwróć uwagę na odstęp między >znakami. Działa dla mnie na GNU bash, wersja 3.2.25 (1) -release (x86_64-redhat-linux-gnu)


5

Przypuszczam, że inne podejście jest takie:

#!/bin/bash

# start grouping at the top
{ 
    # your script goes here
    do_something_that_outputs_stuff_to_STDOUT
    launch_applications_that_output_to_STDOUT

# and at the bottom, redirect everything from the grouped commands
} 2>&1 | tee script.log 

3

Jeśli chcesz kopię wyniku, możesz użyć teetego sposobu:

  #!/bin/bash
  init
  do_something_that_outputs_stuff_to_STDOUT | tee script.log
  launch_applications_that_output_to_STDOUT | tee -a script.log
  fini

Spowoduje to jednak zalogowanie tylko stdout do script.log. Jeśli chcesz się upewnić, że stderr i stdout są przekierowane, użyj:

  #!/bin/bash
  init
  do_something_that_outputs_stuff_to_STDOUT 2>&1 | tee script.log
  launch_applications_that_output_to_STDOUT 2>&1 | tee -a script.log
  fini

Możesz nawet uczynić to nieco ładniejszym dzięki małej funkcji:

  #!/bin/bash

  LOGFILE="script.log"
  # Wipe LOGFILE if you don't want to add to it at each run
  rm -f "$LOGFILE"

  function logcmd() {
        local cmd="$1" logfile=${LOGFILE:-script.log} # Use default value if unset earlier

        # Launch command, redirect stderr to stdout and append to $logfile
        eval '$cmd' 2>&1 | tee -a "$logfile"
  }

  init
  logcmd "do_something_that_outputs_stuff_to_STDOUT"
  logcmd "launch_applications_that_output_to_STDOUT"
  fini

Dobra odpowiedź. Jeśli tak naprawdę zależy mu tylko na stdout, po prostu zostawiłbym to 2>&1i po prostu wyrzuciłbym to do tee.
DaveParillo

To prawda, podałem tylko stderr, ponieważ poprzednia odpowiedź Igora zależała na przekierowaniu go i pomyślałem, że to dobry pomysł.
ℝaphink

1

Po prostu użyjesz tego:

script logfile.txt
your script or command here

Wyprowadza to wszystko do tego dziennika, wszystko, a także wyświetla na ekranie. Jeśli chcesz uzyskać pełny wynik, oto jak to zrobić.

Jeśli jesteś leniwy, możesz ponownie odtworzyć skrypt.


Warto zauważyć, że scriptjest to pakiet, np. sudo apt-get install scriptaby go zainstalować na Debianie aromatyzowane , script -ac 'command opts' ~/Documents/some_log.scriptmożna dodatkowo sprawdzić go w terminalu za pomocą czegoś takiego strings ~/Documents/some_log.script | more; stringsbyć prostym sposobem wstępnej dezynfekcji niektórych rzeczy, które script wstrzykuje, aby umożliwić bardziej wyszukane metody ponownego odtwarzania / przeglądania. W przeciwnym razie solidna odpowiedź @ Phishy, ​​ponieważ scriptrównież zachowuje interaktywne rzeczy.
S0AndS0

0
#!/bin/bash
init
do_something_that_outputs_stuff_to_STDOUT > script.log 2>&1
launch_applications_that_output_to_STDOUT >> script.log 2>&1
fini

Możesz dowiedzieć się więcej o tym, jak to działa tutaj: http://www.xaprb.com/blog/2006/06/06/what-does-devnull-21-mean/


1
Dodałem drugi> do drugiego wiersza w odpowiedzi Igora, aby nie wyczyścił dziennika zapisanego w pierwszym wierszu.
Doug Harris

1
Jednak chce kopii, więc musi jej użyć tee.
ℝaphink,

@Raphink: Zgadza się :)
Tom
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.