我有一个 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
使用awk
GNUstrftime()和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:%M
(F
日期的 ull 格式与%Y-%m-%d
、H
our 和M
inute 相同)并保存到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=20210103
和2021-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