Wymuś opróżnianie danych wyjściowych do pliku, gdy skrypt bash nadal działa

89

Mam mały skrypt, który jest wywoływany codziennie przez crontab za pomocą następującego polecenia:

/homedir/MyScript &> some_log.log

Problem z tą metodą polega na tym, że plik some_log.log jest tworzony dopiero po zakończeniu działania MyScript. Chciałbym wrzucić dane wyjściowe programu do pliku podczas jego działania, aby móc na przykład robić rzeczy

tail -f some_log.log

i śledź postępy itp.

olamundo
źródło
Musimy mieć opis - lub jeśli to możliwe kod - tego, co dokładnie robi twój mały skrypt ...
ChristopheD
7
Aby cofnąć buforowanie skryptów Pythona, możesz użyć "python -u". Aby odblokować skrypty perla, zobacz odpowiedź Grega Hewgilla poniżej. I tak dalej ...
Eloici
Jeśli możesz edytować skrypt, zwykle możesz wyczyścić bufor wyjściowy jawnie w skrypcie, na przykład w pythonie za pomocą sys.stdout.flush().
drevicko

Odpowiedzi:

28

Sam bash nigdy nie zapisze żadnych danych wyjściowych do pliku dziennika. Zamiast tego polecenia, które wywołuje jako część skryptu, będą indywidualnie zapisywać dane wyjściowe i opróżniać je, gdy tylko zechcą. Więc tak naprawdę twoje pytanie brzmi, jak zmusić polecenia w skrypcie bash do opróżnienia, a to zależy od tego, czym one są.

Chris Dodd
źródło
23
Naprawdę nie rozumiem tej odpowiedzi.
Alfonso Santiago
2
Aby lepiej zrozumieć, dlaczego standardowe wyjście zachowuje się w ten sposób, odwiedź stackoverflow.com/a/13933741/282728 . Krótka wersja - domyślnie po przekierowaniu do pliku standardowe wyjście jest w pełni buforowane; jest zapisywany do pliku dopiero po opróżnieniu. Stderr nie jest - jest zapisywany po każdym „\ n”. Jednym z rozwiązań jest użycie polecenia „script” zalecanego przez użytkownika3258569 poniżej, aby opróżniać standardowe wyjście po każdym końcu linii.
Alex
Stwierdzenie tego, co oczywiste, i dziesięć lat później, ale to jest komentarz, a nie odpowiedź i nie powinna to być akceptowana odpowiedź.
RealHandy
84

Tutaj znalazłem rozwiązanie tego problemu . Korzystając z przykładu OP, w zasadzie uruchamiasz

stdbuf -oL /homedir/MyScript &> some_log.log

a następnie bufor jest opróżniany po każdym wierszu wyjścia. Często łączę to z nohupwykonywaniem długich zadań na zdalnym komputerze.

stdbuf -oL nohup /homedir/MyScript &> some_log.log

W ten sposób Twój proces nie zostanie anulowany po wylogowaniu.

Martin Wiebusch
źródło
1
Czy możesz dodać link do jakiejś dokumentacji stdbuf? Na podstawie tego komentarza wydaje się, że nie jest on dostępny w niektórych dystrybucjach. Czy mógłbyś wyjaśnić?
Sfinansuj pozew Moniki
1
stdbuf -o dostosowuje buforowanie stdout. Inne opcje to -i i -e dla stdin i stderr. L ustawia buforowanie linii. Można również określić rozmiar bufora lub 0, aby wyłączyć buforowanie.
Seppo Enarvi
5
ten link nie jest już dostępny.
Fzs2
2
@NicHartley: stdbufjest częścią GNU coreutils, dokumentację można znaleźć na stronie gnu.org
Thor
Na wypadek, gdyby to komukolwiek pomogło, użyj, export -f my_function a następnie stdbuf -oL bash -c "my_function -args"jeśli potrzebujesz uruchomić funkcję zamiast skryptu
Anonimowy
28
script -c <PROGRAM> -f OUTPUT.txt

Klucz to -f. Cytat ze skryptu man:

-f, --flush
     Flush output after each write.  This is nice for telecooperation: one person
     does 'mkfifo foo; script -f foo', and another can supervise real-time what is
     being done using 'cat foo'.

Uruchom w tle:

