【问题标题】:Sort and split CSV file using sed or awk使用 sed 或 awk 对 CSV 文件进行排序和拆分
【发布时间】:2019-05-12 01:55:16
【问题描述】:

我有一个如下所示的 CSV 文件 (test.csv):

WH_01,TRAINAMS,A10,1221-ESD
WH_03,TRAINLON,L10A3,3005-21
WH_01,TRAINAMS,A101,PWR-120
WH_02,TRAINCLE,A1,074-HD-SATA
WH_01,TRAINAMS,A10,PWR-120
WH_02,TRAINCLE,A15,102-55665
WH_03,TRAINLON,L10A3,3005-20
WH_03,TRAINLON,UK-B3,101859

1)。我可以根据第 2 列中的值对文件进行排序,如下所示:

sort -t, -k2,2 test.csv > testsort.csv

2)。接下来我想根据第 2 列中的值拆分文件。使用上面的示例,它应该创建 3 个文件:

testsort_1.csv:
WH_01,TRAINAMS,A10,1221-ESD
WH_01,TRAINAMS,A101,PWR-120
WH_01,TRAINAMS,A10,PWR-120

testsort_2.csv:
WH_02,TRAINCLE,A1,074-HD-SATA
WH_02,TRAINCLE,A15,102-55665

testsort_3.csv:
WH_03,TRAINLON,L10A3,3005-20
WH_03,TRAINLON,L10A3,3005-21
WH_03,TRAINLON,UK-B3,101859

我该怎么做?不确定是否甚至需要排序以及是否可以在不排序的情况下实现上述目标。

谢谢。

【问题讨论】:

  • 你觉得grep怎么样?
  • 您是要指定地图(例如TRAINAMS -> testsort_1.cv),还是希望脚本按字母顺序计算?
  • 查看 awk 中的 RS 变量,以及这个问题中的一些想法:stackoverflow.com/questions/8061475/…
  • 我不介意使用 grep。谢谢
  • @Beta,我想使用原始文件名(test.csv 或 testsort.csv)并为结果文件名使用顺序计数器。第 2 列可以包含任意数量的不同值(而不仅仅是我在示例中使用的 3 个)。谢谢

标签: bash awk sed


【解决方案1】:

分离sortawk的好方法。

$ sort -t, -k2,2 test.csv |awk -F, '!($2 in T) {T[$2]=++i} {print > ("testsort_" i ".csv")}'

$ tail -n +1 testsort*
==> testsort_1.csv <==
WH_01,TRAINAMS,A10,1221-ESD
WH_01,TRAINAMS,A101,PWR-120
WH_01,TRAINAMS,A10,PWR-120

==> testsort_2.csv <==
WH_02,TRAINCLE,A1,074-HD-SATA
WH_02,TRAINCLE,A15,102-55665

==> testsort_3.csv <==
WH_03,TRAINLON,L10A3,3005-20
WH_03,TRAINLON,L10A3,3005-21
WH_03,TRAINLON,UK-B3,101859
  • !($2 in T) - 如果在数组T 的索引中找不到第二个字段,
  • {T[$2]=++i} - 增加计数器并将第二个字段保存为索引。
  • {print} - 打印每一行
  • &gt; "file" - 覆盖、重定向和附加输出到文件
  • ("." i ".") - 连接“字符串”和变量

【讨论】:

  • 如果文件已排序,那么您不需要将所有 $2 值存储在一个数组中并将它们映射到它们的输出文件号 !($2 in T) {T[$2]=++i} 您只需要 $2!=p{++i;p=$2}
  • 感谢tail -n +1 testsort* 打印文件名和内容的想法顺便说一句 - 比我一直使用的带有回声和猫的 shell 循环好多了!
  • 看起来很酷,但如果实际输入在第二列有一百万个不同的值怎么办?在这种情况下,您会收到 打开的文件过多 错误。而且我没有得到在T中使用输出文件计数散列第二列的意义@
【解决方案2】:

由于您不确定是否需要排序,这几乎可以肯定意味着您不需要,并且您只是认为出于某种原因它会很有用,而且您只是在 $2 上进行排序,然后根据2 美元的价值,所以排序没有任何好处。

你真正需要做的就是:

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

看:

$ ls
test.csv

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

$ ls
test.csv  TRAINAMS.csv  TRAINCLE.csv  TRAINLON.csv

