null 바이트로 채워진 10Mb 파일이 있습니다. 프로그램은 파일에 액세스하여 파일 끝까지 0을 특정 문자열로 변경합니다.
을 사용해 보았지만 tail -F | grep wanted_text | grep -v "unwanted_text"
변경 사항을 모니터링하지 않습니다. 0으로 채워진 파일이 아닌 일반 텍스트 파일에서만 작동합니다.
모든 널 바이트는 파일 끝까지 개행으로 구분된 행으로 대체됩니다. 파일이 채워지면 이름이 바뀌고 새 파일이 생성됩니다.
그렇다면 null 바이트로 채워진 파일의 변경 사항을 어떻게 모니터링하고 출력을 필터링할 수 있습니까?
답변1
다음은 NUL 패딩 파일에 대한 tail 명령을 위조하는 데 필요한 것과 유사한 Reader 스크립트입니다. 파일의 변경 사항을 확인하고(나노초 단위의 타임스탬프를 포함하는 전체 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에는 RE 구분 기호를 기반으로 문자열을 분할하여 필드 배열과 구분 기호 배열을 형성하는 내장 patsplit() 함수가 있습니다. 따라서 RE /[\000]+/는 모든 문자열을 하나의 배열에 넣고 모든 NUL 반복을 다른 배열에 넣어야 하며, 이를 모두 누적하여 총 오프셋 파일에서 각 문자열이 어디에 있는지 찾을 수 있습니다. 수사에 적합한 후보인 것 같습니다.
그런데 cat 명령은 NUL 문자를 표시합니다. od 명령을 사용하여 파일에서 볼 수 있습니다. 터미널에 표시되지 않는 이유는 터미널 드라이버가 이를 무시하기 때문입니다.
Romeo가 제안한 것처럼 이전 파일의 cksum을 유지하면 변경되었는지 여부는 알 수 있지만 위치는 알 수 없습니다. 따라서 이는 업데이트 빈도에 따라 유용한 최적화가 될 수 있습니다.
답변3
나는 GNU/awk 및 patsplit() 사용에 대한 내 생각이 작동하는지 확인하기 위해 충분한 작업을 수행했습니다. 가짜 Writer를 설정하는 데에는 개발 시간의 약 70%가 소요되었습니다. 10MB 파일을 설정하고 주기적으로 임의의 위치에 문자열을 쓸 수 있는 dd 옵션 세트를 찾았습니다.
전체 내용을 하나의 긴 문자열로 메모리에 가져오고 null을 하나의 배열로 분할하고 문자열을 다른 배열로 분할하는 판독기가 있습니다. 10MB를 읽는 데 0.044초, 문자열을 배열로 분할하는 데 0.989초, 내가 배치한 20개의 문자열의 시작, 길이, 내용을 보고하는 데 0.138초가 걸립니다. 따라서 파일 스냅샷을 찍는 데 약 1.2초가 걸립니다.
모든 타이밍은 8년 된 내 저렴한 노트북에서 이루어졌습니다. 어쨌든 전체 10MB를 구문 분석해야 하기 때문에 문자열이 더 많아도 성능에 그다지 나쁜 영향을 미치지는 않을 것 같습니다. 다음 단계는 이를 확인하는 것입니다.
나는 이전 문자열과 새 문자열의 해시 테이블을 유지하고 변경 사항을 찾는 것이 간단하고 효율적이라고 믿습니다.
데이터에 문자열을 추가하는 방법에 대해 더 알고 싶으십니까? 이전 데이터와 항상 연속적인 경우 이전 문자열을 보면 꼬리를 쉽게 시뮬레이션할 수 있습니다. 자주 발생하지 않는 경우 파일을 읽기 전에 타임스탬프를 확인할 수 있습니다. 파일의 첫 번째 부분에 인덱스를 쓰는 경우 이를 먼저 확인할 수 있습니다. 이 파일의 전체 개념으로 인해 시스템의 나머지 부분에 어떤 용도로 사용되는지 확인하기가 어렵습니다. 이는 저장소를 사용하는 적대적인 방법일 뿐입니다.
아직도 이 질문에 관심이 있으신가요? 이전 질문에 대한 OP의 응답은 표시되지 않지만 문자열 겹침 등은 업데이트 및 길이 변경으로만 표시되는 것 같습니다.
답변4
작가의 대본입니다. 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