Wie überwacht man Änderungen in einer mit Nullbytes gefüllten Datei?

Wie überwacht man Änderungen in einer mit Nullbytes gefüllten Datei?

Ich habe eine 10 MB große Datei, die mit Nullbytes gefüllt ist. Ein Programm greift darauf zu und ändert Nullen bis zum Ende der Datei in bestimmte Zeichenfolgen.

Ich habe versucht, zu verwenden tail -F | grep wanted_text | grep -v "unwanted_text", aber es überwacht keine Änderungen. Es funktioniert nur für normale Textdateien, aber nicht für Dateien, die mit Nullen gefüllt sind.

Alle Nullbytes werden bis zum Ende der Datei durch Zeilen ersetzt, die durch ein neues Zeilenzeichen getrennt sind. Nachdem die Datei gefüllt ist, wird sie umbenannt und stattdessen die neue erstellt.

Wie kann ich also Änderungen an einer mit Nullbytes gefüllten Datei überwachen und dabei die Ausgabe filtern?

Antwort1

Dies ist das Skript für Reader, das dem ähneln sollte, was Sie zum Vortäuschen eines Tail-Befehls für eine mit NUL gefüllte Datei benötigen. Es sucht nach Änderungen in der Datei (durch Vergleichen der gesamten ls -l-Ausgabe, die einen Zeitstempel bis auf Nanosekunden genau enthält) und meldet alle Hinzufügungen in einem Stapel. Es meldet keine Zeilen, die beim Start bereits in der Datei vorhanden sind, sondern nur Hinzufügungen während der Ausführung.

Es läuft mit zwei Geschwindigkeiten, um unnötige Prüfungen zu vermeiden. Wenn es irgendwelche Zusätze erkennt, versucht es es nach 1,0 Sekunden erneut. Wenn ein Zyklus keine Zusätze erkennt, versucht es es nach 5 Sekunden erneut (diese 5 ist ein Argument für den Prozess).

#! /bin/bash
#: Reader: tail -f a file which is pre-formatted with many trailing NUL characters.

#### Implement the User Requirement.

function Reader {

    local RUN="${1:-60}" SLEEP="${2:-5}" FILE="${3:-/dev/null}"

    local AWK='''
BEGIN { NUL = "\000"; }
function Tick (Local, cmd, ts) {
    cmd = "date \047+%s\047";
    cmd | getline ts; close (cmd); return (ts);
}
function TS (Local, cmd, ts) {
    cmd = "date \047+%H:%M:%S.%N\047";
    cmd | getline ts; close (cmd); return (ts);
}
function Wait (secs) {
    system (sprintf ("sleep %s", secs));
}
function isChange (Local, cmd, tx) {
    cmd = sprintf ("ls 2>&1 -l --full-time \047%s\047", Fn);
    cmd | getline tx; close (cmd);
    if (tsFile == tx) return (0);
    tsFile = tx;
    if (index (tx, "\047")) {
        if (fSt != "B") { fSt = "B"; printf ("%s: No file: %s\n", TS( ), Fn); }
    } else {
        if (fSt != "G") { fSt = "G"; printf ("%s: Reading: %s\n", TS( ), Fn); }
    }
    return (1);
}
function atNul (buf, Local, j) {
    j = index (buf, NUL);
    return ((j > 0) ? j : 1 + length (buf)); 
}
function List (tx, Local, ts, X, j) {
    sub ("\012$", "", tx); split (tx, X, "\012");
    ts = TS( );
    for (j = 1; j in X; ++j) {
        printf ("%s %3d :%s:\n", ts, length (X[j]), X[j]);
    }
}
function Monitor (Local, rs, tk, Buf, Now, End) {
    printf ("%s: READER Begins\n", TS( ));
    tk = Tick( ); Expired = tk + Run;
    Now = -1;
    while (Tick( ) <= Expired) {
        if (! isChange( )) { Wait( Sleep); continue; }
        rs = RS; RS = "\000";
        Buf = ""; getline Buf < Fn; close (Fn);
        RS = rs;
        if (Now < 0) Now = atNul( Buf);
        End = atNul( Buf);
        List( substr (Buf, Now, End - Now));
        Now = End;
        Wait( 1.0);
    }
    printf ("%s: READER Exits\n", TS( ));
}
NR == 1 { Run = $0; next; }
NR == 2 { Sleep = $0; next; }
NR == 3 { Fn = $0; }
END { Monitor( Fn); }
'''
    {
        echo "${RUN}";
        echo "${SLEEP}";
        echo "${FILE}";

    } | awk -f <( echo "${AWK}" )
}

