Leeren einer Datei, ohne das Schreiben der Pipe zu unterbrechen

12

Ich habe ein Programm, dessen Ausgabe ich in eine Protokolldatei umleite:

./my_app > log

Ich möchte das Protokoll von Zeit zu Zeit löschen (dh leeren) (auf Anfrage) und verschiedene Dinge ausprobieren wie

cat "" > log

Es scheint jedoch immer, dass die ursprüngliche Pipe dann unterbrochen wird und das Programm seine Ausgabe nicht mehr in die Protokolldatei umleitet.

Gibt es eine Möglichkeit, das zu tun?

Aktualisieren

Beachten Sie, dass ich die Anwendung, die die Ausgabe erzeugt, nicht ändern kann. Es wird nur als Standard ausgegeben, und ich möchte es in einem Protokoll speichern, damit ich es bei Bedarf überprüfen und löschen kann, wenn ich möchte. Allerdings sollte ich die Anwendung nicht neu starten müssen.

Bangnab
quelle
Aus diesem Grund verwenden Sie normalerweise einen Protokollierungs-Daemon, um Dinge zu protokollieren ...
Kiwy
@Kiwy kannst du erläutern, wie das Problem gelöst werden könnte?
Bangnab
Normalerweise verwenden Sie einen Protokoll-Daemon oder lassen Ihre App das Protokoll verarbeiten, da das Schreiben und Umleiten von Dingen zur Ausgabe nicht zuverlässig ist. Sie sollten einen Blick auf syslogdoderlogrotate
Kiwy
2
Funktionieren die Dinge, wenn Sie das ./my_app >> logAnhängen erzwingen und cp /dev/null logabschneiden?
Mark Plotnick
1
Welche Fehlermeldung erhalten Sie? Welches Verhalten sehen Sie? "Leitet die Ausgabe nicht mehr in die Protokolldatei um" ist nicht sehr spezifisch. Ist auch cat "" > logkein gültiger catBefehl, da keine Datei aufgerufen wird "".
Mikel

Antworten:

13

Eine andere Form dieses Problems tritt bei lang laufenden Anwendungen auf, deren Protokolle regelmäßig gewechselt werden. Auch wenn Sie das ursprüngliche Protokoll verschieben (z. B.mv log.txt log.1 ) verschieben und sofort durch eine Datei mit dem gleichen Namen ersetzen, bevor eine tatsächliche Protokollierung erfolgt, wird beim Öffnen der Datei entweder in das Protokoll geschrieben log.1(da dies möglicherweise immer noch der Fall ist) die offene inode) oder nichts.

Ein üblicher Weg, um damit umzugehen (der Systemlogger selbst funktioniert auf diese Weise), ist die Implementierung eines Signal-Handlers in dem Prozess, der seine Protokolle schließt und wieder öffnet. Wenn Sie dann das Protokoll verschieben oder löschen möchten (indem Sie es löschen), senden Sie das Signal sofort danach an den Prozess.

Hier ist eine einfache Demonstration für bash - verzeihen Sie meine groben Shell-Fähigkeiten (aber wenn Sie dies für Best Practices usw. bearbeiten wollen, stellen Sie bitte sicher, dass Sie die Funktionalität zuerst verstehen und Ihre Revision testen, bevor Sie sie bearbeiten):

#!/bin/bash

trap sighandler INT

function sighandler () {
    touch log.txt
    exec &> log.txt
}

echo $BASHPID
exec &> log.txt

count=0;
while [ $count -lt 60 ]; do
    echo "$BASHPID Count is now $count"
    sleep 2
    ((count++))
done          

Beginnen Sie dies, indem Sie in den Hintergrund treten:

> ./test.sh &
12356

Beachten Sie, dass es seine PID an das Terminal meldet und dann mit der Protokollierung beginnt log.txt. Sie haben jetzt 2 Minuten Zeit, um herumzuspielen. Warte ein paar Sekunden und versuche:

