如何监控由空字节填充的文件的变化?

如何监控由空字节填充的文件的变化?

我有一个由空字节填充的 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

相关内容