Awk:将多个文件中的列与跨多行数据的计算相结合

Awk:将多个文件中的列与跨多行数据的计算相结合

如何将多个文件中的选定列合并到一个文件中,并对相邻行数据执行一些计算?

这是一个示例:来自多个设备的多个数据文件,每个文件报告每个月多个传感器的数据。所以有一个文件/设备/月,我想要一个文件/传感器。

以下是一些示例数据和预期输出。

示例数据文件device0_202105.csv

Date;Time;Timestamp;PM2_5;AQI;PM10;CO2
2021/05/01;00:00:49;1619827249;21.0;70;29.0;413
2021/05/01;00:10:49;1619827849;20.0;68;37.0;409
2021/05/01;00:20:49;1619828449;21.0;70;39.0;412
2021/05/03;08:10:39;1620029439;33.0;95;43.0;430
2021/05/03;08:20:39;1620030039;33.0;95;50.0;427
2021/05/03;08:30:39;1620030639;35.0;99;38.0;429
2021/05/03;08:40:39;1620031239;33.0;95;46.0;431
2021/05/03;18:10:39;1620065439;12.0;50;18.0;425
2021/05/03;18:20:39;1620066039;12.0;50;18.0;426

示例数据文件device0_202106.csv

Date;Time;Timestamp;PM2_5;AQI;PM10;CO2
2021/06/01;08:19:16;1622535556;19.0;66;30.0;426
2021/06/01;08:29:16;1622536156;20.0;68;33.0;454
2021/06/01;08:39:16;1622536756;24.0;76;31.0;456
2021/06/01;20:49:16;1622580556;36.0;102;32.0;447

示例数据文件device1_202105.csv

Date;Time;Timestamp;PM2_5;AQI;PM10;CO2
2021/05/03;11:14:59;1620040499;19.0;66;20.0;438
2021/05/03;11:15:09;1620040509;19.0;66;20.0;486
2021/05/03;11:15:19;1620040519;18.0;63;18.0;485

我想为整个可用数据周期(这里是 202105 和 202106)的每种类型的传感器(例如 CO2)创建一个文件,data-co2.csv使用上述数据看起来像这样:

Date;Time;Device 0;Device 1
2021/05/03;10:30;429.25;469.667
2021/06/01;10:30;475.333

每个设备的数据以列形式报告每个数据点是特定时间间隔内的平均值。因此,一行报告原始数据的一个时间间隔的平均值。

我最初考虑每个时间间隔只有 2 个工作日仅:上午 8:00 至 13:00(标记为 10:30)的时间间隔和下午 13:00 至 18:00(标记为 15:30)的时间间隔。

我计划运行一个awk由脚本启动的脚本,bash该脚本将循环遍历设备和周期文件。这是我的脚本的开始。但是,我在写入输出文件时遇到问题(我应该使用该-inplace选项吗?)。我正在考虑一个更简单的路线:写入临时文件并稍后连接到输出文件。

#!/bin/bash
touch data-co2.csv
gawk -v device=0 -v sensor=18 -f read-data.awk device0_202105.csv data-co2.csv
#!/bin/gawk -f
BEGIN {
    FS  = "[;/:]";
    OFS = ";";
    day  = 1 ;
    sam = 0 ; nam = 0 ; spm = 0 ; npm = 0 ;
}
FNR==NR {
    if ( $1 ~ /20[0-9]{2}/ ) {
        if ( $3 != day ) {
            if ( nam != 0 ) a[date";10:30"] = sam / nam ;
            if ( npm != 0 ) a[date";15:30"] = spm / npm ;
            day = $3 ;
            sam = 0 ; nam = 0 ; spm = 0 ; npm = 0 ;
        }
        if ( strftime("%u", $7, 1) < 6) {
            if ( $4 >= 8 && $4 <= 12 ) {
                sam += $sensor ;
                ++nam ;
            }
            else if ( $4 >= 13 && $4 < 18 )  {
                spm += $sensor ;
                ++npm ;
            }
        }
        date = $1"/"$2"/"$3 ;
    }
    next ;
}
{
    if ( device == 0 ) {
       for ( i in a ) {
           print i, a[i] ;
       }
    }
    else {
       i = $1"/"$2"/"$3";10:30" ;
       j = $1"/"$2"/"$3";15:30" ;
       print $0, a[i] ;
       print $0, a[j] ;
    }
}

请注意,每个设备报告数据的时间不同,并且由于设备故障、网络问题等原因,数据可能会丢失。

已编辑遵循评论。

答案1

#setting ":" as FS allows taking hours as separate field
BEGIN { FS="[:;]" ; OFS="\t" 
        #this gawk feature helps properly addressing the arrays in the end
        PROCINFO["sorted_in"] = "@ind_str_asc"
}

#get device ID from filename on every new file
#get device IDs in array
FNR==1 {devID=FILENAME ; sub(/_.*/,"",devID) ; devs[devID]=devID }

#select time ranges, sum up values in time ranges and count occurences
FNR>1 {
    if ($2 >= 8 && $2 <= 12) {
        vals[devID,$1,1030]=vals[devID,$1,1030]+$NF
        n[devID,$1,1030]++
        }
    else if ($2 >= 13 && $2 <= 17) {
        vals[devID,$1,1530]=vals[devID,$1,1530]+$NF
        n[devID,$1,1530]++
        }
#get dates in array
    dates[$1]=$1
}
    
END {
    #needed for value selection
    times[1030]="10:30"
    times[1530]="15:30"
    #print headers
    printf("date\ttime")
    for (dev in devs) {printf("\t"dev)}
    printf("\n")

    
    #print values
    for (date in dates) {
    #get day of week from system date command
        cmd="date -d"date" +%w"
        cmd | getline dow
    #do not use Sat+Sun
        if ( dow != 0 && dow != 6 ) {
            for (time in times) {
                printf(date"\t"times[time])
                for (dev in devs) {
                    if ( !vals[dev,date,time] ) { printf("\tN/A") }
                    else { printf("\t"vals[dev,date,time]/n[dev,date,time]) }
                }
                printf("\n")
            }
        }
    }   
}

也许不是最优雅的,但它可以完成工作。请注意,数组遍历选项形式gawk是确保设备的列标题与值匹配所必需的。

根据示例输入创建名为 1_04、2_04 和 3_02 的示例输出表单文件,添加了一些日期(5 月 1 日和 2 日是周末,未选择,添加了更多天来测试“N/A”)和一些数字崩溃(以确保数量和设备匹配)。

date    time    1   2   3
2021/05/03  10:30   832 N/A 832
2021/05/03  15:30   406 401 406
2021/05/04  10:30   809 809 1009
2021/05/04  15:30   N/A N/A N/A
2021/05/06  10:30   N/A 832 N/A
2021/05/06  15:30   N/A N/A N/A

正如您所看到的,它甚至会显示全天或时间间隔未给出所有设备的值的情况。但相应的日期必须位于日志文件中。

相关内容