nullバイトで埋められたファイルの変更を監視する方法は?

nullバイトで埋められたファイルの変更を監視する方法は?

nullバイトで埋められた10Mbファイルがあります。プログラムはファイルにアクセスし、ファイルの最後までゼロを特定の文字列に変更します。

を試しましたが、tail -F | grep wanted_text | grep -v "unwanted_text"変更を監視しません。ゼロで埋められたファイルではなく、プレーンテキストファイルでのみ機能します。

すべてのヌルバイトは、ファイルの最後まで改行で区切られた行に置き換えられます。ファイルがいっぱいになると名前が変更され、新しいファイルが作成されます。

それでは、nullバイトで埋められたファイルの変更をどのように監視し、出力をフィルタリングできますか?

答え1

以下は、NULパディングファイルのtailコマンドを偽造するために必要なものと同様のReaderスクリプトです。ファイルの変更を確認し(ナノ秒単位のタイムスタンプを含む完全なls -l出力を比較して)、バッチに追加された内容を報告します。起動時にファイルにすでに存在する行は報告せず、実行時に追加された行のみを報告します。

小切手の無駄を避けるために、2つの速度で実行されます。追加が検出されたら、1.0秒後にもう一度やり直してください。ループに追加が表示されない場合は、5秒後に再試行します(5はプロセスのパラメータです)。

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

答え2

全体的な概念に問題があります。

  1. 作成者はNULバイトを別の文字列に置き換えますか、それとも古い文字列の上に新しい文字列を書き込むことができますか(完全に重複しない可能性があります)?文字列の間には常に1つ以上のNUL区切り文字がありますか?

  2. 新しいNULで文字列を上書きしてファイルの一部を削除することもできますか?

  3. 元のファイルは実際には10MBのNULですか、それとも元のスパースファイルでしたか?

  4. ファイル全体を読む必要がある文字列を見つけることができることを考えると、どのくらいの頻度でこれを行う準備ができていますか?

  5. 競合状態を避けるために、ファイルの書き込み中にファイルをロックする方法はありますか?

  6. 全体の操作中にファイルサイズが変更されますか?

awk(少なくともGNU / awk)はNUL文字と長い行を処理できます。 NUL範囲のリスト(元は[0,10485760])を維持し、これらの領域に新しいフラグメントがあることを確認してください。しかし、これはオーバーレイを検出しません。ただし、追加プロセスなしで追加されたすべての内容を報告できます。

GNU / awkには、RE区切り記号に基づいて文字列を分割してフィールド配列と区切り記号配列を形成する組み込みのpatsplit()関数があります。したがって、RE / [\ 000] + /はすべての文字列を1つの配列に配置し、すべてのNUL反復を別の配列に入れる必要があります。捜査に適した候補のようです。

ところで、catコマンドはNUL文字を表示します。 odコマンドを使用してファイルに表示できます。端末に表示されない理由は、端末ドライバがこれを無視するためです。

Romeoが提案したように、古いファイルのcksumを保持すると、変更されたかどうかはわかりますが、場所は不明です。したがって、これは更新の頻度に応じて有用な最適化になる可能性があります。

答え3

私はGNU / awkとpatsplit()の使用に関する私の考えがうまくいくかどうかを確認するのに十分なことをしました。偽のWriterを設定するには、開発時間の約70%がかかりました。 10MBファイルを設定し、定期的に任意の場所に文字列を書き込むことができるddオプションのセットが見つかりました。

内容全体を1つの長い文字列としてメモリにインポートし、nullを1つの配列に分割し、文字列を別の配列に分割するリーダーがあります。 10MBを読み取るには0.044秒、文字列を配列に分割するには0.989秒、私が配置した20個の文字列の開始、長さ、内容を報告するのに0.138秒かかります。したがって、ファイルのスナップショットを撮るのに約1.2秒かかります。

すべてのタイミングは8年前の私の安いラップトップで行われました。とにかく10MB全体を解析する必要があるため、文字列が多い場合でもパフォーマンスに悪影響を及ぼさないようです。次のステップはこれを確認することです。

私は古い文字列と新しい文字列のハッシュテーブルを維持し、変更を見つけるのは簡単で効率的だと思います。

データに文字列を追加する方法についてもっと知りたいですか?前のデータと常に連続している場合は、古い文字列を見ると尾を簡単にシミュレートできます。頻繁に発生しない場合は、ファイルを読み取る前にタイムスタンプを確認できます。ファイルの最初の部分にインデックスを書き込む場合は、それを最初に確認できます。このファイルの全体的な概念のために、システムの残りの部分でどのような用途に使用されるかを確認することは困難です。これはリポジトリを使用する敵対的な方法です。

それでもこの質問に興味がありますか?以前の質問に対するOPの応答は表示されませんが、文字列の重なりなどは更新と長さの変更でのみ表示されるようです。

答え4

作家の台本です。 ddコマンドを使用して初期ファイルが存在しない場合は、一度作成し、ddを使用してスクリプトファイルの任意の行でファイルを入力します。以前はランダムな場所でこれを行いましたが、今は各場所を前の場所の後ろに配置します。指定されたパラメータの周りに平均ランダム間隔(このバージョンでは2秒)で行を追加します。特定のタイムアウトが過ぎるか、ファイルがいっぱいになると終了します。

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

関連情報