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...

ファイルには数日間の分別データが含まれており、毎分更新されます。次のように、input.csvの前日のTIMESTAMP列に基づいて複数のcsvファイルを生成するbashスクリプトを作成したいと思います。

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

awkGNUの使用strftime()そしてMKタイム()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

気づく86400systime()現在の時刻を次のように返すと言った日または24時間の秒数。第二~から連帯したがって、現在の時刻から1日の秒を減算すると、昨日の日付の時刻を取得します。

人が読める形式に変換し、それが何であるかを見てみましょう。

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

これで、タイムスタンプとは何かを明確にします。このタイムスタンプは開始点として必要であり、startコードの変数に格納されるため、時間/分/秒を「00」として使用します。

start次に、forループを使用して、変数のタイムスタンプから次のように残りのタイムスタンプを作成します。

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

番号を確認してください1440。つまり、1日または24時間の分数(24 * 60 = 1440)ですが、mktime()タイムスタンプをエポックと秒単位で許可するので、毎分60を掛けて秒単位のタイムスタンプを取得してから、この形式に変換します%F %H:%MF日付%Y-%m-%dHとinuteと同じull形式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 
}

とともにシステム()mkdirディレクトリを作成するために外部コマンドを呼び出しています。

次のブロックに移動して最初の列にタイムスタンプが表示されている場合にのみ、次のブロックを実行してください。timestamp大量に(substr($1,1,16) in timestamp) { "run these" };部分文字列(文字列、開始[、長さ])この関数は長さ- 文字長の部分文字列ひも、文字数から始まるスタート

  • cp=$1cp:最初の列を変数にコピーし、その値はcp後続の処理に使用されます。
  • gsub(/[-: ]|00$/, "", cp);;変数から文字-:スペースcp、および後に続く二重0 "00"を削除します。
  • print hdr ORS $0 >(yday"/"cp".csv");: var , aprintに格納するヘッダ行 (これは改行文字です)hdrORS酸素出力エココード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個のファイルをテストしますが、1日に1回だけテストされ、その日は昨日です。大きな打撃を受けた場合:

dt=20210103(a)昨日の日付を2つの形式で取得します2021-01-03

(b)forループを実行します。for hm in {00..23}{00..59}。

${dt}${hm}00.csv(c)存在しないファイルの場合:必要なヘッダーと詳細行を印刷します。

投稿した分割が機能すると仮定し、タイムスタンプのスペースを埋めます。

このBashソリューションはテストとレビューを行いました。 1秒以内に実行されます。

#! /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

関連情報