
У меня есть файл размером 10 Мб, заполненный нулевыми байтами. Программа обращается к нему и меняет нули на определенные строки до конца файла.
Я пробовал использовать tail -F | grep wanted_text | grep -v "unwanted_text"
, но он не отслеживает изменения. Он работает только для обычных текстовых файлов, но не для файлов, заполненных нулями.
Все нулевые байты заменяются строками, разделенными символом новой строки до конца файла. После заполнения файла он переименовывается и вместо него создается новый.
Так как же мне отслеживать изменения файла, заполненного нулевыми байтами, с возможностью фильтрации вывода?
решение1
Это скрипт для Reader, который должен быть близок к тому, что вам нужно для подделки команды tail для файла, заполненного NUL. Он проверяет изменения в файле (сравнивая весь вывод 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-значениями, чтобы стереть части файла?
Действительно ли исходный файл содержит 10 МБ NUL или изначально это разреженный файл?
Учитывая, что мы можем найти строки, только прочитав весь файл, как часто вы готовы это делать?
Есть ли способ заблокировать файл во время записи, чтобы предотвратить возникновение гонки?
Может ли размер файла измениться в ходе всей операции?
awk (по крайней мере, GNU/awk) может работать с символами NUL и длинными строками. Он может хранить список диапазонов, которые были NUL (изначально просто [0,10485760]), и проверять наличие новой фрагментации в этих регионах. Это, однако, не обнаружит перезаписей. Но он сможет сообщать обо всех добавлениях без дополнительных процессов.
В GNU/awk есть встроенная функция patsplit(), которая разрезает строку в соответствии с разделителем RE, создавая массив полей и массив разделителей. Таким образом, RE /[\000]+/ должно поместить все строки в один массив, а все повторы NUL в другой массив, и вы можете length() их все кумулятивно найти общее смещение в файле для каждой строки. Это выглядит как отличный кандидат для исследования.
Кстати, команда cat отображает символы NUL. Вы можете увидеть их в файле с помощью команды od. Причина, по которой они не отображаются в терминале, заключается в том, что драйвер терминала их игнорирует.
Как предполагает Ромео, сохранение cksum предыдущего файла скажет вам, изменился ли он, но не где. Так что это может быть полезной оптимизацией, в зависимости от частоты обновлений.
решение3
Я сделал достаточно, чтобы убедиться, что моя концепция использования GNU/awk с patsplit() осуществима. Настройка фальшивого Writer заняла около 70% времени разработки. Я нашел набор опций dd, который позволяет мне настроить файл размером 10 МБ, а затем периодически записывать строки в случайные места в нем.
У меня есть Reader, который перетаскивает все это в память как одну длинную строку и разделяет нулевые значения в один массив, а строки в другой. Чтение 10 МБ занимает 0,044 секунды, разбиение строки на массивы — 0,989 секунды, а сообщение о начале, длине и содержимом 20 строк, которые я поместил, занимает 0,138 секунды. То есть, чтобы сделать снимок файла, требуется около 1,2 секунды.
Все замеры времени сделаны на моем 8-летнем дешевом ноутбуке. Я думаю, что, поскольку ему в любом случае придется анализировать все 10 МБ, наличие большего количества строк не повлияет на производительность так уж сильно. Следующий шаг — подтвердить это.
Я считаю, что сохранение старой и новой хеш-таблицы строк и поиск изменений будет простым и эффективным.
Известно ли что-нибудь еще о добавлении строк к данным здесь? Если бы это всегда было смежно с предыдущими данными, было бы легко эмулировать 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