2012-09-04 63 views
1

我有一個CSV文件類似於這樣test.csv文件:字段包含字段分隔符作爲字符串:如何在這種情況下正確應用awk?

Header 1; Header 2; Header 3 
A;B;US 
C;D;US 
E;F;US 
G;H;FR 
I;J;FR 
K;L;FR 
M;"String with ; semicolon";UK 
N;"String without semicolon";UK 
O;"String OK"; 
P;"String OK"; 

現在,我想基於頭3.所以我想有四個單獨的CSV文件,一個用於落得拆分此文件「美國」,「FR」,「英國」和「」。

用我有限的Linux命令行技能(可惜我:-(使用,直至現在這一行:

awk -F\; 'NR>1{ fname="country_yearly_"$3".csv"; print >>(fname); close(fname);}' test.csv 

當然,你有經驗的命令行用戶會發現我的問題:在一個領域我test.csv包含一些行,其中用作分隔符的分號也出現在標有引號的字段中(我無法保證肯定因爲有數百萬行,但我很滿意答案假定這一點)。所以,可悲的是,我得到一個名爲country_yearly_分號」的.csv一個額外的文件,其中包含在我的例子此行。

在我的冒險解決這個問題,我在SO遇到這個問題。特別是,Thor的答案似乎通過替換字符串中的所有分號來解決我的問題。我相應調整自己的代碼如下:

awk -F'"' -v OFS='' ' 
    NF > 1 { 
    for(i=2; i<=NF; i+=2) { 
     gsub(";", "|", $i); 
     $i = FS $i FS;  # reinsert the quotes 
    } 
    print 
    }' test.csv > test1.csv 

現在,我得到以下test1.csv文件:

M;"String with | semicolon";UK 
N;"String without semicolon";UK 
O;"String OK"; 
P;"String OK"; 

正如你所看到的,有引號的所有行顯示,我的問題行也是固定的,但是a)我實際上需要所有行,不僅僅是引號中的那些行,而且我也不知道他的代碼中的哪一部分將行限制爲帶引號的行,以及b)我認爲它會更多如果test.csv只是改變而不是發送輸出到一個新文件,但我不知道該怎麼做。

編輯迴應Birei的回答是:

不幸的是,我的小例子,太簡單了。下面是一個更新版本:

Header 1; Header 2; Header 3; Header 4 
A;B;US; 
C;D;US; 
E;F;US; 
G;H;FR; 
I;J;FR; 
K;L;FR; 
M;"String with ; semicolon";UK;"Yet another ; string" 
N;"String without semicolon";UK; "No problem here" 
O;"String OK";;"Fine" 
P;"String OK";;"Not ; fine" 

需要注意的是我的真實數據有大約100列,數百萬行和國家列,忽略字符串分號,被列13。但是,據我看到它,我可以如果我沒有首先刪除字符串中的分號,就不會使用第13列這一事實。

回答

4

要分割的文件,你可能只是做:

awk -v FS=";" '{ CSV_FILE = "country_yearly_" $NF ".csv" ; print > CSV_FILE }' 

總是走在最後一個字段構建文件名。

在您的示例中,由於NF > 1模式,只會打印帶有引號的行。下面的腳本將打印所有行:

awk -F'"' -v OFS='' ' 
    NF > 1 { 
    for(i=2; i<=NF; i+=2) { 
     gsub(";", "|", $i); 
     $i = FS $i FS;  # reinsert the quotes 
    } 
    } 
    { 
    # print all lines 
    print 
    }' test.csv > test1.csv 

做你想做什麼,你可以改變行腳本並重新處理:

awk -F'"' -v OFS='' ' 
    # Save the original line 
    { ORIGINAL_LINE = LINE = $0 } 
    # Replace the semicolon inside quotes by a dummy character 
    # and put the resulting line in the LINE variable 
    NF > 1 { 
    LINE = "" 
    for(i=2; i<=NF; i+=2) { 
     gsub(";", "|", $i) 
     LINE = LINE $(i-1) FS $i FS  # reinsert the quotes 
    } 
    # Add the end of the line after the last quote 
    if ($(i+1)) { LINE = LINE $(i+1) } 
    } 
    { 
    # Put the semicolon-separated fields in a table 
    # (the semicolon inside quotes have been removed from LINE) 
    split(LINE, TABLE, /;/) 
    # Build the file name -- TABLE[ 3 ] is the 3rd field 
    CSV_FILE = "country_yearly_" TABLE[ 3 ] ".csv" 
    # Save the line 
    print ORIGINAL_LINE > CSV_FILE 
    }' 
+0

哇,完美的工作,甚至更多我的2GB真實樣本(仍然讓我感到興奮的是,這幾條線在一分鐘內如何做出如此驚人的事情......)。所以絕對+1!下一步是瞭解您的解決方案;-) –

1

您已接近解決方案。我會用最後一個字段來避免帶雙引號的字段的問題。另外,不需要關閉每個文件。它們將在awk腳本末尾被外殼自動關閉。

awk ' 
    BEGIN { 
     FS = OFS = ";"; 
    } 
    FNR > 1 { 
     fname = "country_yearly_" $NF ".csv"; 
     print >>fname; 
    } 
' infile 

檢查輸出:

head country_yearly_* 

國債收益率:

==> country_yearly_.csv <== 
O;"String OK"; 
P;"String OK"; 

==> country_yearly_FR.csv <== 
G;H;FR 
I;J;FR 
K;L;FR 

==> country_yearly_UK.csv <== 
M;"String with ; semicolon";UK 
N;"String without semicolon";UK 

==> country_yearly_US.csv <== 
A;B;US 
C;D;US 
E;F;US 
+0

感謝您的回答。不幸的是,儘管我讓我的最小例子太簡單了:我想分割的列不是最後一列。更糟糕的是,它位於可以包含字符串的列之間。我會相應地更新我的示例。 –