суммируем по годам и вставляем недостающие записи с 0

#bash #awk

#bash #awk

Вопрос:

У меня есть отчет для записей за год и месяц, как показано ниже

 201703 5
201708 10
201709 20
201710 40
201711 80
201712 100
201802 0
201803 25
201804 50
201805 50
201806 150
201807 300
201808 200
201902 10 
  

Мне нужно суммировать записи за год и месяц по годам и печатать после всех месяцев за этот конкретный год. Год-месяц может содержать пропущенные записи для любого месяца (ов).
Для этих месяцев должно быть вставлено фиктивное значение (0).

Требуемый вывод:

 201703 5
201704 0
201705 0
201706 0
201707 0
201708 10
201709 20
201710 40
201711 80
201712 100
2017 255
201801 0
201802 0
201803 25
201804 50
201805 50
201806 150
201807 300
201808 200
201809 0
201810 0
201811 0
201812 0
2018 775
201901 0
201902 10
201903 0
2019 10
  

Я могу получить сводку за год, используя приведенную ниже команду.

 awk ' { c=substr($1,0,4); if(c!=p) { print p,s ;s=0} s=s $2 ; p=c ; print } ' ym.dat
  

Но, как вставить записи для недостающих?.
Также последняя запись не должна превышать текущий год-месяц (по системному времени). т.е. для этого конкретного примера не следует вставлять фиктивные значения для 201904 .. 201905 .. и т.д. Это должно просто прекратиться с 201903

Комментарии:

1. Вы хотите, чтобы первые тоже были добавлены? Т.е. 201701 и т.д.

2. нет .. это говорит о чем-то в начале отчета .. так что добавление 201701 неверно

3. Как вы распознаете «недостающую запись» для 201701 из отчета, начинающегося с 201702?

4. Почему есть 0 запись для 201903 , но нет для других месяцев 2019 ?

5. @anubhava 201903 — текущий (системный) год-месяц.. так что это должно быть включено..

Ответ №1:

Вы можете использовать этот awk скрипт mmyy.awk :

 {
   rec[$1] = $2;
   yy=substr($1, 1, 4)
   mm=substr($1, 5, 2)   0
   ys[yy]  = $2
}

NR == 1 {
   fm = mm
   fy = yy
}

END {
   for (y=fy; y<=cy; y  )
      for (m=1; m<=12; m  ) {
         # print previous years sums
         if (m == 1 amp;amp; y-1 in ys)
            print y-1, ys[y-1]

         if (y == fy amp;amp; m < fm)
            continue;
         else if (y == cy amp;amp; m > cm)
            break;

         # print year month with values or 0 if entry is missing
         k = sprintf("%dd", y, m)
         printf "%dd %dn", y, m, (k in rec ? rec[k] : 0)
      }
      print y-1, ys[y-1]
}
  

Затем вызовите это как:

 awk -v cy=$(date ' %Y') -v cm=$(date ' %m') -f mmyy.awk file
  

 201703 5
201704 0
201705 0
201706 0
201707 0
201708 10
201709 20
201710 40
201711 80
201712 100
2017 255
201801 0
201802 0
201803 25
201804 50
201805 50
201806 150
201807 300
201808 200
201809 0
201810 0
201811 0
201812 0
2018 775
201901 0
201902 10
201903 0
2019 10
  

Ответ №2:

С помощью GNU awk для strftime():

 $ cat tst.awk
NR==1 {
    begDate = $1
    endDate = strftime("%Y%m")
}
{
    val[$1] = $NF
    year = substr($1,1,4)
}
year != prevYear { prt(); prevYear=year }
END { prt() }

function prt(   mth, sum, date) {
    if (prevYear != "") {
        for (mth=1; mth<=12; mth  ) {
            date = sprintf("dd", prevYear, mth)
            if ( (date >= begDate) amp;amp; (date <=endDate) ) {
                print date, val[date] 0
                sum  = val[date]
                delete val[date]
            }
        }
        print prevYear, sum 0
    }
}
  

.

 $ awk -f  tst.awk file
201703 5
201704 0
201705 0
201706 0
201707 0
201708 10
201709 20
201710 40
201711 80
201712 100
2017 255
201801 0
201802 0
201803 25
201804 50
201805 50
201806 150
201807 300
201808 200
201809 0
201810 0
201811 0
201812 0
2018 775
201901 0
201902 10
201903 0
2019 10
  

С другими awks вы бы просто передали конечную дату, используя awk -v endDate=$(date '%Y%m') '...'

Ответ №3:

