![例](https://rvso.com/image/191806/%E4%BE%8B.png)
+%FT%T.%3NZ
Unix CLI ツールを使用して、ISO-8601 UTC タイムスタンプがミリ秒精度 ( 、例)の CSV ファイルを、定義された時間オフセット / ブレーク / 差異 (たとえば 2 時間) に沿って分割する簡単な方法 (2021-05-27T13:59:33.641Z
おそらく 1 行) があるかどうか知りたいです。
いつものように、それを得るにはいくつかの異なる方法があり、同様の質問を持つ他のユーザーにとっては、包括的な回答には他のオプションも関連する可能性がありますが、私は...
GNU Bash 4.4.23
... git 2.31.1 の、、(およびバンドルされている他のすべてのツール)GNU sed 4.8
を使用します。GNU Awk 5.0.0
xsv 0.13.0
jq 1.6
Windows 7の場合- ...対話型シェルではなくスクリプトでこれを使用する方が良いでしょう
;
...区切り文字としてセミコロン( )を使用し、カンマは使用しないでください。- ... するない値を引用符で囲む(例:一重引用符(
'
)または二重引用符("
)) - ...ヘッダーがありません
- ... すでに変数にCSV全体が格納されており、さらに分析できるように結果を変数(配列?)に格納したいと考えるでしょう。
- 私のコラムはない実際には固定長であり、英数字の他にスペースやハイフンが含まれる場合がある。
- タイムスタンプは、私の現実世界のデータでは8列中5番目です。
- ファイルは最大25万行、20MBと想定される。
- 私のi5-4300Uではスクリプト/コマンドの実行に0.5秒未満しかかからないのが望ましいですが、最大5〜10秒であれば問題にはなりません。
例
分割に使用するオフセットがあった場合2 hours
(何も混ぜていない場合)、次のファイル:
abc;square;2021-05-27T14:15:39.315Z
def;circle;2021-05-27T14:17:03.416Z
ghi;triang;2021-05-27T14:45:13.520Z
abc;circle;2021-05-27T15:25:47.624Z
ghi;square;2021-05-27T17:59:33.641Z
def;triang;2021-05-27T18:15:33.315Z
abc;circle;2021-05-27T21:12:13.350Z
ghi;triang;2021-05-27T21:15:31.135Z
次の3つの部分に分割されます
abc;square;2021-05-27T14:15:39.315Z
def;circle;2021-05-27T14:17:03.416Z
ghi;triang;2021-05-27T14:45:13.520Z
abc;circle;2021-05-27T15:25:47.624Z
ghi;square;2021-05-27T17:59:33.641Z
def;triang;2021-05-27T18:15:33.315Z
abc;circle;2021-05-27T21:12:13.350Z
ghi;triang;2021-05-27T21:15:31.135Z
免責事項: 私はネイティブスピーカーではないので、この質問を言い換えることで理解しやすくなる場合は、ぜひ言い換えてください。冗長な表現は、たとえば、私のユースケースには当てはまらないオプション(カンマ、引用符)を指定したり、この質問のテキストで単語semicolon
と記号の両方を使用したりすることは;
、SEO目的のためです。
答え1
変数にサンプル CSV データを指定すると$csv
、
gawk '
function timestamp2epoch(ts, m) {
if(match(ts, /([0-9]{4})-([0-9]{2})-([0-9]{2})T([0-9]{2}):([0-9]{2}):([0-9]{2})\..*/, m))
return mktime(m[1] " " m[2] " " m[3] " " m[4] " " m[5] " " m[6])
else
return -1
}
BEGIN {
FS = ";"
interval = 2 * 3600 # 2 hours
}
{ t = timestamp2epoch($3) }
t > start + interval { start = t; n++ }
{ batch[n] = batch[n] (batch[n] == "" ? "" : "/") $0 }
END {
PROCINFO["sorted_in"] = "@ind_num_asc"
for (i in batch)
print batch[i]
}
' <<<"$csv"
出力
abc;square;2021-05-27T14:15:39.315Z/def;circle;2021-05-27T14:17:03.416Z/ghi;triang;2021-05-27T14:45:13.520Z/abc;circle;2021-05-27T15:25:47.624Z
ghi;square;2021-05-27T17:59:33.641Z/def;triang;2021-05-27T18:15:33.315Z
abc;circle;2021-05-27T21:12:13.350Z/ghi;triang;2021-05-27T21:15:31.135Z
これは次のようにシェル配列に読み込むことができます:
mapfile -t batches < <(gawk '...' <<<"$csv")
declare -p batches
declare -a batches=([0]="abc;square;2021-05-27T14:15:39.315Z/def;circle;2021-05-27T14:17:03.416Z/ghi;triang;2021-05-27T14:45:13.520Z/abc;circle;2021-05-27T15:25:47.624Z" [1]="ghi;square;2021-05-27T17:59:33.641Z/def;triang;2021-05-27T18:15:33.315Z" [2]="abc;circle;2021-05-27T21:12:13.350Z/ghi;triang;2021-05-27T21:15:31.135Z")
そして、次のように繰り返します。
for ((i = 0; i < "${#batches[@]}"; i++)); do
IFS="/" read -ra records <<<"${batches[i]}"
echo "batch $i"
for record in "${records[@]}"; do echo " $record"; done
echo
done
batch 0
abc;square;2021-05-27T14:15:39.315Z
def;circle;2021-05-27T14:17:03.416Z
ghi;triang;2021-05-27T14:45:13.520Z
abc;circle;2021-05-27T15:25:47.624Z
batch 1
ghi;square;2021-05-27T17:59:33.641Z
def;triang;2021-05-27T18:15:33.315Z
batch 2
abc;circle;2021-05-27T21:12:13.350Z
ghi;triang;2021-05-27T21:15:31.135Z
答え2
次の Perl スクリプトは、入力ファイルを出力し、前回の開始期間から 2 時間以内ではない行が見つかるたびに空白行を追加して、入力を最大 2 時間のバッチに分割します。
開始期間は最初の行の読み取り時に設定され、追加の空白行が印刷された場合にのみ更新されます。これは、少なくとも 2 時間ごとに新しいバッチが確実に作成されるようにするためです。そうしないと、サンプル入力は 2 つのバッチ (14:15 から 18:15 までの 6 行と、21:12 と 21:15 の 2 行) にのみ分割され、たとえば 16:45 と 20:00 などの追加のログ エントリによってサンプル入力が分割されなくなります。
入力の 3 番目のフィールドから日付と時刻を取得します。Perl 配列は 1 ではなく 0 から始まるため、$F[2]
配列の 3 番目のフィールドも同様であることに注意してください@F
。
#!/usr/bin/perl
use strict;
use Date::Parse;
my $start;
while(<>) {
chomp;
my $approx;
my @F = split /;/;
# approximate date/time to start of hour
($approx = $F[2]) =~ s/:\d\d:\d\d\.\d+Z$/:00:00/;
my $now = str2time($approx);
$start = $now if ($. == 1);
if (($now - $start) > 7200) {
$start = $now;
print "\n";
};
print "$_\n";
}
サンプル出力:
$ ./split.pl input.csv
abc;square;2021-05-27T14:15:39.315Z
def;circle;2021-05-27T14:17:03.416Z
ghi;triang;2021-05-27T14:45:13.520Z
abc;circle;2021-05-27T15:25:47.624Z
ghi;square;2021-05-27T17:59:33.641Z
def;triang;2021-05-27T18:15:33.315Z
abc;circle;2021-05-27T21:12:13.350Z
ghi;triang;2021-05-27T21:15:31.135Z
出力を別々のファイルで必要とする場合は、代わりに次のようにします。
#!/usr/bin/perl
use strict;
use Date::Parse;
my $start;
# output-file counter
my $fc = 1;
my $outfile = "file.$fc.csv";
open (my $fh, ">", $outfile) || die "couldn't open $outfile for write: $!\n";
while(<>) {
chomp;
my $approx;
my @F = split /;/;
# approximate date/time to start of hour
($approx = $F[2]) =~ s/:\d\d:\d\d\.\d+Z$/:00:00/;
my $now = str2time($approx);
$start = $now if ($. == 1);
if (($now - $start) > 7200) {
$start = $now;
close($fh);
$fc++;
$outfile = "file.$fc.csv";
open ($fh, ">", $outfile) || die "couldn't open $outfile for write: $!\n";
};
print $fh "$_\n";
}
いずれかのバージョンのスクリプトで処理できる時間形式をもう少し柔軟にしたい場合は、次のようにします。
($approx = $F[2]) =~ s/:\d\d:\d\d(?:\.\d+)?Z?$/:00:00/;
これにより、時間文字列で小数と Z の両方をオプションにすることができます。
答え3
GNU awk の場合gensub()
との場合mktime()
:
$ cat tst.awk
BEGIN {
FS = ";"
maxSecs = 2 * 60 * 60
prevTime = -(maxSecs + 1)
}
{
split($3,dt,/[.]/)
dateHMS = gensub(/[-T:]/," ","g",dt[1])
currSecs = mktime(dateHMS,1) "." dt[2]
secsDelta = currTime - prevTime
prevTime = currTime
}
secsDelta > maxSecs {
close(out)
out = "out" (++numOut)
}
{ print > out }
$ awk -f tst.awk file
$ head out?
==> out1 <==
abc;square;2021-05-27T14:15:39.315Z
def;circle;2021-05-27T14:17:03.416Z
ghi;triang;2021-05-27T14:45:13.520Z
abc;circle;2021-05-27T15:25:47.624Z
==> out2 <==
ghi;square;2021-05-27T17:59:33.641Z
def;triang;2021-05-27T18:15:33.315Z
==> out3 <==
abc;circle;2021-05-27T21:12:13.350Z
ghi;triang;2021-05-27T21:15:31.135Z
答え4
ファイル内のすべての日付が同じ日に属する場合:
#!/usr/bin/awk -f
BEGIN {
FS=OFS=";"
ho = 1
}
{
# Split the last field in date and times
split($NF, a, "T")
# Get the hour from time
h = a[2]
sub(/:.*$/, "", h)
if (lh == 0) lh = h+ho
if (h > lh) {
lh = h+ho
print "\n"
}
}1
スクリプトのブロックho
内の (時間オフセット)を編集して、他の時間オフセットの csv を分割できます。BEGIN
#!/usr/bin/awk -f
BEGIN {
FS=OFS=";"
# Set here the hour offset
hour_offset = 1
# Get the hour values in seconds
ho = 60 * 60 * hour_offset
}
{
sub(/Z$/, "", $NF)
# Call /bin/date and translate the 'visual date' to
# epoch timestamp.
cmd="/bin/date -d " $NF " +%s"
epoch=((cmd | getline line) > 0 ? line : -1)
close(cmd)
if (epoch == -1) {
print "Date throw an error at : " NR;
exit 1;
}
# If the lh (last hour) is not set, set it
# to the current value for the epoch time plus
# the chosen offset
if (!lh) lh = epoch + ho
# if the current offset less the the old hour processed is
# greater then the offset you choose: update the offset and
# print the separator
if (epoch - lh > ho) {
lh = epoch + ho
print ""
}
}1