【问题标题】:Split unevenly a CSV file in multiple files in bash scripting在 bash 脚本中将 CSV 文件不均匀地拆分为多个文件
【发布时间】:2021-09-22 13:49:34
【问题描述】:

我有一个包含几个大 csv 文件的文件夹,并且我希望有一个可变数量的几乎相同大小的 CSV 文件。

目前这是我的均匀分区实现:

#!/bin/bash

#copy header to all resulting files parts
head -n 1 $1_2021.csv | awk -v NPROC=$(nproc) '{ for (i = 0; i < NPROC; ++i) print $0 > "file_"i".csv" }'

#copy the data but the header for each file part
tail --silent -n+2 $1* | awk -v NPROC=$(nproc) '{ part = NR % NPROC; print $0 >> "file_"part".csv" }'

其中$1 是文件的版本,作为参数传递给bash 脚本,例如v1v2。 输出文件名不相关,目前file_"i".csv & file_"part".csv 产生相同的文件名,其中part & i 位于此范围内:(0, NPROC)

文件v1_2020.csv的一些示例(分号分隔)

DATE;COLOUR;CLOSING;CHANGE;Y  
2020-01-02;r;n;4;119  
2020-01-02;y;n;56;130  
2020-01-03;y;n;3;153  
2020-01-03;r;n;46;192  
2020-01-03;b;n;20;241  
2020-01-04;w;n;1252;252  
2020-01-05;w;n;453;253  
2020-01-06;b;y;1;279  
2020-01-06;b;n;945;294  

表格看起来像这样:

DATE COLOUR CLOSING CHANGE
2020-01-02 r n 4
2020-01-02 y n 56
2020-01-03 y n 3
2020-01-03 r n 46
2020-01-03 b n 20
2020-01-03 w n 1252
2020-01-05 w n 453
2020-01-06 b y 1
2020-01-06 b n 945

我想改进这种划分方式,使其不会在相同的日期分成不同的文件。所以它应该考虑到 CSV 文件中的DATE 列。

电流输出与NPROC=2:

file_1.csv

DATE;COLOUR;CLOSING;CHANGE;Y  
2020-01-02;r;n;4;119  
2020-01-03;y;n;3;153  
2020-01-03;b;n;20;241  
2020-01-05;w;n;453;253  
2020-01-06;b;n;945;294

file_2.csv

DATE;COLOUR;CLOSING;CHANGE;Y  
2020-01-02;y;n;56;130  
2020-01-03;r;n;46;192  
2020-01-04;w;n;1252;252  
2020-01-06;b;y;1;279 

带有NPROC=2的新输出:

任何类型的不均匀拆分为NPROC 的文件数量,这样它就不会将日期混入不同的文件中。 一个日期应该只在一个文件中,但一个文件应该包含多个日期。

例如,如果符合上述条件,任何其他类型的文件拆分为NPROC 数量的文件都可以:

file_1.csv

DATE;COLOUR;CLOSING;CHANGE;Y  
2020-01-02;r;n;4;119  
2020-01-02;y;n;56;130  
2020-01-03;y;n;3;153  
2020-01-03;r;n;46;192  
2020-01-03;b;n;20;241  

file_2.csv

DATE;COLOUR;CLOSING;CHANGE;Y  
2020-01-04;w;n;1252;252  
2020-01-05;w;n;453;253  
2020-01-06;b;y;1;279  
2020-01-06;b;n;945;294

您能否给我任何关于不使用 Python 而只使用 bash 脚本的可能解决方案的提示?

【问题讨论】:

  • 它们是分号格式的@TedLyngmo
  • edit 您的问题是将这些图形表替换为您生成它们的原始 CSV,以便我们可以复制/粘贴这些文件来测试潜在的解决方案,以便我们为您提供帮助。跨度>

标签: bash csv awk


【解决方案1】:

如果您只想拆分一个 csv 并为每个拆分添加一个标题,您可以这样做:

awk -v cnt=6 -F ';' 'FNR==1{header=$0; fn=1}
!(FNR%cnt){
    fn++
    print header >"file_" fn ".csv"
}
{print $0>"file_" fn ".csv"}' file