Perl на помощь!

 perl -lane '$start ||= $F[0];
            $Y{substr $F[0], 0, 4}  = $F[1];
            $YM{$F[0]} = $F[1];
            END { for $y (sort keys %Y) {
                      for $m (1 .. 12) {
                          $m = sprintf "d", $m;
                          next if "$y$m" lt $start;
                          print "$y$m ", $YM{$y . $m} || 0;
                          last if $y == 1900   (localtime)[5]
                               amp;amp; (localtime)[4] < $m;
                      }
                      print "$y ", $Y{$y} || 0;
                  }
              }' -- file
  
  • -n считывает входные данные строка за строкой
  • -l удаляет новые строки из входных данных и добавляет их в выходные
  • -a разбивает каждую строку на пробелы в массив @F

  • substr извлекает год из даты ГГГГ. Хэши %Y и %YM используют даты и ключи, а подсчеты — значения. Вот почему используется хэш года = , который добавляет значение к уже накопленному.

  • КОНЕЧНЫЙ блок вычисляется после того, как входные данные исчерпаны.
  • Он просто перебирает годы, хранящиеся в хэше, диапазон 1 .. 12 используется для месяца для вставки нулей ( || оператор печатает его).
  • далее и $start пропускает месяцы, предшествующие началу отчета.
  • last отвечает за пропуск остальной части текущего года.

Комментарии:

1. можете ли вы исправить начало YM

Ответ №4:

Следующий awk-скрипт сделает то, что вы ожидаете. Идея в том,:

  • хранить данные в массиве
  • выводить и суммировать только при изменении года

Это дает:

 # function that prints the year starting
# at month m1 and ending at m2
function print_year(m1,m2,   s,str) {
    s=0
    for(i=(m1 0); i<=(m2 0);   i) { 
       str=y sprintf("%0.2d",i);
       print str, a[str] 0; s =a[str]
    }
    print y,s
}

# This works for GNU awk, replace for posix with a call as
# awk -v stime=$(date " %Y%m") -f script.awk file
BEGIN{ stime=strftime("%Y%m") }
# initializer on first record    
(NR==1){ y=substr($1,1,4); m1=substr($1,5) }
# print intermediate year
(substr($1,1,4) != y) { 
    print_year(m1,12)
    y=substr($1,1,4); m1="01";
    delete a
}
# set array value and keep track of last month
{a[$1]=$2; m2=substr($1,5)}
# check if entry is still valid (past stime or not)
($1 > stime) { exit }
# print all missing years full
# print last year upto system time month
END { 
  for (;y<substr(stime,1,4) 0;y  ) { print_year(m1,12); m1=1; m2=12; }
  print_year(m1,substr(stime,5))
}
  

Комментарии:

1. в выходных данных отсутствует запись 201903

2. @stack0114106 в ваших входных данных нет 201903, поэтому его не может быть в ваших выходных данных. Последний месяц — февраль.

3. Я упомянул это как текущий (т. е. системный) год-месяц .. также добавил уточнение в вопросе

4. np .. где вы использовали системное время?.. когда я запущу с тем же вводом в следующем месяце, то есть в апреле .. в нем также должна быть одна запись за 201904 год..

5. @stack0114106 должен сработать сейчас, но я не знаю, будет ли он охватывать все особые случаи, такие как отсутствие записей до системного времени, пустой файл и т.д…

Ответ №5:

Кстати, хороший вопрос. В пятницу после обеда поджаривай мозги. Пора отправляться домой.

В awk. Необязательное время окончания и его значение вводятся в качестве аргументов:

 $ awk -v arg1=201904 -v arg2=100 '          # optional parameters
function foo(ym,v) {
    while(p<ym){
        y=substr(p,1,4)                     # get year from previous round
        m=substr(p,5,2) 0                   # get month
        p=y (m==12) sprintf("d",m 1)  # December magic
        if(m==12)
            print y,s[y]                    # print the sums (delete maybe?)
        print p, (p==ym?v:0)                # print yyyymm and 0/$2
    }
}
{
    s[substr($1,1,4)] =$2                   # sums in array, year index
}
NR==1 {                                     # handle first record
    print
    p=$1
}
NR>1 {
    foo($1,$2)
}
END {
    if(arg1)
        foo(arg1,arg2)
    print y=substr($1,1,4),s[y] arg2
}' file
  

Хвост из выходных данных:

 2018 775
201901 0
201902 10
201903 0
201904 100
2019 110
  

Комментарии:

1. привет, Джеймс.. спасибо за ответ.. отсутствует запись текущего (системного) месяца 201903..

2. @stack0114106 полностью пропустил это. Думаю, я еще не направляюсь домой.

3. Мне бы это не понадобилось как искусство .. тогда люди, использующие это, не изменились бы в ближайшие месяцы.. np Я могу редактировать iy

4. Я скопирую логику из ответа @ EdMorton, как только вернусь домой. ; D Я имею в виду, что я покупаю это с повышением голосов…

5. :-).. счастливых выходных, Джеймс