
我有一個由空字節填充的 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
整個概念有問題。
編寫器是否只用其他字串取代 NUL 位元組,或者是否可以在舊字串上寫入新字串(可能不完全重疊)?字串之間是否始終至少有一個 NUL 分隔符號?
它也可以用新的 NUL 覆蓋字串來刪除檔案的部分內容嗎?
原始檔案真的是 10MB 的 NUL,還是最初是一個稀疏檔案?
鑑於我們只能透過讀取整個文件來查找字串,您準備多久這樣做一次?
有什麼方法可以在寫入文件時鎖定文件,以防止競爭條件?
在整個操作過程中檔案大小會改變嗎?
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