1

我有一个类似于此 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 中的一个字段包含的行中,我用作分隔符的分号也出现在用引号标记的字段中(我不能保证肯定是因为有数百万行,但我对假设这一点的答案感到满意)。很遗憾,我得到了一个名为 country_yearly_ semicolon".csv 的附加文件,它在我的示例中包含这一行。

在我尝试解决这个问题的过程中,我在SO上遇到了这个问题。特别是,托尔的回答似乎包含了我的问题的解决方案,它通过替换字符串中的所有分号。我相应地调整了他的代码如下:

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

2 回答 2

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
  }'
于 2012-09-04T11:09:19.067 回答
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
于 2012-09-04T10:58:33.853 回答