【问题标题】:Optimize an awk script for large files为大文件优化 awk 脚本
【发布时间】:2019-01-11 17:38:27
【问题描述】:

TLDR 版本

如何优化以下脚本以更快地处理大文件。

  • ./Processed/Ranked_endpoints_*.csv 大约 ~1200 行
  • Dates.csv 大约 100 万行

正在运行的 shell 脚本(我认为?它仍在运行...):

for RFGAUGE in ./Processed/Ranked_endpoints_*.csv
do
    echo "Processing: $RFGAUGE"
    mkIndex="$(basename $RFGAUGE)"
    echo "$mkIndex"

    ##Duplicate the dates file to be able to edit it in place later on
    cp Dates.csv Timed_${mkIndex%.*}.csv

    ##Remove header
    tail -n +2 $RFGAUGE > noHead_${mkIndex%.*}.csv

    ##The slow part, go line by line and find if the time matches an event and copy to additional columns
    while read LINE
    do
        ##Asign variables from columns and replace , with space
        read RankDUR RankRF RankPEAK RankAVG END START <<< `echo $LINE | cut -d, -f9,10,11,13,14,15 | sed 's/,/ /g'`

        ##Tried using sed and line numbers, failed 
        #STARTLINE=`grep -nr $START Dates.csv | cut -d: -f1`
        #ENDLINE=`grep -nr $END Dates.csv | cut -d: -f1`

        ##Gawk only so can edit file in place
        ##Assigning AWK variables from UNIX variables
        gawk -i inplace -v start="$START" -v end="$END" -v rankdur="$RankDUR" -v rankrf="$RankRF" -v rankpeak="$RankPEAK" -v rankavg="$RankAVG" 'BEGIN{FS=OFS=","}{if($2>=start && $2<=end) print $0,rankdur,rankrf,rankpeak,rankavg; else print $0}' Timed_${mkIndex%.*}.csv

    done < noHead_${mkIndex%.*}.csv

    rm noHead_${mkIndex%.*}.csv

done

加长版

我正在尝试根据几个仪表对最严重的降雨事件进行排名。数据的问题是降雨事件不会在完全相同的时间开始/停止,并且通常相对于彼此偏移几个小时。

我已经编写了一个脚本,该脚本从每个仪表的多年数据中提取了可以称为“事件”的内容,然后对事件的不同参数进行排名。我目前拥有的示例:

./Processed/Ranked_endpoints_*.csv

Date,D,M,Y,WOY, Duration (h),Total RF (mm),Max RF (mm),Rank Duration,Rank Total RF,Rank Max RF,AVG Rank,Rank AVG,EndTime EPOCH, StartTime EPOCH
04/12/2010 05:15:00,4,11,2010,48,7.0,22.599999999999994,8.2,71,39,12,40.6667,1,1291439700,1291414500
17/12/2004 08:00:00,17,11,2004,50,6.5,32.6,5.0,89,12,40,47,2,1103270400,1103247000
25/08/2010 18:00:00,25,7,2010,34,6.5,28.6,4.8,83,20,46,49.6667,3,1282759200,1282735800
...

上述 CSV 中的重要列是:

  • 第 9、10、11、13 列 - 不同参数的排名
  • 第 14 列 - 自纪元以来事件的结束时间(以秒为单位)
  • 第 15 列 - 自纪元以来事件的开始时间(以秒为单位)

我还创建了一个 15 分钟的日期/时间 csv,其中包含自纪元以来的日期和时间,这类似于我用来提取“事件”数据的格式:

Dates.csv

...
03/12/2010 21:45:00,1291412700 
03/12/2010 22:00:00,1291413600 
03/12/2010 22:15:00,1291414500 
03/12/2010 22:30:00,1291415400 
03/12/2010 22:45:00,1291416300 
03/12/2010 23:00:00,1291417200 
03/12/2010 23:15:00,1291418100 
03/12/2010 23:30:00,1291419000 
03/12/2010 23:45:00,1291419900 
04/12/2010 00:00:00,1291420800 
04/12/2010 00:15:00,1291421700 
04/12/2010 00:30:00,1291422600 
04/12/2010 00:45:00,1291423500 
04/12/2010 01:00:00,1291424400 
04/12/2010 01:15:00,1291425300 
04/12/2010 01:30:00,1291426200 
04/12/2010 01:45:00,1291427100 
04/12/2010 02:00:00,1291428000 
04/12/2010 02:15:00,1291428900 
04/12/2010 02:30:00,1291429800 
04/12/2010 02:45:00,1291430700 
04/12/2010 03:00:00,1291431600 
04/12/2010 03:15:00,1291432500 
04/12/2010 03:30:00,1291433400 
04/12/2010 03:45:00,1291434300 
04/12/2010 04:00:00,1291435200 
04/12/2010 04:15:00,1291436100 
04/12/2010 04:30:00,1291437000 
04/12/2010 04:45:00,1291437900 
04/12/2010 05:00:00,1291438800 
04/12/2010 05:15:00,1291439700 
04/12/2010 05:30:00,1291440600
...