$ tail -n +1 TRAIN*
==> TRAINAMS.csv <==
WH_01,TRAINAMS,A10,1221-ESD
WH_01,TRAINAMS,A101,PWR-120
WH_01,TRAINAMS,A10,PWR-120

==> TRAINCLE.csv <==
WH_02,TRAINCLE,A1,074-HD-SATA
WH_02,TRAINCLE,A15,102-55665

==> TRAINLON.csv <==
WH_03,TRAINLON,L10A3,3005-21
WH_03,TRAINLON,L10A3,3005-20
WH_03,TRAINLON,UK-B3,101859

如果您超过了大约 20 个输出文件名并且没有使用 GNU awk,那么您必须在 $2 更改时关闭()每个文件名,并使用 &gt;&gt; 而不是 &gt; 附加到它们。

如果由于某种原因您确实需要使用问题中的输出文件名,那就是:

awk -F, '!($2 in map){map[$2]="testsort_"++cnt".csv"} {print > map[$2]}' test.csv

【讨论】:

  • 这几乎不公平,但你是对的。如果您将基于字段的记录输出到也基于字段的文件名 - 无需先排序。就在我身边。
  • awk -F, '{split ($1, a, "_"); print &gt; ("testsort_" a[2] ".csv")}' file.csv 可以避免存储文件名,还是split 的成本会更高?
  • split() 会更慢,因为另一种方式只是哈希查找,而这会在正则表达式中拆分字符串,并且还会在 OP 时根据第一个字段的唯一值生成文件希望它们基于第二个字段中的唯一值。我知道它们在示例中似乎是齐头并进的,但如果这总是成立的话,它们在真实数据中却是一致的。
  • 感谢 Ed,从第一个字段生成文件名的目的只是用 "testsort_1.csv", ... 显示的示例输出
  • @Ed Morton: ($2".csv") 中的 () 是为了可读性还是有任何其他特定目的?我删除了 () 并且它的行为相同。谢谢
【解决方案3】:

您可以通过为文件名保留一个计数器并使用sprintf 为每个连续的文件组创建文件名,以一种相当简单的方式完成此操作。您使用FNR(文件记录号)来区分第一条记录和后续记录。

例如:

$ sort -t, -k2 file.csv | 
awk -F, -v cnt=1 -v fn="testsort_1.csv" '
    FNR==1 {
        prev=$2
        print $0 > fn
    } 
    FNR>1 {
        if ($2!=prev) {
            cnt++
            fn=sprintf("%s_%d.csv", "testsort", cnt)
        }
        print $0 > fn
        prev=$2
    }'

(注意:您将初始文件名设置为要开始的变量,然后使用sprintfcnt(计数)创建所有后续文件名。prev 跟踪第二个字段来自上一条记录。fnsprintf 和计数器创建的文件名。)

最初将 prev 声明为变量的同一脚本的较短版本是:

sort -t, -k2 file.csv | 
awk -F, -v cnt=0 -v prev="" '{
    if ($2!=prev) {
        cnt++
        fn = "testsort_" cnt ".csv"
        prev=$2
    }
    print $0 > fn 
}'

如果您不希望有按顺序编号的文件,而是希望从已排序的记录中获取"testsort_number.csv",请查看@Cyrus now-deleted answer,它在这方面提供了一个优秀(和更短)的解决方案。 (我看到你已经有了很好的答案)

使用/输出示例

使用您在file.csv 中的输入,将创建以下输出文件:

$ for i in testsort_{1..3}.csv; do printf "\n%s\n" $i; cat $i; done

testsort_1.csv
WH_01,TRAINAMS,A10,1221-ESD
WH_01,TRAINAMS,A10,PWR-120
WH_01,TRAINAMS,A101,PWR-120

testsort_2.csv
WH_02,TRAINCLE,A1,074-HD-SATA
WH_02,TRAINCLE,A15,102-55665

testsort_3.csv
WH_03,TRAINLON,L10A3,3005-20
WH_03,TRAINLON,L10A3,3005-21
WH_03,TRAINLON,UK-B3,101859

【讨论】:

    猜你喜欢
    • 2012-04-30
    • 2017-08-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-02-28
    • 1970-01-01
    • 2012-07-11
    • 1970-01-01
    相关资源
    最近更新 更多