#### Script Body Starts Here.

    Reader 40 5 "./myNullFile"

Antwort2

Probleme mit dem gesamten Konzept.

  1. Ersetzt der Writer immer nur NUL-Bytes durch andere Strings oder kann er auch neue Strings über alte schreiben, möglicherweise mit unvollständigen Überlappungen? Steht zwischen den Strings immer mindestens ein NUL-Trennzeichen?

  2. Kann es Zeichenfolgen mit neuen NULs überschreiben, um auch Teile der Datei zu löschen?

  3. Besteht die Originaldatei tatsächlich aus 10 MB NUL oder handelt es sich zunächst um eine Sparse-Datei?

  4. Angesichts der Tatsache, dass wir Zeichenfolgen nur finden können, wenn wir die gesamte Datei lesen: Wie oft sind Sie bereit, dies zu tun?

  5. Gibt es eine Möglichkeit, die Datei während des Schreibens zu sperren, um Race Conditions zu vermeiden?

  6. Kann sich die Dateigröße während des gesamten Vorgangs jemals ändern?

awk (zumindest GNU/awk) kann mit NUL-Zeichen und langen Zeilen umgehen. Es könnte eine Liste von Bereichen führen, die NUL waren (anfangs nur [0,10485760]), und in diesen Bereichen nach neuer Fragmentierung suchen. Das würde Überschreibungen allerdings nicht erkennen. Aber es könnte alle Hinzufügungen ohne zusätzliche Prozesse melden.

GNU/awk hat eine integrierte patsplit()-Funktion, die einen String gemäß einem RE-Trennzeichen aufteilt und ein Array aus Feldern und ein Array aus Trennzeichen erstellt. Das RE /[\000]+/ sollte also alle Strings in ein Array und alle NUL-Wiederholungen in das andere Array setzen, und Sie können sie alle kumulativ mit length() bearbeiten, um den Gesamtoffset in der Datei für jeden String zu ermitteln. Das scheint ein hervorragender Kandidat für eine Untersuchung zu sein.

Der Befehl cat zeigt übrigens NUL-Zeichen an. Sie können sie in einer Datei mit dem Befehl od anzeigen. Der Grund, warum sie in einem Terminal nicht angezeigt werden, ist, dass der Terminaltreiber sie ignoriert.

Wie Romeo vorschlägt, würde Ihnen das Speichern einer Kontrollsumme der vorherigen Datei zwar sagen, ob sie sich geändert hat, aber nicht, wo. Je nach Häufigkeit der Aktualisierungen könnte dies also eine hilfreiche Optimierung sein.

Antwort3

Ich habe genug getan, um zu überprüfen, ob mein Konzept, GNU/awk mit patsplit() zu verwenden, umsetzbar ist. Das Einrichten eines Fake-Writers nahm etwa 70 % der Entwicklungszeit in Anspruch. Ich habe eine Reihe von dd-Optionen gefunden, mit denen ich eine 10 MB große Datei einrichten und dann in regelmäßigen Abständen Zeichenfolgen an zufälligen Stellen darin schreiben kann.

Ich habe einen Reader, der das Ganze als eine lange Zeichenfolge in den Speicher zieht und die Nullen in ein Array und die Zeichenfolgen in ein anderes trennt. Es dauert 0,044 Sekunden, um die 10 MB zu lesen, 0,989 Sekunden, um die Zeichenfolge in die Arrays aufzuteilen, und 0,138 Sekunden, um den Anfang, die Länge und den Inhalt der 20 Zeichenfolgen zu melden, die ich platziert habe. Also etwa 1,2 Sekunden, um einen Datei-Snapshot zu erstellen.

Alle Zeitmessungen wurden auf meinem 8 Jahre alten Billig-Laptop durchgeführt. Ich denke, da die gesamten 10 MB ohnehin analysiert werden müssen, wird sich eine größere Anzahl von Strings nicht so stark auf die Leistung auswirken. Der nächste Schritt besteht darin, dies zu bestätigen.

Ich glaube, dass es einfach und effizient sein wird, eine alte und eine neue Hash-Tabelle der Zeichenfolgen zu führen und die Änderungen zu finden.

