
フィールドに改行が含まれるファイル(二重引用符で囲まれている)を与えられたので、レコード区切り文字として 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
awk
NUL を導入する前に正確な文字列を使用してレコードを選択すると、次のようになります。
$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 -v
CR を^M
s として表示するには のみを使用します):
$ 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