awk(およびgawk):N個の入力ファイルの1つを読み取れないときに致命的なエラーを防ぐ方法

awk(およびgawk):N個の入力ファイルの1つを読み取れないときに致命的なエラーを防ぐ方法

テストケース:(rootは000権限を無視するので、root以外のユーザーを使用してください...)

#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システムにファイルが存在することを保証できないため、既存のファイルを使用する代わりに一時ファイルを作成しています。存在する場合でも、ファイルを読み取る必要がある追加のオーバーヘッドを避けるために、正確に1行の長さにする必要があります。すべてのawksがそれをサポートしているわけではないからです(または内部でnextfile代わりに呼び出すことができます)。nextchkTmp()

関連情報