Мой fb.csv
файл находится как показано ниже:
"Source","Time"
"192.168.137.174","120025"
"10.0.138.163","120525"
"157.240.10.13","121036"
"157.240.10.13","122536"
"157.240.10.23","123041"
"157.240.10.23","123241"
"10.0.138.163","123352"
"192.168.137.174","123952"
"157.240.10.18","124152"
"157.240.10.18","124252"
"157.240.10.23","125653"
"157.240.10.23","130053"
"192.168.137.174","130102"
"10.0.138.163","130302"
"192.168.137.174","131007"
"192.168.137.174","131352"
"157.240.10.18","132552"
"157.240.10.18","132752"
"157.240.10.23","132953"
"157.240.10.23","133253"
"192.168.137.174","133502"
"10.0.138.163","134002"
"192.168.137.174","134507"
"192.168.137.174","135752"
"157.240.10.18","140052"
"157.240.10.18","140552"
"157.240.10.23","140653"
"157.240.10.23","141053"
"192.168.137.174","141402"
"10.0.138.163","141702"
"192.168.137.174","142707"
Я хотел бы сгруппировать (подсчитать) «Источники» для каждого временного интервала 003000 (30 минут).
Пример желаемого результата:
"Time Interval","Count of Sources"
"120000","4"
"123000","7"
"130000","8"
"133000","5"
"140000","7"
Есть ли какое-нибудь возможное решение этой проблемы? {{1} } Спасибо.
Мы можем сделать это полностью использование только awk
:
awk -F, 'BEGIN{print"\"Time Interval\",\"Count of Sources\""}NR>1{gsub(/"/,"",$2);h=int($2/10000)*10000;m=int(($2-h)/3000)*3000;ctr[h+m]++}END{n=asorti(ctr,idx);for(i=1;i<=n;i++){print "\""idx[i]"\",\""ctr[idx[i]]"\""}}' fb.csv
Для Вашего данного входного файла fb.csv
это приводит к выводу
"120000","4"
"123000","7"
"130000","8"
"133000","5"
"140000","7"
Важный: Это требует GNU AWK (gawk
) работать, потому что это использует asorti(...)
функция для сортировки ассоциативных массивов их индексами. Это не работает с mawk
. Можно узнать значение по умолчанию awk
версия с использованием awk -Wv
.
Мы работаем awk
как это здесь, устанавливая разделителя полей, который разграничивает столбцы к ,
и использование файла fb.csv
как введено:
awk -F, '<COMMAND>' fb.csv
awk
команда (заполнитель <COMMAND>
выше), это, после надлежащего форматирования:
BEGIN {
print "\"Time Interval\",\"Count of Sources\""
}
NR>1 {
gsub(/"/, "", $2)
h = int($2 / 10000) * 10000
m = int(($2-h) / 3000) * 3000
ctr[h+m]++
}
END {
n = asorti(ctr, idx)
for(i=1; i<=n; i++) {
print "\"" idx[i] "\",\"" ctr[idx[i]] "\""
}
}
Это выглядит ужасно сложным (и я не могу отклонить это, требуется некоторая мысль для понимания), таким образом, я попытаюсь разбить его немного:
BEGIN { ... }
блок кода будет выполняться однажды, первая строка входа из файла читается. Затем для каждого, но первой строки ("номер строки, больше, чем 1"), NR>1 { ... }
блок выполняется. Наконец после того, как весь вход читается, END { ... }
блок будет работать.
Теперь BEGIN
блок довольно прост, он только печатает новую строку заголовка CSV.
Давайте посмотрим на NR>1
блок. Помните это awk
разделения каждая строка в поля, которые были разделены разделителем полей (тот мы устанавливаем на ,
использование -F
аргумент). Первый столбец/поле будет сохранен в переменной $1
, второе в $2
и так далее. Мы только интересуемся значением второго поля, которое содержит время.
Используя gsub(<PATTERN>, <REPLACEMENT>, <VARIABLE>)
функция, мы заменяем все случаи <PATTERN>
(регулярное выражение включило в наклонные черты, здесь оно просто соответствует только кавычкам) с a <REPLACEMENT>
(пустой, поскольку мы хотим удалить их), строка в <VARIABLE>
($2
т.е. второй зарегистрированный, содержащий время здесь).
Затем мы декодируем метку времени в целые целые часы h
(умноженный с 10 000) и целое полчаса m
(без целых часов; умноженный с 3 000). Мы используем ассоциативный массив ctr
как счетчик как часто округленная метка времени h+m
происходит во входе.
Наконец в END
блок, мы распечатываем значения счетчика, отсортированные по округленным индексам метки времени.
Я уверен, что есть более элегантный способ, но я предлагаю создать исполняемый файл, назовем его counter.bash
, с таким содержанием скрипта:
#!/bin/bash
echo '"Time Interval","Count of Sources"'
FILTRED=$(tail -n +2 "$1" | sed -e 's/^.*\,//' -e 's/"//g' | sort)
T=1
while [ $T -lt 24 ]; do
((T++)); R1=0; R2=0
for i in $FILTRED; do
hour=${i::-4}; minute=${i:2:-2}
if [ "$T" -lt "10" ]; then TT="0${T}"; else TT="${T}"; fi
if [ "$minute" -lt "30" ]; then
if [ "$hour" == "$TT" ]; then ((R1++)); fi
else
if [ "$hour" == "$TT" ]; then ((R2++)); fi
fi
done
if [ "$R1" -ne "0" ]; then echo "\"${TT}0000\",\"$R1\""; fi
if [ "$R2" -ne "0" ]; then echo "\"${TT}3000\",\"$R2\""; fi
done
Затем запустите:
./counter.bash fb.csv
Если результат достаточный, перенаправьте вывод в новый файл:
./counter.bash fb.csv > fb.counted.csv
Я сравнил производительность ответа Byte Commander и этого сценария, оба применяются к одному и тому же не такой огромный файл. Они просто несопоставимы:
$ cat fb.csv | wc -l
3304
$ time awk -F, 'BEGIN{print"\"Time Interval\",\"Count of Sources\""}NR>1{gsub(/"/,"",$2);h=int($2/10000)*10000;m=int(($2-h)/3000)*3000;ctr[h+m]++}END{n=asorti(ctr,idx);for(i=1;i<=n;i++){print "\""idx[i]"\",\""ctr[idx[i]]"\""}}' fb.csv
"Time Interval","Count of Sources"
"120000","672"
"123000","672"
"130000","560"
"133000","560"
"140000","839"
real 0m0.017s
user 0m0.012s
sys 0m0.000s
$ time ./counter.bash fb.csv
"Time Interval","Count of Sources"
"120000","672"
"123000","672"
"130000","560"
"133000","560"
"140000","839"
real 0m2.374s
user 0m2.368s
sys 0m0.000s
Когда файл действительно огромен, мой скрипт просто вылетает (по разным строкам) через долгое время:
$ cat fb.csv | wc -l
9303745
$ time awk -F, 'BEGIN{print"\"Time Interval\",\"Count of Sources\""}NR>1{gsub(/"/,"",$2);h=int($2/10000)*10000;m=int(($2-h)/3000)*3000;ctr[h+m]++}END{n=asorti(ctr,idx);for(i=1;i<=n;i++){print "\""idx[i]"\",\""ctr[idx[i]]"\""}}' fb.csv
"Time Interval","Count of Sources"
"120000","1892288"
"123000","1892290"
"130000","1576904"
"133000","1576905"
"140000","2365357"
real 0m23.193s
user 0m23.080s
sys 0m0.096s
$ time ./counter.bash fb.csv
"Time Interval","Count of Sources"
./counter.bash: line 17: [: .0.138.1: integer expression expected
^C
real 2m27.992s
user 2m27.940s
sys 0m1.636s