考虑到我有大约 20 年的每个仪表 15 分钟的数据,并且可能有大量仪表。如果时间与“事件”之一匹配,将 9、10、11、13 列转移到 Dates.csv 的最佳方法是什么?上面的当前脚本不会将不同的仪表连接到 1 个 CSV 中,但通过剪切/粘贴很容易做到这一点。

因此,假设雨在 1 号后一小时到达 2 号并且持续时间少于 1 小时,那么最终输出将类似于以下内容:

03/12/2010 22:00:00,1291413600
03/12/2010 22:15:00,1291414500 ,71,39,12,1
03/12/2010 22:30:00,1291415400 ,71,39,12,1
03/12/2010 22:45:00,1291416300 ,71,39,12,1
03/12/2010 23:00:00,1291417200 ,71,39,12,1
03/12/2010 23:15:00,1291418100 ,71,39,12,1,13,25,35,4
03/12/2010 23:30:00,1291419000 ,71,39,12,1,13,25,35,4
...
04/12/2010 05:00:00,1291438800 ,71,39,12,1,13,25,35,4
04/12/2010 05:15:00,1291439700 ,71,39,12,1,13,25,35,4
04/12/2010 05:30:00,1291440600

【问题讨论】:

  • 阅读while IFS=, read var1 var2 var3 ... ; do .... ; done &lt; inputFile。但我希望这可以归结为 1 个 awk 脚本来处理它们。现在不能花时间在上面。祝你好运。
  • 您想要的输出是一个 CSV 文件还是几个文件(每个 Ranked_endpoints_*.csv 一个)?
  • 我有一个不同的方法给你。为什么不使用 awk 处理 Ranked_endpoints* 以生成您需要的行,并调整时间到正确的 bin?将时间格式化为 YYYYMMDDhhmmss 并将这些行附加到单个文件中。一旦你有了那个大文件,按数字排序,你就会对所有的事件进行排序。如果您需要填写空白,您可以在大文件上额外执行一次,而不是像 csv 文件中的行一样多地执行。
  • 我不明白如何从该输入中获取该输出。如果答案是“您不能”,那么请修复您的数据,以便样本输出正是您希望从样本输入中产生的结果。
  • Columns 7-10 are just from another gauge (2nd) and have been pasted into the output of gauge 1 using cut -d, -f 3-6。我不知道那是什么意思。提问时,只需发布​​从输入生成的输出,以便帮助展示您的问题,这样我们就可以针对您的输入测试潜在的解决方案,如果它完全产生您的输出,那么我们知道它有效,否则无效。让我们保持简单和轻松。

标签: bash performance csv optimization awk


【解决方案1】:

听起来你可能想做的是运行这个(使用 GNU awk 处理真正的多维数组并排序):

$ cat tst.awk
BEGIN { FS=OFS="," }
NR == 1 { next }
{
    ranks    = $9 OFS $10 OFS $11 OFS $13
    endEpoch = $14
    begEpoch = $15

    for ( epoch=begEpoch; epoch<=endEpoch; epoch+=(15*60) ) {
        epoch2ranks[epoch][++numRanks[epoch]] = ranks
    }
}
END {
    PROCINFO["sorted_in"] = "@ind_num_asc"
    for ( epoch in epoch2ranks ) {
        printf "%s", epoch
        for ( rankNr in epoch2ranks[epoch] ) {
            ranks = epoch2ranks[epoch][rankNr]
            printf "%s%s", OFS, ranks
        }
        print ""
    }
}

你的运行方式:

$ awk -f tst.awk Ranked_endpoints_*.csv

然后使用 UNIX 工具 join 将其输出与 Dates.csv 连接起来。