> mv log.txt log.1 && kill -s 2 12356

kill -2 12356Vielleicht funktioniert auch hier einfach alles für Sie. Signal 2 ist SIGINT (es ist auch das, was Ctrl-C tut, also können Sie dies im Vordergrund versuchen und die Protokolldatei von einem anderen Terminal verschieben oder entfernen), was der trapTrap ausführen soll. Überprüfen;

> cat log.1
12356 Count is now 0
12356 Count is now 1
12356 Count is now 2
12356 Count is now 3
12356 Count is now 4
12356 Count is now 5
12356 Count is now 6
12356 Count is now 7
12356 Count is now 8
12356 Count is now 9
12356 Count is now 10
12356 Count is now 11
12356 Count is now 12
12356 Count is now 13
12356 Count is now 14

Nun wollen wir sehen, ob es noch in a schreibt, log.txtobwohl wir es verschoben haben:

> cat log.txt
12356 Count is now 15
12356 Count is now 16
12356 Count is now 17
12356 Count is now 18
12356 Count is now 19
12356 Count is now 20
12356 Count is now 21

Beachten Sie, dass es dort weiterging, wo es aufgehört hat. Wenn Sie die Aufzeichnung nicht behalten möchten, löschen Sie einfach das Protokoll, indem Sie es löschen

> rm -f log.txt && kill -s 2 12356

Prüfen:

> cat log.txt
12356 Count is now 29
12356 Count is now 30
12356 Count is now 31
12356 Count is now 32
12356 Count is now 33
12356 Count is now 34
12356 Count is now 35
12356 Count is now 36

Geht immer noch.

