예

+%FT%T.%3NZ예를 들어 2시간과 같이 정의된 시간 오프셋/중단/차이에 따라 ISO-8601 UTC 타임스탬프가 있는 CSV 파일을 밀리초 정밀도( 예: ) 로 분할하는 Unix cli 도구를 사용하여 하나의 라이너와 같은 쉬운 방법이 2021-05-27T13:59:33.641Z있는지 궁금합니다.

항상 그렇듯이 이를 갖는 방법에는 여러 가지가 있으며 비슷한 질문을 가진 다른 사용자의 경우 다른 옵션도 포괄적인 답변과 관련이 있을 수 있습니다.

  • 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윈도우 7에서는
  • ...대화형 쉘에서 스크립트에서 이것을 사용하고 싶습니다.
  • ... 세미콜론( ;)을 구분 기호로 사용하고 쉼표는 사용하지 마세요.
  • ... 하다~ 아니다내 값을 인용합니다(예: 작은따옴표( ') 또는 큰따옴표( ")).
  • ... 헤더가 없습니다
  • ... 이미 변수에 전체 CSV가 있고 추가 분석을 위해 결과를 변수(배열?)에 갖고 싶을 수도 있습니다.
  • 내 칼럼은 그렇지~ 아니다실제로는 길이가 고정되어 있으며 영숫자 외에 공백과 하이픈이 포함될 수 있습니다.
  • 타임스탬프는 실제 데이터의 8개 열 중 5번째입니다.
  • 파일은 최대 250,000줄 및 20MiB로 가정할 수 있습니다.
  • 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

다음 세 부분으로 나뉩니다.

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시간마다 새 배치를 보장하기 위한 것입니다. 그렇지 않으면 샘플 입력이 두 개의 배치(6줄에서 6줄)로만 분할됩니다. 14:15~18:15, 21:12와 21:15에 2줄), 16:45와 20:00에 추가 로그 항목을 추가하면 샘플 입력이 분할되는 것을 방지할 수 있습니다. .

입력의 세 번째 필드에서 날짜 및 시간을 가져옵니다. Perl 배열은 1이 아닌 0부터 시작하므로 $F[2]array 의 세 번째 필드도 마찬가지입니다 @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

다른 시간 오프셋을 위해 csv에서 분할하도록 스크립트 블록 ho의 (시간 오프셋) 을 편집할 수 있습니다 .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

관련 정보