如何拆分 CSV 文件并基于列创建多个 CSV 文件

如何拆分 CSV 文件并基于列创建多个 CSV 文件

我有一个 csv 文件,格式如下:

输入.csv:

TIMESTAMP,Data1,Data2,Data3,Data4
"2021-01-03 00:00:00",80953,3.243183,2.943338,358.0123
"2021-01-03 00:01:00",80954,2.173187,1.990327,344.5851
...
"2021-01-03 23:59:00",80957,4.04172,3.82053,355.5481
"2021-01-04 00:00:00",80955,3.700353,3.593842,346.2665
...
"2021-01-04 23:59:00",80956,3.125094,2.922542,350.9915
"2021-01-05 00:00:00",80957,4.04172,3.82053,355.5481
...
"2021-01-05 23:59:00",80956,3.125094,2.922542,350.9915
etc...

该文件包含多天的每分钟数据,并且每分钟更新一次。我想编写一个 bash 脚本,根据 input.csv 中前一天的 TIMESTAMP 列创建多个 csv 文件,如下所示:

cat 20210103000000.csv
TIMESTAMP,Data1,Data2,Data3,Data4
"2021-01-03 00:00:00",80953,3.243183,2.943338,358.0123

cat 20210103000100.csv
TIMESTAMP,Data1,Data2,Data3,Data4
"2021-01-03 00:01:00",80954,2.173187,1.990327,344.5851

......依此类推,直到当天的最后一刻

cat 20210103235900.csv
TIMESTAMP,Data1,Data2,Data3,Data4
"2021-01-03 23:59:00",80957,4.04172,3.82053,355.5481

如果某个时间的数据(例如“2021-01-03 17:06:00”)丢失/不存在,则必须创建以下文件:

20210103170600.csv:
TIMESTAMP,Data1,Data2,Data3,Data4
"2021-01-03 17:06:00",0,0,0,0

这篇文章的解决方案如何按初始列(带标题)拆分 CSV 文件?

awk -F ',' 'NR==1{h=$0; next};!seen[$1]++{f=$1".csv"; print h > f};{f=$1".csv"; print >> f; close(f)}' input.csv

部分解决了我的问题,但它为 input.csv 文件中包含的所有数据创建文件,并且不考虑丢失的记录。

答案1

尝试:

awk -F, -v yesterday="$(date -d'-1day' +'%F')" '
BEGIN{ for(min=0; min<1440; min++){
           mins = "date +%F\" " "\"%T -d\"" min "minutes" yesterday"\""
           mins |getline yday_tmp; close(mins);
           timestamp["\"" yday_tmp "\""] }
     }

NR==1{ hdr=$0; next }

