AWK:外部ユーティリティに渡された引数がフィールド分割されるのを防ぎます。

AWK:外部ユーティリティに渡された引数がフィールド分割されるのを防ぎます。

AWKスクリプト内では、変数を外部ユーティリティに引数として渡すことができます。

awk 'BEGIN {
    filename = "path_to_file_without_space"
    "file " filename | getline
    print $0
}'

ただし、変数にスペースが含まれている場合

awk 'BEGIN {
    filename = "path to file with spaces"
    "file " filename | getline
    print $0
}'

エラーが発生しました。

file: cannot open `path' (No such file or directory)

シェルが空白で引用しない変数を分割する方法と同様に、空白から引数を分割することをお勧めします。次のように、シェルのIFSをnullに設定してシェルフィールド分割を無効にしたいと思います。

"IFS= file " filename | getline

あるいは、AWK コマンドを実行する前に IFS を null に設定しましたが、両方のオプションに違いはありません。このフィールド分割を避ける方法は?

答え1

ファイル名を引用する必要があります。

awk 'BEGIN {
    filename = "path to file with spaces"
    "file \"" filename "\"" | getline
    print
}'

またはコメントで提案されているように読みやすくするため

awk 'BEGIN {
    DQ = "\042" # double quote (ASCII octal 42)
    filename = "path to file with spaces"
    "file " DQ filename DQ | getline
    print
}'

またはこれがawkより大きなプログラムの一部であると仮定すると、

BEGIN {
    SQ = "\047"
    DQ = "\042"
}

BEGIN {
    name = "filename with spaces"
    cmd = sprintf("file %s%s%s", DQ, name, DQ)

    cmd | getline
    close(cmd)

    print
}

つまり、オープンファイルハンドルの保存が完了したら、コマンドを閉じます。別のブロックに便利な「定数」を設定しますBEGIN(これらのブロックは順次実行されます)。別の変数を使用してコマンドを作成しますsprintf。 (この内容のほとんどは、明らかにメンテナンスawkのために読みやすい構造を提供する必要があるより長いまたは複雑なプログラムを対象としています。dquote()文字列を参照して関数を書くことを想像することもできます。)squote()

「パイプ」の左側はリテラル文字列として評価されます。

file "path to file with spaces"

デフォルトでは、usingは文字列である単一のパラメータを使用cmd | getlineしてawk呼び出します。したがって、実行を使用するには文字列を正しく引用する必要があります。sh -ccmdsh -c

技術的な詳細については、以下を参照してください。POSIX規格:

expression | getline [var]

コマンド出力がパイプされるストリームから入力レコードを読み取ります。ストリームが現在開いていない場合は、expressionコマンド名として値を使用して生成する必要があります。生成されたストリームは、popen()式の値をコマンド引数に、値を引数としてr関数を呼び出して生成されたストリームと同じでなければなりませんmode。ストリームが開いている限り、expression同じ文字列値で評価される後続の呼び出しは、ストリームから後続のレコードを読み取る必要があります。close同じ文字列値で評価される式を使用して関数が呼び出されるまで、ストリームは開いたままにする必要があります。そのとき、この関数を呼び出したかのようにストリームが閉じられますpclose()var省略すると設定され、そうでなければ設定さ$0れ、該当する場合は数値文字列として扱われます(awkの式を参照)。NFvar

popen()ここで言及される関数はCライブラリpopen()関数である。これは実行のために与えられた文字列を予約しますsh -c

system()スペースを含むファイル名でコマンドを実行すると、まったく同じ問題が発生しますが、この場合はsystem()Cライブラリの関数が呼び出されます。返品呼び出しsh -c方法は似ていますpopen()(ただし、I/O ストリームのパイプラインは異なります)。

したがって、単一の引数で呼び出すと設定はIFS役に立ちません。sh -c

file path to file with spaces

答え2

ファイル名にかかわらず、スペースは最も心配することはありません。たとえば、$(reboot)またはfoo;reboot #whateverまたは...というfoo|reboot|barファイルを考えてみましょう。

awkコマンドラインを解釈するために呼び出されるので、任意のsh入力からコマンドラインを作成するときにコマンド注入の脆弱性を防ぐために、パラメータを適切にエスケープすることが重要です。cmdline | getlineprint | cmdlinesystem(cmdline)

シェルから引用するのは難しいことです。シェルにはさまざまな引用演算子('...'、、、、、、)がありますが、"..."エスケープされないため、安全で\はない可能性があります。$'...'$"..."'...'すべて特に、\対応するエンコーディングは、一部の文字セットの他の文字エンコーディングにも存在するため、危険な文字をエスケープしません。

また、シェルコードで以前の形式のコマンド置換を使用しないことも重要です`...`。これは、異なるレベルのバックスラッシュ処理を導入するためです。

環境変数に任意のファイル名があるとします。

#! /bin/sh -
FILE="${1?No file provided}"
export FILE

awk -v q="'" '
  function shquote(s) {
    gsub(q, "&\"&\"&", s)
    return q s q
  }
  BEGIN {
    cmdline = "file -- " shquote(ENVIRON["FILE"])
    if ((cmdline | getline) > 0)
      print "The first line of \""cmdline"\" output was \""$0"\"."
    else
      print "Could not read a line from \""cmdline"\" output."
    if (close(cmdline) != 0)
      print cmdline" failed."
  }'

上記では、shquote()文字列を引数として使用し、sh一重引用符(最も安全な引用符の種類)で囲んで引用しますが、文字列自体の一重引用符はに変わります'"'"'。つまり、 end '、そのあとに引用符'、 別の再開された引用符"..."が続きます。'別の単一引用符で囲まれた文字列の場合。

上記では、他の可能な警告を確認できます。

  • --ファイル名で終わるかどうかを確認するには、これが必要です-
  • このコマンドの出力は、file特にファイル名自体に改行文字が含まれている場合、1行に出力されるという保証はありません。最後に、改行文字はファイル名のすべての文字と同じくらい有効です。getline1つのレコードのみが読み取られ、基本レコードは行です。バラよりawkのフルルックモード?出力全体を読み取る方法のヒント。
  • 出力に行がまったく含まれていない可能性があります。空の最初の行でこれを表示するには、の戻り値を確認する必要がありますgetline
  • コマンドの終了ステータスを確認し、必要に応じて問題を報告することをお勧めします。これは返された値を確認することによって行われますclose()。ただし、awkこの値が終了状態をエンコードする方法によって実装が異なります。唯一の共通点は、コマンドが成功した場合(0終了コードで終了)、値が0であることです。

関連情報