awk: レコード区切り文字として NUL を使用すると、フィールドでの正確な文字列一致が機能しない

awk: レコード区切り文字として NUL を使用すると、フィールドでの正確な文字列一致が機能しない

フィールドに改行が含まれるファイル(二重引用符で囲まれている)を与えられたので、レコード区切り文字として NUL を使用して、目的のレコードを選択しようとしました。このために、行末を NUL に置き換え、改行で分割されたフィールドを修正しました( を使用して実行sed)。ただし、その後、(GNU)の最初のフィールドawkと文字列の正確な一致が失敗します。興味深いことに、最初のフィールドでの文字列パターンの一致は失敗します。これは、が正しく適用されていると想定させますRS="\x00"

なぜ失敗するのでしょうか? パターン マッチが機能するのはなぜですか?

サンプルファイルinput.txt:

head1,head2,head3
a,b,c
b,no a in first field,c
a,"with quotes",c
a,"with ,",c
b,a,1
a,"with
 newline",c
b,1,a

awkNUL を導入する前に正確な文字列を使用してレコードを選択すると、次のようになります。

$awk 'BEGIN {FS=OFS=","} {if ($1=="a") print}' input.txt

結果:

a,b,c
a,"with quotes",c
a,"with ,",c
a,"with

NUL を導入し、「改行分割」を修正すると機能します ("with\n newline"エントリに注意してください)。

$sed -e 's/$/\x00/' -e 's/\(,"[^,"]*\)\x00/\1/' input.txt | cat -A

head1,head2,head3^@$
a,b,c^@$
b,no a in first field,c^@$
a,"with quotes",c^@$
a,"with ,",c^@$
b,a,1^@$
a,"with$
 newline",c^@$
b,1,a^@$

フィールド 1 の にパターン マッチを使用すると機能します ("a"他のフィールドでは失敗しますが、"head1"一致することに注意してください)。

$sed -e 's/$/\x00/' -e 's/\(,"[^,"]*\)\x00/\1/' input.txt |
awk 'BEGIN {RS=ORS="\x00" ; FS=OFS=","}
     { if ($1~"a") print}' |
cat -A

head1,head2,head3^@$
a,b,c^@$
a,"with quotes",c^@$
a,"with ,",c^@$
a,"with$
 newline",c^@

しかし"a":フィールド 1の完全一致が失敗します:

sed -e 's/$/\x00/' -e 's/\(,"[^,"]*\)\x00/\1/' input.txt |
awk 'BEGIN {RS=ORS="\x00" ; FS=OFS=","} { if ($1=="a") print}' 

##<no output>##

どこが間違っているのでしょうか? NUL を使用する前はなぜ が機能するのでしょうかRS?

答え1

sed コマンドは、改行 ( \n) を NUL ( \0) に変更するのではなく、NUL + 改行 ( \0\n) に変更します (図にcat -A示すように)。

RS を に設定して GNU awk を使用すると\0、後続のレコードの最初の文字 (およびその最初のフィールドの最初の文字) は になり\n、完全一致が失われます。

改行分割の修正では's/\(,"[^,"]*\)\x00/\1/'、これはまったく変更されません。newline",cレコードが前のレコードに追加されるだけです。


手っ取り早い「解決策」は、ではなく にRS設定することです。しかし、csv ファイルを awk で解析できるように編集するこの方法は信頼性が高くないため、もっと良い方法を見つける必要があります。\0\n\0

最後の例:

sed -e 's/$/\x00/' -e 's/\(,"[^,"]*\)\x00/\1/' input.txt |
gawk 'BEGIN {RS=ORS="\x00\n" ; FS=OFS=","} { if ($1=="a") print}' | cat -A
a,b,c^@$
a,"with quotes",c^@$
a,"with ,",c^@$
a,"with$
 newline",c^@$
sed -e 's/$/\x00/' -e 's/\(,"[^,"]*\)\x00/\1/' input.txt |
gawk 'BEGIN {RS="\x00\n" ; FS=OFS=","} { if ($1=="a") print}'
a,b,c
a,"with quotes",c
a,"with ,",c
a,"with
 newline",c

答え2

たとえば、MS-Excel からエクスポートされた場合、ファイルの途中に LF と CRLF 行末が含まれている可能性があります。その場合、gawk で必要なのは次のコードだけです。

awk 'BEGIN{RS=ORS="\r\n"; FPAT="[^,]*|(\"[^\"]*\")+"} $1=="a"' file

たとえば ( cat -vCR を^Ms として表示するには のみを使用します):

$ cat -v file
head1,head2,head3^M
a,b,c^M
b,no a in first field,c^M
a,"with quotes",c^M
a,"with ,",c^M
b,a,1^M
a,"with
 newline",c^M
b,1,a^M

$ awk 'BEGIN{RS=ORS="\r\n"; FPAT="[^,]*|(\"[^\"]*\")+"} $1=="a"' file | cat -v
a,b,c^M
a,"with quotes",c^M
a,"with ,",c^M
a,"with
 newline",c^M

上記の方法がうまくいかない場合は、https://stackoverflow.com/questions/45420535/whats-the-most-robust-way-to-efficiently-parse-csv-using-awkまたは、gawkextlib の gawks CSV パーサー拡張機能をダウンロード/使用します。

答え3

混合 sed awk アプローチ:

$ < file \
sed -e '
  s/$/\x00/
  s/\(,"[^,"]*\)\x00/\1/
  H;1h;$!d;g
  s/\x00\n/\x00/g
' |
awk '/^a,/' RS="\x00" -

コメント: sed+awk の混合コード 私はあなたのコードを採用し、希望する結果を得るために少し調整しました。主なアイデアは、sed が必ず挿入する改行を削除することです。そのため、各レコードを処理した後、sed が印刷するのを控えます。次に、eof で改行を削除し、この NUL で区切られたデータを、NUL をレコード区切り文字として使用して awk に渡します。次に、a で始まるレコードを検索します。

出力:

a,b,c
a,"with quotes",c
a,"with ,",c
a,"with
 newline",c

以下に、awk のみと sed のみのメソッドを示します。これらは、引用符で囲まれたフィールド内の引用符が二重化されることに依存します。

純粋なsedアプローチ:

$ sed -Ee ':a
    /^(([^"]*"){2})*[^"]*$/!{
      $d;N;ba
    }
    /^a,/!d
' file

純粋なawkアプローチ

$ awk -F\" '
   !(NF%2){
      t = $0; n = NF
      while (getline a > 0) {
        t = t ORS a
        n = n + split(a, _x, FS)
        if (!(nf%2)) break 
      }
      $0 = t
   }/^a,/
' file

関連情報