如何監控由空位元組填滿的檔案的變化?

如何監控由空位元組填滿的檔案的變化?

我有一個由空字節填充的 10Mb 檔案。程式正在存取它並將零更改為特定字串,直到文件末尾。

我嘗試過使用tail -F | grep wanted_text | grep -v "unwanted_text",但它不監視更改。它僅適用於普通文字文件,不適用於由零填充的文件。

所有空位元組都將替換為由換行符號分隔的行,直到檔案結尾。文件填滿後,將對其進行重命名,並建立新文件。

那麼,如何監視由空字節填充的文件的更改並能夠過濾輸出呢?

答案1

這是 Reader 的腳本,它應該接近為 NUL 填充文件偽造 tail 命令所需的腳本。它檢查檔案中的變更(透過比較整個 ls -l 輸出,其中包括低至奈秒的時間戳記),並報告批次中的任何新增內容。它在啟動時不會報告文件中已有的行,僅報告運行時新增的行。

它以兩種速度運行以避免浪費檢查。如果它檢測到任何添加,它會在 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 位元組,或者是否可以在舊字串上寫入新字串(可能不完全重疊)?字串之間是否始終至少有一個 NUL 分隔符號?

  2. 它也可以用新的 NUL 覆蓋字串來刪除檔案的部分內容嗎?

  3. 原始檔案真的是 10MB 的 NUL,還是最初是一個稀疏檔案?

  4. 鑑於我們只能透過讀取整個文件來查找字串,您準備多久這樣做一次?

  5. 有什麼方法可以在寫入文件時鎖定文件,以防止競爭條件?

  6. 在整個操作過程中檔案大小會改變嗎?

awk(至少,GNU/awk)可以處理 NUL 字元和長行。它可以保留 NUL 範圍的清單(最初只是 [0,10485760]),並檢查這些區域中是否有新的碎片。但這不會偵測到覆蓋。但它能夠報告所有添加的內容,而無需任何額外的流程。

GNU/awk 有一個內建的 patsplit() 函數,它根據 RE 分隔符號分割字串,形成一個字段數組和一個分隔符號數組。因此RE /[\000]+/ 應該將所有字串放在一個數組中,所有NUL 在另一個數組中重複,並且您可以將它們全部累積起來,以查找每個字串在文件中的總偏移量。這看起來是一個很好的調查候選人。

順便說一句,cat 命令確實顯示 NUL 字元。您可以使用 od 命令在檔案中查看它們。它們沒有出現在終端機中的原因是終端驅動程式忽略了它們。

正如羅密歐建議的那樣,保留前一個文件的 cksum 可以告訴您它是否已更改,但不能告訴您更改的位置。因此,這可能是一個有用的最佳化,具體取決於更新頻率。

答案3

我已經做了足夠的工作來驗證我使用 GNU/awk 和 patsplit() 的想法是可行的。設定一個假 Writer 大約花了 70% 的開發時間。我找到了一組 dd 選項,可以讓我設定一個 10MB 的文件,然後定期在其中的隨機位置寫入字串。

我有一個閱讀器,它將整個內容作為一個長字串拖入內存,並將空值分成一個數組,將字串分成另一個數組。讀取 10MB 需要 0.044 秒,將字串拆分為數組需要 0.989 秒,報告我放置的 20 個字串的開始、長度和內容需要 0.138 秒。所以大約需要1.2秒來做一個檔案快照。

所有計時都是在我用了 8 年的廉價筆記型電腦上完成的。我認為,由於無論如何它都必須解析整個 10MB,因此擁有更多字串不會對效能產生那麼嚴重的影響。下一步是確認這一點。

我相信保留字串的新舊哈希表並查找更改將是簡單且高效的。

關於向資料添加字串還有更多了解嗎?如果它總是與先前的資料連續,那麼透過查看前一個字串就可以輕鬆模擬 tail。如果不頻繁,我們可以在讀取文件之前檢查時間戳。如果它正在將索引寫入檔案的第一部分,那麼我們可以先檢查這一點。這個文件的整個概念讓人很難看出它對系統的其餘部分有什麼用處——它只是一種敵對的使用儲存的方式。

這個問題還有興趣嗎?我沒有看到OP對我之前的問題有任何回應,但在我看來,字串重疊等只會顯示為更新和長度變化。

答案4

這是 Writer 的腳本。它使用 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

相關內容