Dies ist in einem Shell-Skript für einen ausgeführten Unterprozess leider nicht möglich, da die bash-eigenen Signal-Handler (trap angehalten werden und Sie nicht neu zuweisen können, wenn Sie sie in den Hintergrund stellen Ausgabe. Das heißt, dies ist etwas, das Sie in Ihrer Anwendung implementieren müssen.

Jedoch...

Wenn Sie die Anwendung nicht ändern können (z. B. weil Sie sie nicht geschrieben haben), habe ich ein CLI-Dienstprogramm, das Sie als Vermittler verwenden können. Sie können auch eine einfache Version davon in einem Skript implementieren, das als Pipe zum Protokoll dient:

#!/bin/bash

trap sighandler INT

function sighandler () {
    touch log.txt
    exec 1> log.txt
}

echo "$0 $BASHPID"
exec 1> log.txt

count=0;
while read; do
    echo $REPLY
done  

Nennen wir das mal pipetrap.sh. Jetzt benötigen wir ein separates Programm zum Testen, das die zu protokollierende Anwendung imitiert:

#!/bin/bash

count=0
while [ $count -lt 60 ]; do
    echo "$BASHPID Count is now $count"
    sleep 2
    ((count++))
done           

Das wird sein test.sh:

> (./test.sh | ./pipetrap.sh) &
./pipetrap.sh 15859

Dies sind zwei separate Prozesse mit separaten PIDs. So löschen Sie test.shdie Ausgabe, die übertragen wird pipetrap.sh:

> rm -f log.txt && kill -s 2 15859

Prüfen:

>cat log.txt
15858 Count is now 6
15858 Count is now 7
15858 Count is now 8

15858 test.shläuft noch und die Ausgabe wird protokolliert. In diesem Fall sind keine Änderungen an der Anwendung erforderlich.

Goldlöckchen
quelle
Danke für die netten Erklärungen. In meinem Fall kann ich die Anwendung jedoch nicht ändern, um Ihre Lösung zu implementieren.
Bangnab
2
Wenn Sie in Ihrer Anwendung keinen Signal-Handler implementieren können (weil Sie den Zeitraum nicht ändern können), können Sie mit dieser Technik das Protokoll durch eine Signalfalle
leiten. Weitere Informationen finden
Ok, ich werde es versuchen und dich wissen lassen, wie es gelaufen ist.
Bangnab
Ich habe endlich eine CLI-App in C dafür geschrieben (sorry, es hat etwas länger
gedauert
6

TL; DR

Öffnen Sie Ihre Protokolldatei im Anhänge- Modus:

cmd >> log

Dann können Sie es sicher abschneiden mit:

: > log

Einzelheiten

Bei einer Bourne-ähnlichen Shell gibt es drei Möglichkeiten, wie eine Datei zum Schreiben geöffnet werden kann. In write-only ( >), read + write ( <>) oder append (und write-only,>> ) Modus.

In den ersten beiden merkt sich der Kernel Ihre aktuelle Position (von Ihnen meine ich die offene Dateibeschreibung) , die von allen Dateideskriptoren geteilt wird, die sie dupliziert oder geerbt haben, indem sie von abgezweigt wurden, auf der Sie die Datei geöffnet haben) Datei.

Wenn Sie das tun:

cmd > log

logist im schreibgeschützten Modus von der Shell für die Standardausgabe von geöffnet cmd.

cmdWenn Sie in ihre Standardausgabe schreiben, schreiben Sie an die aktuelle Cursorposition, die in der geöffneten Dateibeschreibung enthalten ist sie für diese Datei freigeben.

Wenn Sie zum Beispiel cmdanfänglich schreiben zzz, befindet sich die Position bei Byte-Offset 4 in der Datei und beim nächsten Malcmd oder die untergeordneten Elemente in die Datei geschrieben werden, werden die Daten dort geschrieben, unabhängig davon, ob die Datei im Intervall gewachsen oder geschrumpft ist .

Wenn die Datei verkleinert wurde, z. B. wenn sie mit einem abgeschnitten wurde

: > log

und cmdschreibt xx, diejenigen xxgeschrieben wird bei Offset 4, und die ersten drei Zeichen durch NUL - Zeichen ersetzt werden.

$ exec 3> log # open file on fd 3.
$ printf zzz >&3
$ od -c log
0000000   z   z   z
0000003
$ printf aaaa >> log # other open file description -> different cursor
$ od -c log
0000000   z   z   z   a   a   a   a
0000007
$ printf bb >&3 # still write at the original position
$ od -c log
0000000   z   z   z   b   b   a   a
0000007
$ : > log
$ wc log
0 0 0 log
$ printf x >&3
$ od -c log
0000000  \0  \0  \0  \0  \0   x
0000006

Das bedeutet, dass Sie eine Datei, die nur im Schreibmodus geöffnet wurde (und dies gilt auch für Lesen + Schreiben ), nicht abschneiden können Datei (diese Dateien, mit Ausnahme von OS / X, belegen normalerweise keinen Speicherplatz auf der Festplatte, sondern werden zu spärlichen Dateien).

Stattdessen (und Sie werden feststellen, dass die meisten Anwendungen dies tun, wenn sie in Protokolldateien schreiben), sollten Sie die Datei im Anhänge- Modus öffnen :

cmd >> log

oder

: > log && cmd >> log

wenn Sie mit einer leeren Datei beginnen möchten.

Im Anhänge-Modus werden alle Schreibvorgänge am Ende der Datei ausgeführt, unabhängig davon, wo sich der letzte Schreibvorgang befand:

$ exec 4>> log
$ printf aa >&4
$ printf x >> log
$ printf bb >&4
$ od -c log
0000000   a   a   x   b   b
0000005
$ : > log
$ printf cc >&4
$ od -c log
0000000   c   c
0000002

Das ist auch sicherer, als hätten zwei Prozesse versehentlich die Datei geöffnet (zum Beispiel, wenn Sie zwei Instanzen desselben Daemons gestartet haben), so dass sich deren Ausgabe nicht gegenseitig überschreibt.

Auf neueren Versionen von Linux, können Sie die aktuelle Position überprüfen und ob ein Dateideskriptor in geöffnet war append - Modus an , indem Sie /proc/<pid>/fdinfo/<fd>:

$ cat /proc/self/fdinfo/4
pos:        2
flags:      0102001

Oder mit:

$ lsof +f G -p "$$" -ad 4
COMMAND  PID USER   FD   TYPE  FILE-FLAG DEVICE SIZE/OFF     NODE NAME
zsh     4870 root    4w   REG 0x8401;0x0 252,18        2 59431479 /home/chazelas/log
~# lsof +f g -p "$$" -ad 4
COMMAND  PID USER   FD   TYPE FILE-FLAG DEVICE SIZE/OFF     NODE NAME
zsh     4870 root    4w   REG   W,AP,LG 252,18        2 59431479 /home/chazelas/log

Diese Flags entsprechen den O ..._-Flags, die an den openSystemaufruf übergeben wurden.

$ gcc -E - <<< $'#include <fcntl.h>\nO_APPEND O_WRONLY' | tail -n1
02000 01

( O_APPENDist 0x400 oder oktal 02000)

So ist die Shell >>die Datei mit öffnet O_WRONLY|O_APPEND(und 0.100.000 hier ist O_LARGEFILE, die diese Frage nicht relevant ist) , während >ist O_WRONLYnur (und <>ist O_RDWRnur).

Wenn Sie Folgendes tun:

sudo lsof -nP +f g | grep ,AP

Um nach Dateien zu suchen, die mit geöffnet sind O_APPEND, finden Sie die meisten Protokolldateien, die derzeit auf Ihrem System zum Schreiben geöffnet sind.

Stéphane Chazelas
quelle
Warum verwenden Sie das :(Doppelpunkt) in : > ?
mvorisek
1
@Mvorisek, das ist die Ausgabe des Befehls zu umleiten , die keine Ausgabe erzeugt: :. Ohne einen Befehl variiert das Verhalten zwischen den Shells.
Stéphane Chazelas
1

Wenn ich das richtig verstehe, teescheint das ein vernünftiger Ansatz zu sein:

$ ./myapp-that-echoes-the-date-every-second | tee log > /dev/null &
[1] 20519
$ head log
Thu Apr  3 11:29:34 EDT 2014
Thu Apr  3 11:29:35 EDT 2014
Thu Apr  3 11:29:36 EDT 2014
$ > log
$ head log
Thu Apr  3 11:29:40 EDT 2014
Thu Apr  3 11:29:41 EDT 2014
Thu Apr  3 11:29:42 EDT 2014
Bischof
quelle
1

Als schnelle Lösung können Sie ein Protokoll mit Rotation verwenden (zum Beispiel tägliche Rotation):

date=`date +%Y%m%d`
LOGFILE=/home/log$date.log

und leiten Sie die Protokollierung darauf um ./my_app >> log$date.log

Charles Nakhel
quelle
Ich möchte nach Bedarf rotieren können. Dies ist eigentlich ein Protokoll, das während eines automatisierten Tests erstellt wird, und ich möchte es löschen, bevor der Test ausgeführt wird.
Bangnab
0

Dies ist ein Problem, das seit langem mit syslog (in allen Varianten) behoben wurde. Es gibt jedoch zwei Tools, mit denen Sie Ihr spezielles Problem mit minimalem Aufwand lösen können.

Die erste, portablere, aber weniger vielseitige Lösung ist der Logger (ein Muss für jede Administratoren-Toolbox). Es ist ein einfaches Dienstprogramm, das Standardeingaben in syslog kopiert. (Weitergeben des Geldes und Drehen von Dateien zum Problem von logrotate und syslog machen)

Die zweite, elegantere, aber weniger tragbare Lösung ist syslog-ng, mit der nicht nur Protokollnachrichten von den Standard-Syslog-Sockets akzeptiert werden, sondern auch Programme ausgeführt werden können, deren Ausgabe über den Logger gefiltert wird. (Ich habe diese Funktion noch nicht verwendet, aber sie sieht perfekt für das aus, was Sie tun möchten.)

hildred
quelle