Erzwingen Sie das Leeren der Ausgabe in eine Datei, während das Bash-Skript noch ausgeführt wird


89

Ich habe ein kleines Skript, das täglich von crontab mit dem folgenden Befehl aufgerufen wird:

/homedir/MyScript &> some_log.log

Das Problem bei dieser Methode ist, dass some_log.log erst nach Abschluss von MyScript erstellt wird. Ich möchte die Ausgabe des Programms in die Datei leeren, während es ausgeführt wird, damit ich Dinge wie tun kann

tail -f some_log.log

und verfolgen Sie den Fortschritt usw.


Wir brauchen eine Beschreibung - oder wenn möglich einen Code - darüber, was Ihr kleines Skript genau macht ...
ChristopheD

7
Zum Entpuffern von Python-Skripten können Sie "python -u" verwenden. Informationen zum Entpuffern von Perl-Skripten finden Sie in der Antwort von Greg Hewgill weiter unten. Und so weiter ...
Eloici

Wenn Sie das Skript bearbeiten können, können Sie den Ausgabepuffer normalerweise explizit in einem Skript leeren, z. B. in Python mit sys.stdout.flush().
Drevicko

Antworten:


28

bash selbst schreibt niemals eine Ausgabe in Ihre Protokolldatei. Stattdessen schreiben die Befehle, die als Teil des Skripts aufgerufen werden, die Ausgabe einzeln und leeren sie, wann immer sie möchten. Ihre Frage ist also wirklich, wie Sie die Befehle im Bash-Skript zum Leeren zwingen können, und das hängt davon ab, was sie sind.


23
Ich verstehe diese Antwort wirklich nicht.
Alfonso Santiago

2
Eine bessere Vorstellung davon, warum sich die Standardausgabe so verhält, finden Sie unter stackoverflow.com/a/13933741/282728 . Eine Kurzversion - Standardmäßig wird stdout vollständig gepuffert, wenn es in eine Datei umgeleitet wird. Es wird erst nach einem Flush in eine Datei geschrieben. Stderr ist nicht - es wird nach jedem '\ n' geschrieben. Eine Lösung besteht darin, den von user3258569 unten empfohlenen Befehl 'script' zu verwenden, um stdout nach jedem Zeilenende zu leeren.
Alex

84

Hier habe ich eine Lösung gefunden . Am Beispiel des OP führen Sie grundsätzlich aus

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

und dann wird der Puffer nach jeder Ausgabezeile geleert. Ich kombiniere dies oft mit nohup, um lange Jobs auf einem Remote-Computer auszuführen.

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

Auf diese Weise wird Ihr Prozess beim Abmelden nicht abgebrochen.


1
Könnten Sie einen Link zu einer Dokumentation für hinzufügen stdbuf? Basierend auf diesem Kommentar scheint es, dass es in einigen Distributionen nicht verfügbar ist. Könnten Sie das klarstellen?
Fund Monica Klage

1
stdbuf -o passt die stdout-Pufferung an. Die anderen Optionen sind -i und -e für stdin und stderr. L setzt die Zeilenpufferung. Man könnte auch die Puffergröße oder 0 für keine Pufferung angeben.
Seppo Enarvi

5
Dieser Link ist nicht mehr verfügbar.
Fzs2

2
@NicHartley: stdbufist Teil von GNU Coreutils, Dokumentation finden Sie auf gnu.org
Thor

Falls es jemandem hilft, verwenden Sie export -f my_function und dann, stdbuf -oL bash -c "my_function -args"wenn Sie eine Funktion anstelle eines Skripts ausführen müssen
Anonym

28
script -c <PROGRAM> -f OUTPUT.txt

Schlüssel ist -f. Zitat aus dem Man-Skript:

-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'.

Im Hintergrund laufen:

nohup script -c <PROGRAM> -f OUTPUT.txt

Beeindruckend! Eine Lösung, die funktioniert busybox! (Meine Muschel friert danach ein, aber was auch immer)
Victor Sergienko

9