nohup script -c <PROGRAM> -f OUTPUT.txt
user3258569
źródło
Łał! Rozwiązanie, które działa busybox! (moja muszla później się zawiesza, ale nieważne)
Victor Sergienko,
9

Możesz użyć teedo zapisu do pliku bez potrzeby opróżniania.

/homedir/MyScript 2>&1 | tee some_log.log > /dev/null
karbowany
źródło
2
To nadal buforuje dane wyjściowe, przynajmniej w moim środowisku Ubuntu 18.04. Treść ostatecznie zostanie zapisana w pliku w obie strony, ale myślę, że OP prosi o metodę, w której mogą dokładniej monitorować postęp przed zakończeniem zapisu pliku, a ta metoda nie pozwala na to bardziej niż przekierowanie wyjścia robi.
mltsy
3

Nie jest to funkcja programu bash, ponieważ wszystko, co robi powłoka, to otwiera dany plik, a następnie przekazuje deskryptor pliku jako standardowe wyjście skryptu. To, co musisz zrobić, to upewnić się, że dane wyjściowe skryptu są opróżniane częściej niż obecnie.

Na przykład w Perlu można to osiągnąć ustawiając:

$| = 1;

Więcej informacji na ten temat znajdziesz w perlvar .

Greg Hewgill
źródło
2

Buforowanie danych wyjściowych zależy od sposobu /homedir/MyScriptimplementacji programu . Jeśli zauważysz, że dane wyjściowe są buforowane, musisz wymusić to w swojej implementacji. Na przykład użyj sys.stdout.flush (), jeśli jest to program w języku Python, lub fflush (stdout), jeśli jest to program w C.

Midas
źródło
2

Czy to pomogłoby?

tail -f access.log | stdbuf -oL cut -d ' ' -f1 | uniq 

Spowoduje to natychmiastowe wyświetlenie unikatowych wpisów z access.log za pomocą narzędzia stdbuf .

Ondra Žižka
źródło
Jedynym problemem jest to, że stdbuf wydaje się być starym narzędziem, które nie jest dostępne w nowych dystrybucjach.
Ondra Žižka
.. ani w moim busyboxie :(
Campa
W tej chwili mam stdbufdostępne w Ubuntu, nie jestem pewien, skąd je mam.
Ondra Žižka
Mam stdbuf w Centos 7.5
Max
1
W Ubuntu 18.04 stdbufjest częścią coreutilus(znajduje się z apt-file search /usr/bin/stdbuf).
Rmano
1

Jak tylko dostrzeżone tutaj problemem jest to, że trzeba czekać, że programy, które biegną od skryptu zakończyć pracę.
Jeśli w swoim skrypcie uruchamiasz program w tle , możesz spróbować czegoś więcej.

Generalnie wywołanie syncprzed wyjściem pozwala opróżnić bufory systemu plików i może trochę pomóc.

Jeśli w skrypcie uruchamiasz niektóre programy w tle ( &), możesz poczekać , aż zakończą się, zanim wyjdziesz ze skryptu. Aby dowiedzieć się, jak to działa, możesz zobaczyć poniżej

#!/bin/bash
#... some stuffs ...
program_1 &          # here you start a program 1 in background
PID_PROGRAM_1=${!}   # here you remember its PID
#... some other stuffs ... 
program_2 &          # here you start a program 2 in background
wait ${!}            # You wait it finish not really useful here
#... some other stuffs ... 
daemon_1 &           # We will not wait it will finish
program_3 &          # here you start a program 1 in background
PID_PROGRAM_3=${!}   # here you remember its PID
#... last other stuffs ... 
sync
wait $PID_PROGRAM_1
wait $PID_PROGRAM_3  # program 2 is just ended
# ...

Ponieważ waitdziała zarówno z zadaniami, jak iz PIDliczbami, leniwym rozwiązaniem powinno być umieszczenie na końcu skryptu

for job in `jobs -p`
do
   wait $job 
done

Trudniejsza jest sytuacja, jeśli uruchamiasz coś, co uruchamia coś innego w tle, ponieważ musisz wyszukać i poczekać (jeśli tak jest) na koniec całego procesu potomnego : na przykład, jeśli uruchamiasz demona, prawdopodobnie tak nie jest czekać, kończy się :-).

