【问题标题】:Split CSV by column value, and keep header按列值拆分 CSV,并保留标题
【发布时间】:2018-07-27 16:21:48
【问题描述】:

这已经被问过很多次了,但我根本无法正确实施解决方案。我有一个名为 2017-01.csv 的大型 csv,带有一个日期列(它是文件中的第二列),我正在按日期拆分文件。原始文件如下所示:

 date
 2017-01-01
 2017-01-01
 2017-01-01
 2017-01-02
 2017-01-02
 2017-01-02

拆分后,2017-01-01.csv 的样子

2017-01-01
2017-01-01
2017-01-01

而 2017-01-02.csv 看起来像

2017-01-02
2017-01-02
2017-01-02

我使用的代码是

awk -F ',' '{print > (""$2".csv")}' 2017.csv

一切正常,但我需要保留标题行。所以我尝试了

awk -F ',' 'NR==1; NR > 1{print > (""$2".csv")}' 2017-01.csv

但是没有标题行我仍然得到相同的结果。我究竟做错了什么?我在 Stackoverflow 上阅读了许多类似问题的答案,但我无法理解他们在做什么。

我想要这个:

2017-01-01.csv 应该是这样的

date
2017-01-01
2017-01-01
2017-01-01

2017-01-02.csv 应该是这样的

date
2017-01-02
2017-01-02
2017-01-02

【问题讨论】:

  • 您的输入和输出文件名看起来一样吗?是错字还是正确,请确认?
  • 我已经再次编辑它以使其清楚。输入和输出文件不同。让我知道现在是否有意义。谢谢。
  • 请检查我的回答,如果对您有帮助,请告诉我?
  • 脚本中的"" 什么都不做,您可以将其删除。 edit您的问题是提供更真实地代表您的真实多列数据的示例输入/输出,以便我们为您提供帮助。

标签: csv awk


【解决方案1】:
awk -F, '
FNR==1{hdr=$2}
 FNR > 1{
   if (! hdrPrinted[$2]){
      print hdr > (""$2".csv")
      hdrPrinted[$2]=$2
  }
  print $1, $2, $3> (""$2".csv")
}' 2017-01.csv

作为 1-liner

awk -F, ' FNR==1{hdr=$2} FNR > 1{ if (! hdrPrinted[$2]){ print hdr > (""$2".csv"); hdrPrinted[$2]=$2; } print $1, $2, $3> (""$2".csv") }' 2017-01.csv

产生输出

cat 2017\-01\-01.csv
date
  2017-01-01
  2017-01-01
  2017-01-01

cat 2017\-01\-02.csv
date
  2017-01-02
  2017-01-02
  2017-01-02

注意FNR的意思是FileNumber(of)Record,所以每次打开一个新文件,FNR都会重置为1。这对于具体的处理情况可能会出现问题,但一般来说,我觉得这样比较好方法,允许您在 cmd 行上列出多个文件,并在一个进程中处理它们。

-----------------

根据以下合理的 cmets,这里有更多的防弹版本,如果 cmd 行上列出的文件超过 20 个,则应该处理这种情况。

我没有一个简单的方法来测试这个,所以欢迎反馈。

AND 下面的每个 cmets,它仍然需要一些工作,我现在没有时间。寻找周六下午的更新。

awk -F, ' FNR==1{hdr=$2}  FNR > 1{
      # length() assumes newish gawk version
      if ( length(openFiles) > 20) {
             # close the first/next file in the array
             close(openFiles[++j]".csv")
             openFiles[j]=""
      }
      if (! ($2 in openFiles) ) {
             # put the filename into the openFiles array (just once)
            openFiles[++i]=$2
            }    if (! hdrPrinted[$2]){
   print hdr > (""$2".csv")
  hdrPrinted[$2]=$2   }   print $1, $2, $3> (""$2".csv") 2017-01.csv

IHTH

由 Ed Morton 编辑:

awk -F, '
FNR==1 { hdr=$0; next}
{
    out = $2 ".csv"
    if (!seen[out]++) {
        print hdr > out
    }
    print >> out
    close(out)
}
' file

【讨论】:

  • 谢谢。但是我得到 hdrPrinted[$2]=$2 中的“=”的语法错误。
  • 糟糕,在没有 dbl 检查它真的工作的情况下进行了就地更新;-( 。这里已经修改。日期值是缩进的,因为字段 $1 包含在输出,正如您所指出的,您需要在实际问题中包含其他字段。祝您好运。
  • 因此,您仍然需要处理分配给hdr 的内容以及要输出的字段(以及以什么顺序)。 printf("%s\t\%s\n, $1, $2) 之类的语句将为您的输出提供更大的灵活性。
  • 这是基本的 cmd,应该可以工作。请发送uname -svr ; awk --version的输出。
  • 好的,您的代码有效,但前提是我将其保存为 awk 文件并以 sh file.awk 运行。起初,我试图将你所有的代码放在 1 行并直接在 bash 上运行,但没有成功。知道为什么吗?如何将其更改为可以直接复制/粘贴到 bash 中的单行符?
【解决方案2】:

以下内容在包含多列的 csv 上进行测试,其中第二列设置为日期:

awk -F',' 'prev!=$2{close(prev".csv");print "date" > ($2".csv")}{print $2 > ($2".csv");prev=$2}' Input_file

【讨论】:

  • 您的代码有效,但与 Ravinder 的回答存在相同的问题。除了“日期”之外,我还有多个列。所以我需要我的标题不仅仅是“日期”。你能告诉我如何打印 NR==1{header=$0; next} 在每个 csv 开始之前?
  • 当然很明显,如果您的真实输入文件的列多于日期,那么您提供给我们用来测试潜在解决方案的示例输入也应该具有比日期更多的列。
  • 它应该是一个工作示例,而不是从我的原始数据集中复制/粘贴。我确实提到我有超过 1 列。所以不,这对我来说并不明显。感谢您的帮助。
猜你喜欢
  • 2019-11-24
  • 2017-12-24
  • 1970-01-01
  • 2016-09-20
  • 1970-01-01
  • 2014-04-01
  • 1970-01-01
  • 2018-12-27
相关资源
最近更新 更多