Ist mehr über das Hinzufügen von Zeichenfolgen zu den Daten hier bekannt? Wenn es immer an die vorherigen Daten angrenzt, wäre es einfach, tail zu emulieren, indem man direkt nach der vorherigen Zeichenfolge sucht. Wenn es selten vorkommt, könnten wir den Zeitstempel überprüfen, bevor wir die Datei lesen. Wenn ein Index in den ersten Teil der Datei geschrieben wird, könnten wir dies zuerst überprüfen. Das gesamte Konzept dieser Datei macht es ohnehin schwer zu erkennen, welchen Nutzen sie für den Rest des Systems hat – es ist einfach eine feindliche Art, Speicher zu verwenden.

Ist diese Frage noch von Interesse? Ich sehe keine Antwort vom OP auf meine vorherigen Fragen, aber mir scheint, dass Zeichenfolgenüberlappungen usw. nur als Aktualisierungen und Längenänderungen angezeigt werden.

Antwort4

Dies ist das Skript für Writer. Es verwendet den Befehl dd, um die Ausgangsdatei in einem Durchgang zu erstellen (falls sie nicht existiert), und verwendet dann dd, um zufällige Zeilen aus einer Skriptdatei in die Datei einzufügen. Früher geschah dies an zufälligen Positionen, aber jetzt wird jede Zeile nach der vorherigen eingefügt. Es fügt Zeilen in zufälligen Intervallen hinzu, die um ein bestimmtes Argument gemittelt werden (in dieser Version 2 Sekunden). Es wird nach einer bestimmten Zeitbegrenzung oder wenn die Datei voll ist, beendet.

#! /bin/bash

#.. Declare the shared file.
FILE="./myNullFile"
SIZE=$(( 10 * 1024 * 1024 ))

#.. Using script as source of the strings.
TEXT="./isNulls"
WORK="./myLine"

#### Simulate the file writer defined in the question.

function Writer {

    local RUN="${1:-60}" SLEEP="${2:-5}"

    local AWK='''
BEGIN { printf ("%s: WRITER Begins\n", TS( )); }
function Begin (Local) {
    Pos = getNull( );
    printf ("Initial NUL is at %d\n", Pos);
    if (Pos < 0) exit;
}
function TS (Local, cmd, ts) {
    cmd = "date \047+%H:%M:%S.%N\047";
    cmd | getline ts; close (cmd); return (ts);
}
function Wait (secs) {
    system (sprintf ("sleep %s", secs));
}
function getNull (Local, rs, Buf) {
    rs = RS; RS = "^$";
    getline Buf < Fn; close (Fn);
    RS = rs;
    return (-1 + index (Buf, "\000"));
}
function Str (Local, Row, Lth) {
    Row = int (nTx * rand());
    Lth = length (Tx[Row]);
    if (Pos + Lth >= Sz) {
        printf ("%s is full: Pos %d, Lth %d, Sz %d\n", Fn, Pos, Lth, Sz);
        exit;
    }
    printf ("%s: Write Pos %10d Lth %3d Txt :%s:\n",
        TS( ), Pos, 1 + Lth, Tx[Row]);
    print Tx[Row] "\n" > Wk; close (Wk);
    system (sprintf (Fmt, Pos, 1 + Lth, Wk, Fn, Wk));
    Pos += 1 + Lth;
}
NR == 1 { Fmt = $0; srand (); next; }
NR == 2 { Fn = $0; next; }
NR == 3 { Sz = $0; next; }
NR == 4 { Wk = $0; Begin( ); next; }
NF { sub (/^[ \011]+/, ""); Tx[nTx++] = $0; next; }
{ Str( ); }
END { printf ("%s: WRITER Exits\n", TS( )); }
'''
    local EXPIRED=$(( SECONDS + RUN ))
    local AWK_WT='BEGIN { srand(); } { print 0.1 + 2 * $1 * rand(); }'
    {
        DD_OPT='status=none conv=notrunc bs=1 seek="%s" count="%s"'
        DD_FNS='if="%s" of="%s" && rm -f "%s"'

        echo "dd ${DD_OPT} ${DD_FNS}"
        echo "${FILE}"; echo "${SIZE}"; echo "${WORK}"
        awk NF "${TEXT}"
        while (( SECONDS <= EXPIRED )); do
            sleep "$( echo "${SLEEP}" | awk "${AWK_WT}" )"
            echo ''
        done
    } | awk -f <( echo "${AWK}" )
}

#### Script Body Starts Here.

    [[ -r "${FILE}" ]] || {
        dd count=1 bs="${SIZE}" if="/dev/zero" of="${FILE}"
        od -A d -t x1a "${FILE}"
    }

    Writer 32 2

verwandte Informationen