null 바이트로 채워진 파일의 변경 사항을 모니터링하는 방법은 무엇입니까?

null 바이트로 채워진 파일의 변경 사항을 모니터링하는 방법은 무엇입니까?

null 바이트로 채워진 10Mb 파일이 있습니다. 프로그램이 이에 액세스하고 파일 끝까지 0을 특정 문자열로 변경합니다.

을(를) 사용해 보았지만 tail -F | grep wanted_text | grep -v "unwanted_text"변경 사항을 모니터링하지 않습니다. 일반적인 텍스트 파일에만 작동하지만 0으로 채워진 파일에는 작동하지 않습니다.

모든 널 바이트는 파일 끝까지 개행 문자로 구분된 행으로 대체됩니다. 파일이 가득 차면 이름이 바뀌고 대신 새 파일이 생성됩니다.

그렇다면 출력 필터링 기능을 사용하여 null 바이트로 채워진 파일의 변경 사항을 어떻게 모니터링할 수 있습니까?

답변1

이것은 NUL로 채워진 파일에 대한 꼬리 명령을 위조하는 데 필요한 것과 유사한 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

전체 개념에 문제가 있습니다.

  1. 작성자는 NUL 바이트를 다른 문자열로만 대체합니까, 아니면 불완전한 겹침이 있는 이전 문자열 위에 새 문자열을 쓸 수 있습니까? 문자열 사이에는 항상 하나 이상의 NUL 구분 기호가 있습니까?

  2. 파일의 일부를 지우기 위해 새로운 NUL로 문자열을 덮어쓸 수도 있나요?

  3. 원본 파일은 실제로 10MB의 NUL입니까, 아니면 처음에는 스파스 파일입니까?

  4. 전체 파일을 읽어야 문자열을 찾을 수 있다는 점을 고려하면 얼마나 자주 이 작업을 수행할 준비가 되어 있습니까?

  5. 경쟁 조건을 방지하기 위해 파일을 쓰는 동안 파일을 잠글 수 있는 방법이 있습니까?

  6. 전체 작업 중에 파일 크기가 변경될 수 있나요?

awk(적어도 GNU/awk)는 NUL 문자와 긴 줄을 처리할 수 있습니다. NUL(처음에는 [0,10485760])인 범위 목록을 유지하고 해당 영역에서 새로운 조각화를 확인할 수 있습니다. 하지만 덮어쓰기는 감지되지 않습니다. 하지만 추가 프로세스 없이 모든 추가 사항을 보고할 수 있습니다.

GNU/awk에는 RE 구분 기호에 따라 문자열을 잘라내어 필드 배열과 구분 기호 배열을 만드는 내장 patsplit() 함수가 있습니다. 따라서 RE /[\000]+/는 모든 문자열을 한 배열에 넣고 모든 NUL은 다른 배열에 반복해야 하며, 이를 모두 누적하여 length()하여 각 문자열에 대한 파일의 총 오프셋을 찾을 수 있습니다. 수사에 아주 적합한 후보인 것 같습니다.

그런데 cat 명령은 NUL 문자를 표시합니다. od 명령을 사용하여 파일에서 볼 수 있습니다. 터미널에 표시되지 않는 이유는 터미널 드라이버가 이를 무시하기 때문입니다.

Romeo가 제안한 것처럼 이전 파일의 cksum을 유지하면 변경되었는지 여부는 알 수 있지만 위치는 알 수 없습니다. 따라서 업데이트 빈도에 따라 유용한 최적화가 될 수 있습니다.

답변3

나는 patsplit()과 함께 GNU/awk를 사용하는 개념이 실현 가능하다는 것을 충분히 검증했습니다. 가짜 Writer를 설정하는 데에는 개발 시간의 약 70%가 소요되었습니다. 10MB 파일을 설정한 다음 주기적으로 임의의 위치에 문자열을 쓸 수 있는 dd 옵션 세트를 찾았습니다.

나는 모든 것을 하나의 긴 문자열로 메모리에 끌어서 null을 하나의 배열로 분리하고 문자열을 다른 배열로 분리하는 Reader를 가지고 있습니다. 10MB를 읽는 데 0.044초, 문자열을 배열로 분할하는 데 0.989초, 배치한 20개의 문자열의 시작, 길이 및 내용을 보고하는 데 0.138초가 걸립니다. 따라서 파일 스냅샷을 만드는 데 약 1.2초가 소요됩니다.

모든 타이밍은 내 8년 된 저렴한 노트북에서 이루어졌습니다. 어쨌든 10MB 전체를 파싱해야 하기 때문에 문자열이 많아진다고 해서 성능이 크게 저하되지는 않을 것이라고 생각합니다. 다음 단계는 이를 확인하는 것입니다.

나는 문자열의 이전 해시 테이블과 새 해시 테이블을 유지하고 변경 사항을 찾는 것이 간단하고 효율적이라고 믿습니다.

여기 데이터에 문자열을 추가하는 것에 대해 더 알려진 것이 있나요? 이전 데이터와 항상 연속되어 있었다면 이전 문자열 바로 뒤를 살펴봄으로써 꼬리를 에뮬레이트하는 것이 쉬울 것입니다. 자주 발생하지 않는 경우 파일을 읽기 전에 타임스탬프를 확인할 수 있습니다. 파일의 첫 번째 부분에 인덱스를 쓰는 경우 이를 먼저 확인할 수 있습니다. 이 파일의 전체 개념으로 인해 시스템의 나머지 부분에 어떤 용도로 사용되는지 파악하기가 어렵습니다. 이는 저장소를 사용하는 적대적인 방법일 뿐입니다.

이 질문이 아직도 흥미롭나요? 이전 질문에 대한 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

관련 정보