($1 in timestamp){
           cp=$1; gsub(/[-": ]/, "", cp);
           print hdr ORS $0 >(cp".csv");
           close(cp".csv");
           delete timestamp[$1] }

END{ for (x in timestamp){
         cpx=x; gsub(/[-": ]/, "", cpx);
         print hdr ORS x ",0,0,0,0" >(cpx".csv")
         close(cpx".csv")
     }
}' infile

使用awkGNUstrftime()MKTIME()函数可以减少生成时间戳的执行时间,而不是调用外部date命令,并将文件存储在单独的文件中目录并删除所有双引号:

gawk -F, '
BEGIN{ start=strftime("%Y %m %d 00 00 00", systime()-86400);
       for(min=0; min<1440; min++)
           timestamp[strftime("%F %H:%M", mktime(start)+min*60)]
     }

{ gsub(/"/,"") }

NR==1{ 
       hdr=$0; yday=strftime("dir_%Y%m%d", systime()-86400);
               system("mkdir "yday); next 
     }

(substr($1,1,16) in  timestamp){
       cp=$1; gsub(/[-: ]|00$/, "", cp);
       print hdr ORS $0 >(yday"/"cp".csv");
       close(yday"/"cp".csv");
       delete  timestamp[substr($1,1,16)] }

END{ for (x in  timestamp){
         cpx=x; gsub(/[-: ]/, "", cpx);
         print hdr ORS x ",0,0,0,0" >(yday"/"cpx".csv");
         close(yday"/"cpx".csv")
     }
}' infile

如在GNUawk文档
systime()返回一天中的当前时间作为数字自从时代(POSIX 系统上的 1970-01-01 00:00:00 UTC)。让我们打印它:

$ awk 'BEGIN{ print systime() }'
1614100199

mktime(timestamp)关闭时间戳的格式为YYYY MM DD HH MM SS进入纪元时间。

让我们打印它;

$ awk 'BEGIN{ print mktime("2021 02 22 00 00 00") }'
1613939400

strftime(format, timestamp): 格式时间戳根据规范格式。这时间戳应该是纪元类型。

让我们格式化一个时间戳:

$ awk 'BEGIN{ print strftime("%Y-%m-%d %H:%M:%S", mktime("2021 02 23 01 02 00")) }'
2021-02-23 01:02:00

记住以上所有 3 个awk时间函数。

现在让我们看看他们在答案中做了什么:

$ awk 'BEGIN{ print systime()-86400 }'
1614014848

注意86400是每天或 24 小时的秒数;在上面我们说systime()返回一天中的当前时间作为自从时代,因此,如果我们从当前时间减去一天中的秒数,就会得到昨天日期的时间。

让我们将其转换为人类可读的格式,看看它是什么:

$ awk 'BEGIN{ print strftime("%Y %m %d 00 00 00", systime()-86400);  }'
2021 02 22 00 00 00

现在很清楚它是什么时间戳,我们使用小时/分钟/秒为“00”,因为我们需要这个时间戳作为起点,并将其存储到start代码中的变量中。

然后我们使用 for 循环从变量中的时间戳生成其余时间戳,start如下所示:

for(min=0; min<1440; min++)
    timestamp[strftime("%F %H:%M", mktime(start)+min*60)]

注意到号码了1440吗?即一天或 24 小时中的分钟数 (24*60=1440);但mktime()接受时间戳作为纪元和以秒为单位,因此我们将每分钟乘以 60 以获得以秒为单位的时间戳,然后将其转换为这种格式%F %H:%MF日期的 ull 格式与%Y-%m-%dHour 和Minute 相同)并保存到awk我们将其命名为数组timestamp[...];现在我们有所有时间戳的昨天的日期。

你甚至可以打印它们来看看它们是什么:

$ awk '
  BEGIN{
         start=strftime("%Y %m %d 00 00 00", systime()-86400);
         for(min=0; min<1440; min++)
             timestamp[strftime("%F %H:%M", mktime(start)+min*60)];
         for (t in timestamp)
             print t
  }'

以下gsub()函数删除当前行中的所有引号:

{ gsub(/"/,"") }

然后我们将输入文件的第一行(即标题行)备份到hdr变量中,因为我们需要将标题行添加到我们生成的每个文件中;然后我们也创建一个包含昨天日期的目录,它将采用以下格式dir_%Y%m%d:下面的代码块仅在第一个输入行时运行一次NR==1 { "run these" }

NR==1{ 
       hdr=$0; yday=strftime("dir_%Y%m%d", systime()-86400);
       system("mkdir "yday); next 
}

随着系统()我们正在调用外部命令mkdir来创建该目录。

进入下一个块,仅当在第一列中看到时间戳时才运行以下块timestamp大批(substr($1,1,16) in timestamp) { "run these" };substr(字符串,开始[,长度])函数返回一个长度- 字符长的子串细绳,从字符数开始开始

  • cp=$1:我们将第一列复制到cp变量中,我们将使用其中的值进行cp后续处理。
  • gsub(/[-: ]|00$/, "", cp);;从变量中去除字符-:Spacecp以及尾随双零“00”。
  • print hdr ORS $0 >(yday"/"cp".csv");print我们将其保存在 var 中的标题行hdr,一个ORS(这是换行符)输出埃科德S默认情况下分隔符)并将整行$0放入相关的directory/fileName.csv.
  • close(yday"/"cp".csv");:关闭()写入后的文件。
  • delete timestamp[substr($1,1,16)]: 并从数组中删除该时间戳。

在该END { "run these" }块中,我们将输入文件中不存在的时间戳打印到文件中。


处理多个文件并将每个输入文件拆分为单独的文件目录。

gawk -F, '
{ gsub(/"/,"") }

FNR==1{
       delete timestamp;
       start=strftime("%Y %m %d 00 00 00", systime()-86400);
       for(min=0; min<1440; min++)
           timestamp[strftime("%F %H:%M", mktime(start)+min*60)]
       hdr=$0; yday=strftime("%Y%m%d", systime()-86400);
       fname=FILENAME; sub(/\.csv$/,"", fname); dirName=fname"_"yday;
       system("mkdir "dirName); next
     }

(substr($1,1,16) in  timestamp){
       cp=$1; gsub(/[-: ]|00$/, "", cp);
       print hdr ORS $0 >(dirName"/"cp".csv");
       close(dirName"/"cp".csv");
       delete  timestamp[substr($1,1,16)] }

ENDFILE{ for (x in  timestamp){
             cpx=x; gsub(/[-: ]/, "", cpx);
             print hdr ORS x ",0,0,0,0" >(dirName"/"cpx".csv");
             close(dirName"/"cpx".csv")
     }
}' multiple*.csv

答案2

一种蛮力方法,用于填补现有解决方案的空白:它测试了 1440 个文件,但是,嘿,每天只测试一次,而且是昨天的日期。在重击中:

(a) 以两种格式获取昨天的日期:dt=202101032021-01-03

(b) 运行一个for循环:for hm in {00..23}{00..59}。

(c) 对于任何${dt}${hm}00.csv不存在的文件:将所需的标题和详细信息行打印到其中。

我假设您发布的分割部分有效,您只需填写时间戳中的空白即可。

这个 Bash 解决方案已经过测试和评论。它运行时间不到一秒。

#! /bin/bash

#.. In the current directory, this script makes dummy files such that:
#.. (a) There shall be a file for every minute in the previous day.
#..     with the name like YYYYmmddHHMM00 (e.g. 20210103173800.csv).
#.. (b) Any existing file with that name shall not be affected in any way.
#.. (c) Any new file shall contain a header line and one data line, like:
#..     TIMESTAMP,Data1,Data2,Data3,Data4
#..     "2021-01-03 17:38:00",0,0,0,0

#.. Create all the data apart from the hh,mm values.
fnYest="$( date -d '- 1 day' '+%Y%m%d%%s%%s00.csv' )"
dtYest="$( date -d '- 1 day' '+"%Y-%m-%d %%s:%%s:00"' )"
Header='TIMESTAMP,Data1,Data2,Data3,Data4'
Fields=',0,0,0,0'

#.. These five lines are debug, and can be removed.
printf 1>&2 'fnYest: %s\n' "${fnYest}"
printf 1>&2 'dtYest: %s\n' "${dtYest}"
printf 1>&2 "Filename:  ${fnYest}\n" 13 58
printf 1>&2 "TimeStamp: ${dtYest}\n" 13 58
printf 1>&2 "Line 1: %s\nLine 2: ${dtYest}%s\n" "${Header}" 13 58 "${Fields}"

#.. Creates the files that are missing.

for hh in {00..23}; do
    for mm in {00..59}; do
        printf -v fn "${fnYest}" "${hh}" "${mm}" 
        [[ -r "${fn}" ]] && continue
        printf > "${fn}" "%s\n${dtYest}%s\n" "${Header}" "${hh}" "${mm}" "${Fields}"
    done
done

相关内容