awk(및 gawk): N개의 입력 파일 중 1개를 읽을 수 없을 때 치명적인 오류를 방지하는 방법

awk(및 gawk): N개의 입력 파일 중 1개를 읽을 수 없을 때 치명적인 오류를 방지하는 방법

테스트 사례: (루트가 000 권한을 무시하므로 루트가 아닌 사용자를 사용하십시오...)

#in a clean directory:
[ -f file_1 ] && chmod 600 file_? # for repeat tests...
for i in file_1 file_2 file_3; do
    printf 'A\nB\n' > "$i" 
    # we need at least 1 char : awk/gawk silently skips empty files...
done
chmod 000 file_2
awk '(FNR==1) { print FILENAME }' file_? 
  # tried with : regular (old unixes) awk on AIX. and gawk on Linux.
  # the fatal "permission denied" on file_2 stops [g]awk.

치명적인 오류를 포착하고 다음 파일을 계속 처리할 수 있는 방법이 있습니까?

(그렇지 않으면 걱정스럽습니다. 여러 파일에 awk를 사용한다고 해서 모든 파일이 처리된다는 보장은 없습니다. 파일 중 하나라도 읽을 수 없으면 치명적으로 종료되기 때문입니다.)

가능하다면: 답변해주세요

  • 일반 awk의 경우,
  • 그리고 바보
  • 다른 관련 awk 버전이 있습니까? (안돼? 등)

답변1

GAWK를 사용하면:

gawk 'BEGINFILE { if (ERRNO) nextfile } (FNR==1) { print FILENAME }' file_?

BEGINFILE블록 에서는 ERRNO파일이 성공적으로 열리면 비어 있습니다. nextfile이는 다음 파일로 점프하고 오류로 인해 종료되는 것을 방지하는 데 사용할 수 있습니다.

나는 AWK의 다른 구현이 이것을 지원한다고 생각하지 않습니다.

이식 가능하게는 모든 인수를 반복하여 읽을 수 없는 파일을 가리키는지 확인하고, 그렇다면 AWK가 수동으로 처리를 시작하기 전에 인수에서 해당 인수를 제거할 수 있습니다.구현 예가 있습니다.. 그러나 이 루프를 사용하여 검사된 파일은 AWK가 처리를 시작하기 전에 읽을 수 없게 될 수 있으므로(그 반대의 경우도 마찬가지) 위험합니다.

답변2

@StephenKitt와 @ilkkachu가 gawk 매뉴얼에서 이미 지적했듯이일부 코드가 포함되어 있습니다이렇게 하면 섹션에서 읽을 수 없는 파일이 제거되지만 ARGV[]테스트 BEGIN파일 과 실제로 해당 내용을 읽으려고 시도하는 awk 사이에 경쟁 조건이 있습니다. 이전 파일이 큰 경우 훨씬 늦어질 수 있습니다.

gawk 매뉴얼의 스크립트나 gawk가 있는 경우 gawk 매뉴얼 스크립트가 더 깔끔하고 짧고 간단하며 효율적이기 때문에 실제로 경쟁 조건 문제가 있을 수 있다고 생각하지 않는 한 @StephenKitt의 답변에 있는 스크립트를 사용하겠습니다. . 아래 것보다 낫습니다. 임시 파일과 전역 변수가 필요하지 않지만 경쟁 조건이 걱정되는 사람들을 위한 것입니다. 이것은 모든 awk에서 작동하고 시도하기 전에 임시 파일을 생성하는 데 의존하는 더 복잡한 스크립트입니다. 실제 파일 열기 즉시 다가오는 실제 파일을 읽을 수 있는지 여부를 테스트하십시오.

$ cat skip.awk
function addTmp(        cmd, oArgv, i, j) {
    cmd = "mktemp"
    cmd | getline TmpChkFile
    close(cmd)

    if ( TmpChkFile != "" ) {
        print "" > TmpChkFile
        close(TmpChkFile)

        for (i in ARGV) {
            oArgv[i] = ARGV[i]
        }
        oArgc = ARGC

        ARGC = 1
        for (i = 1; i < oArgc; i++) {
            if ( ! (oArgv[i] ~ /^[a-zA-Z_][a-zA-Z0-9_]*=.*/ \
                    || oArgv[i] == "-" || oArgv[i] == "/dev/stdin") ) {
                # not assignment or standard input so a file name
                ARGV[ARGC] = TmpChkFile
                ArgFileNames[++j] = oArgv[i]
                ArgFileIndices[j] = ++ARGC
            }
            ARGV[ARGC++] = oArgv[i]
        }
    }
}

function rmvTmp() {
    system("rm -f \047" TmpChkFile "\047")
}

function chkTmp(        stderr, line) {
    if ( (FNR == 1) && (FILENAME == TmpChkFile) ) {
        ++TmpFileNr
        if ( (getline line < ArgFileNames[TmpFileNr]) < 0 ) {
            stderr = "cat>&2"
            printf "Warning: skipping unreadable file \"%s\"\n", ArgFileNames[TmpFileNr] | stderr
            close(stderr)
            delete ARGV[ArgFileIndices[TmpFileNr]]
        }
        close(ArgFileNames[TmpFileNr])
        next
    }
}

BEGIN { addTmp() }
END { rmvTmp() }
{ chkTmp() }

awk가 여러 -f매개변수를 지원하는 경우(예:POSIX) 또는 여러 스크립트를 동시에 실행하는 다른 방법(예: GNU awk has @include)을 사용하여 위의 내용을 실제 스크립트에 포함할 수 있습니다(그렇지 않으면 위의 내용을 동일한 파일에 복사/붙여넣기). 스크립트는 다음과 같습니다:

$ cat tst.awk
FNR == 1 { print FILENAME, $0 }

그리고 다음과 같은 파일:

$ ls file_{1..3}
ls: cannot access 'file_2': No such file or directory
file_1  file_3

그런 다음 POSIX awk(그리고 대부분의 (전부는 아니지만) 다른 것)를 사용하여 다음을 수행할 수 있습니다.

$ awk -f skip.awk -f tst.awk file_{1..3}
file_1 A
Warning: skipping unreadable file "file_2"
file_3 C

위의 대부분은 BEGIN첫 번째 입력 파일이 열리기 전에 한 번 호출하여 ARGV[]각 실제 입력 파일 앞에 읽을 수 있는 임시 파일이 있는지 확인한 다음 chkTmp()각 입력 줄에 대해 호출하는 방식으로 작동합니다. 단, 첫 번째인 경우에만 수행합니다. 임시 파일의 줄을 입력하고 열어 보십시오 ARGV[]. 그런 다음 END임시 파일을 삭제하십시오. 따라서 실제 오버헤드는 chkTmp()각 입력 라인에 대한 호출 및 테스트입니다.FNR==1

chkTmp()모든 Unix 시스템에 파일이 존재한다고 보장할 수 없기 때문에 기존 파일을 사용하는 대신 임시 파일을 생성하고 있습니다. 만약 존재한다고 하더라도, 파일을 읽어야 하는 추가 오버헤드를 피하기 위해 정확히 한 줄 길이가 되어야 합니다. 모든 awks가 이를 지원하는 것은 아니기 때문입니다 (또는 내부에서 nextfile대신 호출할 수 있습니다 ).nextchkTmp()

관련 정보