
ヌルバイトで埋められた 10 MB のファイルがあります。プログラムはそれにアクセスし、ファイルの最後までゼロを特定の文字列に変更します。
を使用しようとしましたtail -F | grep wanted_text | grep -v "unwanted_text"
が、変更を監視しません。通常のテキスト ファイルに対してのみ機能し、ゼロで埋められたファイルに対しては機能しません。
すべてのヌル バイトは、ファイルの最後まで、改行文字で区切られた行に置き換えられます。ファイルがいっぱいになると、名前が変更され、代わりに新しいファイルが作成されます。
では、出力をフィルタリングする機能を使用して、ヌルバイトで埋められたファイルの変更を監視するにはどうすればよいでしょうか?
答え1
これは Reader のスクリプトで、NUL で埋められたファイルに対して tail コマンドを偽装するために必要なものに近いはずです。ファイル内の変更をチェックし (ナノ秒単位のタイムスタンプを含む 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
全体のコンセプトに問題がある。
ライターは NUL バイトを他の文字列に置き換えるだけですか、それとも、不完全な重複がある可能性もある古い文字列の上に新しい文字列を書き込むことができますか? 文字列の間には常に少なくとも 1 つの NUL 区切り文字が含まれますか?
文字列を新しい NUL で上書きして、ファイルの一部を消去することもできますか?
元のファイルは本当に 10 MB の NUL ですか、それとも最初はスパース ファイルですか?
ファイル全体を読み取ることによってのみ文字列を見つけることができることを考えると、どのくらいの頻度でこれを実行する準備ができていますか?
競合状態を防ぐために、書き込み中にファイルをロックする方法はありますか?
操作中にファイル サイズが変更されることはありますか?
awk (少なくとも、GNU/awk) は、NUL 文字と長い行を処理できます。NUL であった範囲のリスト (最初は [0,10485760] のみ) を保持し、それらの領域で新しい断片化をチェックできます。ただし、上書きは検出されません。ただし、追加のプロセスなしですべての追加をレポートできます。
GNU/awk には組み込みの patsplit() 関数があり、これは文字列を RE セパレータに従って分割し、フィールドの配列とセパレータの配列を作成します。したがって、RE /[\000]+/ はすべての文字列を 1 つの配列に、すべての NUL 繰り返しを別の配列に格納し、それらをすべて累積的に length() して、各文字列のファイル内の合計オフセットを見つけることができます。これは調査するのに最適な候補のようです。
ちなみに、cat コマンドは NUL 文字を表示します。od コマンドを使用すると、ファイル内で NUL 文字を表示できます。ターミナルに NUL 文字が表示されないのは、ターミナル ドライバーが NUL 文字を無視するためです。
Romeo が示唆しているように、以前のファイルの cksum を保持しておくと、変更があったかどうかはわかりますが、どこが変更されたかはわかりません。したがって、更新の頻度によっては、これは役立つ最適化となる可能性があります。
答え3
patsplit() で GNU/awk を使用するという私のコンセプトが実現可能であることを十分に検証しました。偽の Writer の設定には開発時間の約 70% を費やしました。10MB のファイルを設定して、その中のランダムな場所に定期的に文字列を書き込むことができる dd オプションのセットを見つけました。
全体を 1 つの長い文字列としてメモリにドラッグし、ヌルを 1 つの配列に、文字列を別の配列に分割するリーダーがあります。10 MB の読み取りには 0.044 秒、文字列を配列に分割するには 0.989 秒、配置した 20 個の文字列の開始、長さ、および内容を報告するには 0.138 秒かかります。つまり、ファイルのスナップショットを実行するには約 1.2 秒かかります。
すべてのタイミングは、8 年前の安物のラップトップで実行されました。いずれにしても 10 MB 全体を解析する必要があるため、文字列の数が増えてもパフォーマンスにそれほど大きな影響はないと思います。次のステップは、それを確認することです。
文字列の新旧ハッシュ テーブルを保持し、変更点を見つけることが簡単かつ効率的になると思います。
ここでデータに文字列が追加されることについて、他に何かわかっていますか? 常に前のデータと連続している場合は、前の文字列の直後を調べることで tail をエミュレートするのは簡単です。頻度が低い場合は、ファイルを読み取る前にタイムスタンプをチェックできます。ファイルの最初の部分にインデックスを書き込んでいる場合は、最初にこれをチェックできます。いずれにしても、このファイルの全体的な概念により、システムの残りの部分でどのような用途があるのかがわかりにくくなっています。これは、ストレージを使用するための敵対的な方法です。
この質問はまだ興味深いですか? 以前の質問に対する OP からの回答は見当たりませんが、文字列の重複などは更新や長さの変更として表示されるだけのように思えます。
答え4
これは Writer のスクリプトです。dd コマンドを使用して、最初のファイルを 1 回で作成し (存在しない場合)、次に 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