Uwaga:

  • czekaj $ {!} oznacza „czekaj, aż zakończy się ostatni proces w tle”, gdzie $!jest PID ostatniego procesu w tle. Tak więc wstawienie wait ${!}tuż po program_2 &jest równoważne wykonaniu bezpośrednio program_2bez wysyłania go w tle za pomocą&

  • Z pomocy wait:

    Syntax    
        wait [n ...]
    Key  
        n A process ID or a job specification
    
Hastur
źródło
1

Dzięki @user3258569, skrypt to być może jedyna rzecz, która działa busybox!

Jednak po tym muszla zamarzała. Szukając przyczyny, znalazłem te duże czerwone ostrzeżenia „nie używaj w powłokach nieinteraktywnych” na stronie podręcznika skryptów :

scriptjest przeznaczony głównie do interaktywnych sesji terminalowych. Gdy stdin nie jest terminalem (na przykład echo foo | script:), sesja może się zawiesić, ponieważ powłoka interaktywna w sesji skryptu omija EOF i scriptnie ma pojęcia, kiedy zamknąć sesję. Więcej informacji można znaleźć w sekcji UWAGI .

Prawdziwe. script -c "make_hay" -f /dev/null | grep "needle"zamrażał dla mnie muszlę.

Wbrew ostrzeżeniu myślałem, że echo "make_hay" | scriptprzejdzie EOF, więc spróbowałem

echo "make_hay; exit" | script -f /dev/null | grep 'needle'

i zadziałało!

Zwróć uwagę na ostrzeżenia na stronie podręcznika. To może nie działać dla Ciebie.

Victor Sergienko
źródło
0

Alternatywą dla stdbuf jest to, awk '{print} END {fflush()}' że chciałbym mieć wbudowany bash, który to robi. Zwykle nie powinno to być konieczne, ale w starszych wersjach mogą występować błędy synchronizacji bash w deskryptorach plików.

Brian Chrisman
źródło
-2

Nie wiem, czy to zadziała, ale co z dzwonieniem sync?

forkandwait
źródło
1
syncjest niskopoziomową operacją systemu plików i nie ma związku z buforowanym wyjściem na poziomie aplikacji.
Greg Hewgill,
2
syncw razie potrzeby zapisuje brudne bufory systemu plików do pamięci fizycznej. Jest to wewnętrzne dla systemu operacyjnego; aplikacje działające w systemie operacyjnym zawsze widzą spójny obraz systemu plików niezależnie od tego, czy bloki dysku zostały zapisane w fizycznej pamięci masowej. W pierwotnym pytaniu aplikacja (skrypt) prawdopodobnie buforuje dane wyjściowe w buforze wewnętrznym aplikacji, a system operacyjny nie będzie nawet wiedział (jeszcze), że dane wyjściowe są faktycznie przeznaczone do zapisania na standardowe wyjście. Zatem hipotetyczna operacja typu „synchronizacja” nie byłaby w stanie „sięgnąć” do skryptu i wyciągnąć danych.
Greg Hewgill,
-2

Miałem ten problem z procesem w tle w systemie Mac OS X przy użyciu rozszerzenia StartupItems. Oto jak to rozwiązuję:

Jeśli sprawię sudo ps aux, zobaczę, że mytooljest uruchomiony.

Zauważyłem, że (z powodu buforowania), gdy Mac OS X wyłącza się, mytoolnigdy nie przesyła danych wyjściowych do sedpolecenia. Jeśli jednak wykonam sudo killall mytool, mytoolprzekazuje dane wyjściowe do sedpolecenia. Dlatego dodałem do stopsprawy, StartupItemsktóra jest wykonywana, gdy Mac OS X się wyłącza:

start)
    if [ -x /sw/sbin/mytool ]; then
      # run the daemon
      ConsoleMessage "Starting mytool"
      (mytool | sed .... >> myfile.txt) & 
    fi
    ;;
stop)
    ConsoleMessage "Killing mytool"
    killall mytool
    ;;
Obywatel
źródło
To naprawdę nie jest dobra odpowiedź Freeman, ponieważ jest bardzo specyficzna dla twojego środowiska. OP chce monitorować wyjście, a nie go zabijać.
Gray
-3

czy ci się to podoba, czy nie, tak działa przekierowanie.

W twoim przypadku wyjście (co oznacza, że ​​skrypt się zakończył) twojego skryptu zostało przekierowane do tego pliku.

To, co chcesz zrobić, to dodać te przekierowania w swoim skrypcie.


źródło