鉴于您在问题中提供的输入,FWIW:

$ cat file
Date,D,M,Y,WOY, Duration (h),Total RF (mm),Max RF (mm),Rank Duration,Rank Total RF,Rank Max RF,AVG Rank,Rank AVG,EndTime EPOCH, StartTime EPOCH
04/12/2010 05:15:00,4,11,2010,48,7.0,22.599999999999994,8.2,71,39,12,40.6667,1,1291439700,1291414500
17/12/2004 08:00:00,17,11,2004,50,6.5,32.6,5.0,89,12,40,47,2,1103270400,1103247000
25/08/2010 18:00:00,25,7,2010,34,6.5,28.6,4.8,83,20,46,49.6667,3,1282759200,1282735800

它会产生这个输出:

$ awk -f tst.awk file
1103247000,89,12,40,2
1103247900,89,12,40,2
1103248800,89,12,40,2
1103249700,89,12,40,2
1103250600,89,12,40,2
1103251500,89,12,40,2
1103252400,89,12,40,2
1103253300,89,12,40,2
1103254200,89,12,40,2
1103255100,89,12,40,2
1103256000,89,12,40,2
1103256900,89,12,40,2
1103257800,89,12,40,2
1103258700,89,12,40,2
1103259600,89,12,40,2
1103260500,89,12,40,2
1103261400,89,12,40,2
1103262300,89,12,40,2
1103263200,89,12,40,2
1103264100,89,12,40,2
1103265000,89,12,40,2
1103265900,89,12,40,2
1103266800,89,12,40,2
1103267700,89,12,40,2
1103268600,89,12,40,2
1103269500,89,12,40,2
1103270400,89,12,40,2
1282735800,83,20,46,3
1282736700,83,20,46,3
1282737600,83,20,46,3
1282738500,83,20,46,3
1282739400,83,20,46,3
1282740300,83,20,46,3
1282741200,83,20,46,3
1282742100,83,20,46,3
1282743000,83,20,46,3
1282743900,83,20,46,3
1282744800,83,20,46,3
1282745700,83,20,46,3
1282746600,83,20,46,3
1282747500,83,20,46,3
1282748400,83,20,46,3
1282749300,83,20,46,3
1282750200,83,20,46,3
1282751100,83,20,46,3
1282752000,83,20,46,3
1282752900,83,20,46,3
1282753800,83,20,46,3
1282754700,83,20,46,3
1282755600,83,20,46,3
1282756500,83,20,46,3
1282757400,83,20,46,3
1282758300,83,20,46,3
1282759200,83,20,46,3
1291414500,71,39,12,1
1291415400,71,39,12,1
1291416300,71,39,12,1
1291417200,71,39,12,1
1291418100,71,39,12,1
1291419000,71,39,12,1
1291419900,71,39,12,1
1291420800,71,39,12,1
1291421700,71,39,12,1
1291422600,71,39,12,1
1291423500,71,39,12,1
1291424400,71,39,12,1
1291425300,71,39,12,1
1291426200,71,39,12,1
1291427100,71,39,12,1
1291428000,71,39,12,1
1291428900,71,39,12,1
1291429800,71,39,12,1
1291430700,71,39,12,1
1291431600,71,39,12,1
1291432500,71,39,12,1
1291433400,71,39,12,1
1291434300,71,39,12,1
1291435200,71,39,12,1
1291436100,71,39,12,1
1291437000,71,39,12,1
1291437900,71,39,12,1
1291438800,71,39,12,1
1291439700,71,39,12,1

但是 idk 如果这就是您想要的,因为您问题中的示例输出似乎与示例输入不匹配。如果是,那么您只需使用 Dates.csv 中的第二个字段和上述输出中的第一个字段作为要匹配的字段并使用逗号作为字段分隔符来运行 join

【讨论】:

  • 感谢 Ed Mortom!这比我的脚本快得多,是的,现在我需要做的就是将主 Dates.csv 与此运行的输出连接起来以获取多个仪表。输出的不同之处在于我在上面的输出中包含了第二个仪表,方法是在第二个仪表上使用 cut -d, -f3-6 并将其粘贴到第一个仪表上。
  • 等一下 - 您输入的 CSV 中的每一行都是您所说的“仪表”吗?如果是这样,您不需要为多个仪表多次运行该工具,如果这是您的想法,该工具将在其输出中为每个时间戳组合所有这些仪表。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多