
給定一個在欄位中包含換行符的檔案(由雙引號嵌入),我嘗試使用 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 並修正“newline-splits”有效(注意"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^@$
對 in 欄位 1 使用模式比對是有效的(請注意"a"
in 其他欄位如何失敗,但"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^@
然而:字段 1 中的精確匹配"a"
失敗:
sed -e 's/$/\x00/' -e 's/\(,"[^,"]*\)\x00/\1/' input.txt |
awk 'BEGIN {RS=ORS="\x00" ; FS=OFS=","} { if ($1=="a") print}'
##<no output>##
我哪裡錯了?為什麼在使用 NUL as 之前就可以工作RS
?
答案1
您的 sed 指令不會將換行符號 ( \n
) 變更為 NUL ( \0
) ,而是變更為 NUL + 換行符號 ( \0\n
) (如圖cat -A
所示)。
當使用 GNU awk 並將 RS 設為 時\0
,後續記錄(及其第一個欄位)的第一個字元將為\n
,這將破壞您的精確匹配。
換行符's/\(,"[^,"]*\)\x00/\1/'
分割修正根本不會改變這一點——它只是將newline",c
記錄附加到前一個記錄。
一個快速而骯髒的“解決方案”是設置RS
為\0\n
而不是僅僅設置\0
。但是這種處理 csv 檔案以便 awk 解析它們的方法並不可靠,所以你真的應該找到更好的東西。
用你的最後一個例子:
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
您的檔案可能包含帶有 CRLF 行結尾的 LF 中場,例如,如果它是從 MS-Excel 匯出的。在這種情況下,你所需要的 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 分隔的資料傳遞給 awk,並使用 NUL 作為記錄分隔符號。然後我們只需找出以 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