如果您想根据日期列进行上下文拆分(假设已经排序):

awk -v sp=6 -v fn=1 -F ';' 'FNR==1{header=$0}
cnt++>sp && l1!=$1 {
    fn++
    cnt=0
    print header >"file_" fn ".csv"
}
{print $0>"file_" fn ".csv"; l1=$1}' file

第二个结果:

cat *.csv
DATE;COLOUR;CLOSING;CHANGE
2020-01-02;r;n;4
2020-01-02;y;n;56
2020-01-03;y;n;3
2020-01-03;r;n;46
2020-01-03;b;n;20
2020-01-03;w;n;1252
DATE;COLOUR;CLOSING;CHANGE
2020-01-05;w;n;453
2020-01-06;b;y;1
2020-01-06;b;n;945

【讨论】:

  • 除非我看错了,否则那是行不通的。首先,它会在每个文件的开头将fn 重置为 1,从而反复覆盖同一个文件。而且,print &gt; 将用每一行覆盖整个文件......应该是&gt;&gt;
  • 我想以这样的方式拆分它:1)一个日期只包含在一个文件部分中 2)一个文件部分应包含多个日期。这是因为我需要提供一致性以便之后并行处理这些文件。
  • @Guido:在您的问题中显示您想要输出的内容会很有帮助。
【解决方案2】:
awk -F';' -v NPROC=2 '
    NR == 1 {head = $0; next}
    !($1 in dates) {
        n = (n + 1) % NPROC
        file = "out_" n ".csv"
        if (!(file in created)) {
            print head > file
            created[file]
        }
        dates[$1] = file
    }
    { print > dates[$1] }
' v1_2020.csv

由于 NPROC = 2,创建了两个输出文件:

$ cat out_0.csv
DATE;COLOUR;CLOSING;CHANGE;Y
2020-01-03;y;n;3;153
2020-01-03;r;n;46;192
2020-01-03;b;n;20;241
2020-01-05;w;n;453;253

$ cat out_1.csv
DATE;COLOUR;CLOSING;CHANGE;Y
2020-01-02;r;n;4;119
2020-01-02;y;n;56;130
2020-01-04;w;n;1252;252
2020-01-06;b;y;1;279
2020-01-06;b;n;945;294

【讨论】:

    【解决方案3】:

    首先,使用命令行工具处理 CSV/TSV 文件可能很棘手。 awk 命令是这里的首选,但它没有对引用的内置支持;如果您有像column 1; "column 2 has a ';' in it";column 3 这样的行,那么awk -F';' 将看到它为$1="column 1"$2="\"column to has a '"$3="'in it\""$4="column3"

    如果您的数据没有类似的内容,那么这很简单。首先,您要将每个日期写入自己的文件:

     awk -F';'  '{print >>$1".csv"}'
    

    这将为您提供以日期命名的文件,例如 2020-01-02.csv

    现在您可以将它们合并到 NPROC 文件中,只要您只合并整个文件,就不会将给定日期的数据拆分为多个文件。这是一种简单(不一定优雅!)的方法:

    declare -i lines=$(cat *-*-*.csv | wc -l) chunk cur
    (( chunk = lines / NPROC, cur = 1 ))
    for f in *-*-*.csv; do
      cat "$f" >>"file_$cur.csv"
      if (( $(wc -l <"file_$cur.csv") >= chunk )); then
         (( cur += 1 ))
      fi
    done
    

    【讨论】:

    • 感谢您的输入马克,是的,我不应该遇到您提到的有关列名的问题。关于您提出的解决方案,我写道一个日期应该只在一个文件中,但我的意思是一个文件可能包含多个日期,因为我要输出的最终文件的数量由NPROC 给出。我相信您的解决方案无论如何都适合我的用例,因为这可能是第一步,然后作为第二步,我可以将您输入创建的那些文件(第一步)合并到NPROC 文件中。你觉得这听起来对吗?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-05-20
    • 1970-01-01
    • 2014-06-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多