Sie können teedamit in die Datei schreiben, ohne dass ein Löschen erforderlich ist.

/homedir/MyScript 2>&1 | tee some_log.log > /dev/null

2
Dies puffert immer noch die Ausgabe, zumindest in meiner Ubuntu 18.04-Umgebung. Der Inhalt wird schließlich so oder so in die Datei geschrieben, aber ich denke, das OP fragt nach einer Methode, mit der der Fortschritt genauer überwacht werden kann, bevor die Datei fertig geschrieben ist, und diese Methode erlaubt dies nicht mehr als die Umleitung der Ausgabe tut.
mltsy

3

Dies ist keine Funktion von bash, da die Shell lediglich die betreffende Datei öffnet und dann den Dateideskriptor als Standardausgabe des Skripts übergibt. Sie müssen lediglich sicherstellen, dass die Ausgabe Ihres Skripts häufiger als derzeit gelöscht wird.

In Perl kann dies beispielsweise erreicht werden, indem Folgendes festgelegt wird:

$| = 1;

Weitere Informationen hierzu finden Sie unter perlvar .


2

Die Pufferung der Ausgabe hängt davon ab, wie Ihr Programm /homedir/MyScriptimplementiert ist. Wenn Sie feststellen, dass die Ausgabe gepuffert wird, müssen Sie sie in Ihrer Implementierung erzwingen. Verwenden Sie beispielsweise sys.stdout.flush (), wenn es sich um ein Python-Programm handelt, oder fflush (stdout), wenn es sich um ein C-Programm handelt.


2

Würde das helfen?

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

Dadurch werden sofort eindeutige Einträge aus access.log mit dem Dienstprogramm stdbuf angezeigt .


Das einzige Problem ist, dass stdbuf ein altes Dienstprogramm zu sein scheint, das in neuen Distributionen nicht verfügbar ist.
Ondra Žižka

..nor in meiner Busybox :(
Campa

Eigentlich habe ich stdbufim Moment in Ubuntu verfügbar, nicht sicher, woher ich es habe.
Ondra Žižka

Ich habe stdbuf in Centos 7.5
Max

1
In Ubuntu 18.04 stdbufist ein Teil von coreutilus(gefunden mit apt-file search /usr/bin/stdbuf).
Rmano

1

Wie hier gerade entdeckt, ist das Problem, dass Sie warten müssen, bis die Programme, die Sie von Ihrem Skript ausführen, ihre Jobs beenden.
Wenn Sie in Ihrem Skript ein Programm im Hintergrund ausführen, können Sie etwas mehr ausprobieren.

Im Allgemeinen ermöglicht ein Aufruf von syncvor dem Beenden das Leeren von Dateisystempuffern und kann ein wenig helfen.

Wenn Sie im Skript einige Programme im Hintergrund ( &) starten , können Sie warten , bis sie beendet sind, bevor Sie das Skript verlassen. Eine Vorstellung davon, wie es funktionieren kann, finden Sie unten

#!/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
# ...

Da es waitsowohl mit Jobs als auch mit PIDZahlen funktioniert, sollte eine träge Lösung darin bestehen, am Ende des Skripts zu setzen

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

Schwieriger ist die Situation, wenn Sie etwas ausführen, das etwas anderes im Hintergrund ausführt, weil Sie das Ende des gesamten untergeordneten Prozesses suchen und abwarten müssen (falls dies der Fall ist) : Wenn Sie beispielsweise einen Daemon ausführen , ist dies wahrscheinlich nicht der Fall zu warten endet es :-).

Hinweis:

  • wait $ {!} bedeutet "Warten, bis der letzte Hintergrundprozess abgeschlossen ist", wobei $!die PID des letzten Hintergrundprozesses angegeben ist. So setzt wait ${!}kurz nach program_2 &entsprechen direkt auszuführen , program_2ohne sie im Hintergrund senden mit&

  • Mit Hilfe von wait:

    Syntax    
        wait [n ...]
    Key  
        n A process ID or a job specification
    

1

