Shell-Script

如何拆分 CSV 文件並基於列創建多個 CSV 文件

  • February 25, 2021

我有一個格式為 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 文件中包含的所有數據創建文件,並且不考慮失去的記錄。

嘗試:

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

awkstrftime()mktime()函式使用 GNU來減少生成時間戳的執行時間,而不是呼叫外部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):根據格式中的規範格式化**時間戳時間戳應該是 epoch 類型。

讓我們格式化一個時間戳:

$ 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()返回目前時間作為自Epoch以來的秒數,所以如果我們從目前時間減去一天中的秒數,它會給我們昨天日期的時間。

讓我們將其轉換為人類可讀的,看看那是什麼:

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

現在很清楚它是什麼時間戳,我們使用 Hour/Min/Sec 為“00”,因為我們需要這個時間戳作為起點,並將其儲存到start程式碼中的變數中。

然後我們使用 for 循環從變數中的時間戳生成其餘時間戳,start如下所示:

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

注意數字1440?即一天或 24 小時中的分鐘數 (2460=1440);但是mktime()接受時間戳作為紀元和秒,所以我們將每分鐘乘以 60 以獲得以秒為單位的時間戳,然後將其轉換為這種格式%F %H:%MF日期的 ull 格式與我們的和inute 相同%Y-%m-%d)並保存到我們命名的awk*數組中;現在我們每分鐘都有所有時間戳的昨天日期。H``M``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 
}

使用system()函式,我們呼叫外部命令mkdir來創建該目錄。

進入下一個塊,只有在*timestamp數組中看到第一列的時間戳時才執行下一個塊(substr($1,1,16) in timestamp) { "run these" }substr(字元串,開始$$ , length $$)函式返回 string 的長度*-character-long 子字元串,從字元號start 開始

  • cp=$1:我們將第一列複製到cp變數中,我們將在cp後面的處理中使用值。
  • gsub(/[-: ]|00$/, "", cp);; 從變數中去除字元-和空格以及尾隨的雙零“00”。:``cp
  • print hdr ORS $0 >(yday"/"cp".csv");:print我們將它保存在hdrvar 中的標題行,an (預設情況下,它是O utput Record SORS分隔符的換行符)和整行到相關的.$0``directory/fileName.csv
  • close(yday"/"cp".csv");: close()寫入後的文件。
  • 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

引用自:https://unix.stackexchange.com/questions/635715