Gryu Asked: 2019-11-26 06:32:21 +0800 CST2019-11-26 06:32:21 +0800 CST 2019-11-26 06:32:21 +0800 CST 如何监控由空字节填充的文件的变化? 772 我有一个由空字节填充的 10Mb 文件。程序正在访问它并将零更改为特定字符串,直到文件末尾。 我尝试使用tail -F | grep wanted_text | grep -v "unwanted_text",但它不监视更改。它仅适用于通常的文本文件,但不适用于由零填充的文件。 所有空字节都被替换为由换行符分隔的行,直到文件末尾。文件填满后,它被重命名,而是创建新文件。 那么如何通过过滤输出来监视由空字节填充的文件的变化呢? linux files 5 个回答 Voted Paul_Pedant 2019-11-26T17:17:54+08:002019-11-26T17:17:54+08:00 整个概念的问题。 作者是否只用其他字符串替换 NUL 字节,或者它可以在旧字符串上写入新字符串,可能有不完整的重叠?字符串之间是否总是至少有一个 NUL 分隔符? 它也可以用新的 NUL 覆盖字符串以擦除文件的某些部分吗? 原始文件真的是 10MB 的 NUL,还是最初是稀疏文件? 鉴于我们只能通过读取整个文件来查找字符串,您准备多久执行一次? 有没有办法在写入文件时锁定文件,以防止出现竞争条件? 在整个操作过程中文件大小可以改变吗? awk(至少,GNU/awk)可以处理 NUL 字符和长行。它可以保留一个 NUL 范围列表(最初只是 [0,10485760]),并检查这些区域中的新碎片。不过,这不会检测到覆盖。但它无需任何额外流程即可报告所有添加内容。 GNU/awk 有一个内置的 patsplit() 函数,它根据 RE 分隔符分割字符串,生成字段数组和分隔符数组。所以 RE /[\000]+/ 应该将所有字符串放在一个数组中,所有 NUL 都在另一个数组中重复,您可以将它们全部累加长度()以找到每个字符串在文件中的总偏移量。这看起来是一个很好的调查对象。 顺便说一句,cat 命令确实显示 NUL 字符。您可以使用 od 命令在文件中查看它们。它们没有出现在终端中的原因是终端驱动程序忽略了它们。 正如罗密欧建议的那样,保留前一个文件的 cksum 会告诉您它是否已更改,但不会告诉您更改的位置。因此,这可能是一个有用的优化,具体取决于更新的频率。 Paul_Pedant 2019-11-27T16:44:08+08:002019-11-27T16:44:08+08:00 我已经做了足够多的工作来验证我将 GNU/awk 与 patsplit() 结合使用的概念是否可行。设置一个假 Writer 大约需要 70% 的开发时间。我找到了一组 dd 选项,可以让我设置一个 10MB 的文件,然后定期在其中的随机位置写入字符串。 我有一个阅读器,它将整个东西作为一个长字符串拖到内存中,并将空值分隔到一个数组中,将字符串分隔到另一个数组中。读取 10MB 需要 0.044 秒,将字符串拆分为数组需要 0.989 秒,报告我放置的 20 个字符串的开始、长度和内容需要 0.138 秒。所以大约需要 1.2 秒来做一个文件快照。 所有计时都是在我使用了 8 年的便宜笔记本电脑上完成的。我认为,因为无论如何它都必须解析整个 10MB,所以有更多的字符串不会对性能造成那么严重的影响。下一步是确认这一点。 我相信保留字符串的新旧哈希表并查找更改将简单有效。 对这里的数据添加字符串有更多了解吗?如果它总是与以前的数据连续,那么通过查看前一个字符串就可以很容易地模拟 tail。如果它不频繁,我们可以在读取文件之前检查时间戳。如果它正在将索引写入文件的第一部分,那么我们可以先检查一下。这个文件的整个概念使得很难看出它对系统的其余部分有什么用——它只是一种使用存储的敌对方式。 这个问题还有兴趣吗?我没有看到 OP 对我之前的问题做出任何回应,但在我看来,字符串重叠等只会显示为更新和长度变化。 Paul_Pedant 2019-12-03T13:55:19+08:002019-12-03T13:55:19+08:00 我已经测试了 Writer(以生成测试)和 Reader(您要求的脚本)的代码,并将在稍后发布。这是一个 30 秒的测试运行,以证明这是有效的。Writer 在开始使用 dd 时创建一个 10MB 的文件,并使用 od 转储它,并将每个输出报告为 Write Pos。数据是脚本文件中的随机行。Reader 只显示长度和内容(每个长度短 1,因为写入包含 NL,读取丢弃 NL)。当您决定如何以及何时发生这种情况时,我们需要修复文件之间的转换。 Paul--) ~/wdog -w 1m; ./Reader & sleep 3; ./Writer & jobs; wait [1] 25030 21:40:00.032622643: READER Begins 21:40:00.059864870: No file: ./myNullFile [2] 25066 [1]- Running ./Reader & [2]+ Running ./Writer & 1+0 records in 1+0 records out 10485760 bytes (10 MB, 10 MiB) copied, 0.0421849 s, 249 MB/s 0000000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 nul nul nul nul nul nul nul nul nul nul nul nul nul nul nul nul * 10485760 21:40:03.155837791: WRITER Begins 21:40:03.166173417: Reading: ./myNullFile Initial NUL is at 0 21:40:04.053667668: Write Pos 0 Lth 32 Txt :print Tx[Row] > Wk; close (Wk);: 21:40:04.221015418 31 :print Tx[Row] > Wk; close (Wk);: 21:40:07.453403168: Write Pos 32 Lth 26 Txt :Row = int (nTx * rand());: 21:40:08.320950327: Write Pos 58 Lth 26 Txt :Row = int (nTx * rand());: 21:40:08.331308212 25 :Row = int (nTx * rand());: 21:40:08.331308212 25 :Row = int (nTx * rand());: 21:40:11.526810010: Write Pos 84 Lth 42 Txt :n = patsplit (buf, Str, /[^\000]+/, Sep);: 21:40:12.444730213 41 :n = patsplit (buf, Str, /[^\000]+/, Sep);: 21:40:12.998876406: Write Pos 126 Lth 51 Txt :function Monitor (Local, rs, tk, Buf, Now, P, Q) {: 21:40:13.474633987 50 :function Monitor (Local, rs, tk, Buf, Now, P, Q) {: 21:40:14.604161200: Write Pos 177 Lth 43 Txt :Parser( Q, Buf); Differ( Q, P); Now = "P";: 21:40:15.529375510 42 :Parser( Q, Buf); Differ( Q, P); Now = "P";: 21:40:18.381688129: Write Pos 220 Lth 44 Txt :cmd | getline ts; close (cmd); return (ts);: 21:40:18.611992430 43 :cmd | getline ts; close (cmd); return (ts);: 21:40:19.583131365: Write Pos 264 Lth 19 Txt :split ("", X, FS);: 21:40:19.642066181 18 :split ("", X, FS);: 21:40:20.670745471: Write Pos 283 Lth 2 Txt :}: 21:40:21.701154684 1 :}: 21:40:23.060019472: Write Pos 285 Lth 5 Txt :done: 21:40:23.749500189 4 :done: 21:40:25.956196125: Write Pos 290 Lth 2 Txt :}: 21:40:26.832889643 1 :}: 21:40:27.098544055: Write Pos 292 Lth 19 Txt :split ("", X, FS);: 21:40:27.861066600 18 :split ("", X, FS);: 21:40:30.707892127: Write Pos 311 Lth 32 Txt :print Tx[Row] > Wk; close (Wk);: 21:40:30.939979744 31 :print Tx[Row] > Wk; close (Wk);: 21:40:33.705042808: Write Pos 343 Lth 15 Txt :Reader 30 10 &: 21:40:34.010378554 14 :Reader 30 10 &: 21:40:35.103897039: Write Pos 358 Lth 2 Txt :}: 21:40:36.063585547 1 :}: 21:40:38.661280841: Write Pos 360 Lth 49 Txt :printf ("%s: Write Pos %10d Lth %3d Txt :%s:\n",: 21:40:38.674634657: WRITER Exits 21:40:39.144828936 48 :printf ("%s: Write Pos %10d Lth %3d Txt :%s:\n",: 21:40:41.188952035: READER Exits [1]- Done ./Reader [2]+ Done ./Writer Paul_Pedant 2019-12-03T14:16:54+08:002019-12-03T14:16:54+08:00 这是 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 Best Answer Paul_Pedant 2019-12-03T14:39:45+08:002019-12-03T14:39:45+08:00 这是 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"
整个概念的问题。
作者是否只用其他字符串替换 NUL 字节,或者它可以在旧字符串上写入新字符串,可能有不完整的重叠?字符串之间是否总是至少有一个 NUL 分隔符?
它也可以用新的 NUL 覆盖字符串以擦除文件的某些部分吗?
原始文件真的是 10MB 的 NUL,还是最初是稀疏文件?
鉴于我们只能通过读取整个文件来查找字符串,您准备多久执行一次?
有没有办法在写入文件时锁定文件,以防止出现竞争条件?
在整个操作过程中文件大小可以改变吗?
awk(至少,GNU/awk)可以处理 NUL 字符和长行。它可以保留一个 NUL 范围列表(最初只是 [0,10485760]),并检查这些区域中的新碎片。不过,这不会检测到覆盖。但它无需任何额外流程即可报告所有添加内容。
GNU/awk 有一个内置的 patsplit() 函数,它根据 RE 分隔符分割字符串,生成字段数组和分隔符数组。所以 RE /[\000]+/ 应该将所有字符串放在一个数组中,所有 NUL 都在另一个数组中重复,您可以将它们全部累加长度()以找到每个字符串在文件中的总偏移量。这看起来是一个很好的调查对象。
顺便说一句,cat 命令确实显示 NUL 字符。您可以使用 od 命令在文件中查看它们。它们没有出现在终端中的原因是终端驱动程序忽略了它们。
正如罗密欧建议的那样,保留前一个文件的 cksum 会告诉您它是否已更改,但不会告诉您更改的位置。因此,这可能是一个有用的优化,具体取决于更新的频率。
我已经做了足够多的工作来验证我将 GNU/awk 与 patsplit() 结合使用的概念是否可行。设置一个假 Writer 大约需要 70% 的开发时间。我找到了一组 dd 选项,可以让我设置一个 10MB 的文件,然后定期在其中的随机位置写入字符串。
我有一个阅读器,它将整个东西作为一个长字符串拖到内存中,并将空值分隔到一个数组中,将字符串分隔到另一个数组中。读取 10MB 需要 0.044 秒,将字符串拆分为数组需要 0.989 秒,报告我放置的 20 个字符串的开始、长度和内容需要 0.138 秒。所以大约需要 1.2 秒来做一个文件快照。
所有计时都是在我使用了 8 年的便宜笔记本电脑上完成的。我认为,因为无论如何它都必须解析整个 10MB,所以有更多的字符串不会对性能造成那么严重的影响。下一步是确认这一点。
我相信保留字符串的新旧哈希表并查找更改将简单有效。
对这里的数据添加字符串有更多了解吗?如果它总是与以前的数据连续,那么通过查看前一个字符串就可以很容易地模拟 tail。如果它不频繁,我们可以在读取文件之前检查时间戳。如果它正在将索引写入文件的第一部分,那么我们可以先检查一下。这个文件的整个概念使得很难看出它对系统的其余部分有什么用——它只是一种使用存储的敌对方式。
这个问题还有兴趣吗?我没有看到 OP 对我之前的问题做出任何回应,但在我看来,字符串重叠等只会显示为更新和长度变化。
我已经测试了 Writer(以生成测试)和 Reader(您要求的脚本)的代码,并将在稍后发布。这是一个 30 秒的测试运行,以证明这是有效的。Writer 在开始使用 dd 时创建一个 10MB 的文件,并使用 od 转储它,并将每个输出报告为 Write Pos。数据是脚本文件中的随机行。Reader 只显示长度和内容(每个长度短 1,因为写入包含 NL,读取丢弃 NL)。当您决定如何以及何时发生这种情况时,我们需要修复文件之间的转换。
这是 Writer 的脚本。它使用 dd 命令一次性创建初始文件(如果它不存在),然后使用 dd 将脚本文件中的随机行填充到文件中。它曾经在随机位置执行此操作,但现在它将每个位置都放在前一个位置之后。它以围绕给定参数平均的随机间隔添加行(此版本中为 2 秒)。它在特定时间限制后或文件已满时退出。
这是 Reader 的脚本,它应该接近于为 NUL 填充文件伪造 tail 命令所需的内容。它检查文件中的更改(通过比较整个 ls -l 输出,其中包括低至纳秒的时间戳),并报告批处理中的任何添加。它在启动时不报告文件中已经存在的行,仅在运行时添加。
它以两种速度运行,以避免浪费检查。如果它检测到任何添加,它会在 1.0 秒后再次尝试。如果一个循环没有看到任何添加,它会在 5 秒后再次尝试(这 5 是该过程的参数)。