Danke @user3258569, Skript ist vielleicht das einzige, was funktioniert busybox!

Die Muschel fror danach für mich. Auf der Suche nach der Ursache fand ich diese großen roten Warnungen "Nicht in nicht interaktiven Shells verwenden" in der Skript-Handbuchseite :

scriptist hauptsächlich für interaktive Terminalsitzungen konzipiert. Wenn stdin kein Terminal ist (zum Beispiel echo foo | script:), kann die Sitzung hängen bleiben, da die interaktive Shell in der Skriptsitzung EOF verfehlt und scriptkeine Ahnung hat, wann die Sitzung geschlossen werden soll. Weitere Informationen finden Sie im Abschnitt NOTES .

Wahr. script -c "make_hay" -f /dev/null | grep "needle"fror die Schale für mich ein.

Entgegen der Warnung dachte ich, dass echo "make_hay" | scriptWILL einen EOF bestehen wird, also versuchte ich es

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

und es hat funktioniert!

Beachten Sie die Warnungen in der Manpage. Dies funktioniert möglicherweise nicht für Sie.


0

Alternative zu stdbuf ist, awk '{print} END {fflush()}' ich wünschte, es wäre eine Bash eingebaut, um dies zu tun. Normalerweise sollte dies nicht erforderlich sein, aber bei älteren Versionen kann es zu Bash-Synchronisationsfehlern bei Dateideskriptoren kommen.


-2

Ich weiß nicht, ob es funktionieren würde, aber was ist mit einem Anruf sync?


1
syncist eine Dateisystemoperation auf niedriger Ebene und steht in keinem Zusammenhang mit der gepufferten Ausgabe auf Anwendungsebene.
Greg Hewgill

2
syncSchreibt bei Bedarf fehlerhafte Dateisystempuffer in den physischen Speicher. Dies ist betriebsintern; Anwendungen, die auf dem Betriebssystem ausgeführt werden, sehen immer eine kohärente Ansicht des Dateisystems, unabhängig davon, ob die Plattenblöcke in den physischen Speicher geschrieben wurden oder nicht. Bei der ursprünglichen Frage puffert die Anwendung (das Skript) wahrscheinlich die Ausgabe in einem anwendungsinternen Puffer, und das Betriebssystem weiß (noch) nicht einmal, dass die Ausgabe tatsächlich dazu bestimmt ist, in stdout geschrieben zu werden. Eine hypothetische Operation vom Typ "Synchronisierung" wäre also nicht in der Lage, in das Skript zu "greifen" und die Daten herauszuholen.
Greg Hewgill

-2

Ich hatte dieses Problem mit einem Hintergrundprozess in Mac OS X unter Verwendung der StartupItems. So löse ich es:

Wenn ich mache, sudo ps auxkann ich sehen, dass mytooldas gestartet wird.

Ich habe festgestellt, dass (aufgrund der Pufferung) beim Herunterfahren von Mac OS X mytooldie Ausgabe niemals an den sedBefehl übertragen wird. Wenn ich jedoch ausführe sudo killall mytool, wird mytooldie Ausgabe an den sedBefehl übertragen. Daher habe ich dem stopFall einen Fall hinzugefügt , der StartupItemsausgeführt wird, wenn Mac OS X heruntergefahren wird:

start)
    if [ -x /sw/sbin/mytool ]; then
      # run the daemon
      ConsoleMessage "Starting mytool"
      (mytool | sed .... >> myfile.txt) & 
    fi
    ;;
stop)
    ConsoleMessage "Killing mytool"
    killall mytool
    ;;

Dies ist wirklich keine gute Antwort, Freeman, da es sehr spezifisch für Ihre Umgebung ist. Das OP möchte die Ausgabe überwachen und nicht beenden.
Grau

-3

Ob es Ihnen gefällt oder nicht, so funktioniert die Umleitung.

In Ihrem Fall wird die Ausgabe (dh Ihr Skript ist fertig) Ihres Skripts in diese Datei umgeleitet.

Sie möchten diese Umleitungen in Ihr Skript einfügen.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.