#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. :-).. счастливых выходных, Джеймс