【问题标题】:Fields contain field separator as string: How to apply awk correctly in this case?字段包含字段分隔符作为字符串:在这种情况下如何正确应用 awk?
【发布时间】:2012-09-04 19:54:32
【问题描述】:

我有一个类似于这个 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 列。但是,据我所知,如果我不会先去掉字符串中的分号。

【问题讨论】:

    标签: awk


    【解决方案1】:

    要拆分文件,您可以这样做:

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

    始终采用最后一个字段来构造文件名。

    在您的示例中,由于NF &gt; 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
      }'
    

    【讨论】:

    • 哇,效果很好,甚至更多的是我的 2GB 真实样本(仍然让我兴奋,这几行如何在一分钟内完成如此惊人的事情......)。所以绝对+1!下一步是了解您的解决方案 ;-)
    【解决方案2】:

    您已经接近解决方案了。我会使用最后一个字段来避免双引号字段的问题。此外,无需关闭每个文件。它们将在 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
    

    【讨论】:

    • 感谢您的回答。不幸的是,我的最小示例太简单了:我要拆分的列不是最后一列。更糟糕的是,它位于可以包含字符串的列之间。我将相应地更新我的示例。
    猜你喜欢
    • 1970-01-01
    • 2011-02-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-06-30
    • 2012-01-05
    • 2014-10-14
    • 1970-01-01
    相关资源
    最近更新 更多