字段包含字段分隔符作为字符串:如何在这种情况下正确应用awk?

时间:2012-09-04 10:40:10

标签: awk

我有一个与此test.csv文件类似的CSV文件:

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文件,一个用于“US”,“FR”,“UK”和“”。

凭借我非常有限的Linux命令行技能(遗憾地:-(我直到现在才使用这一行:

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

当然,您经验丰富的命令行用户会注意到我的问题:我的test.csv中的一个字段包含行,其中我用作分隔符的分号也出现在带引号的字段中(I因为有数百万行,所以无法保证这一点,但我很满意这个假设的答案。很遗憾,我得到一个名为country_yearly_ semicolon“.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列的事实我首先没有摆脱字符串中的分号。

2 个答案:

答案 0 :(得分: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
  }'

答案 1 :(得分:1)

你接近解决方案。我会使用最后一个字段来避免双引号字段的问题。此外,无需关闭每个文件。它们将在awk脚本末尾的shell中自动关闭。

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
相关问题