Объединить несколько строк на основе столбца 1 - PullRequest
0 голосов
/ 23 апреля 2020

В настоящее время у меня есть этот образец данных в test.csv:

    0004F2426603,74.214.224.150,16/Apr/2020
    0004F2426603,74.214.224.150,17/Apr/2020
    0004F2426603,74.214.224.150,18/Apr/2020
    00085D20A469,1.2.3.4,16/Apr/2020
    00085D20A469,1.2.3.4,17/Apr/2020
    00085D20A469,1.2.3.4,18/Apr/2020
    00085D20A469,8.8.8.8,16/Apr/2020
    64167F801BF5,1.2.3.4,16/Apr/2020
    64167F801BF5,1.2.3.4,17/Apr/2020
    64167F801BF5,1.2.3.4,18/Apr/2020
    64167F801BF5,8.8.8.8,16/Apr/2020

Я использовал datama sh для группировки на основе столбца 1 (адрес MA C) и проанализировать IP-адреса.

Я могу заставить его выглядеть следующим образом:

    datamash -st, -g1 unique 2 < test.csv
    0004F2426603,74.214.224.150
    00085D20A469,1.2.3.4,8.8.8.8
    64167F801BF5,1.2.3.4,8.8.8.8

    datamash -st, -g1,2 count 2 < test.csv
    0004F2426603,74.214.224.150,3
    00085D20A469,1.2.3.4,3
    00085D20A469,8.8.8.8,1
    64167F801BF5,1.2.3.4,3
    64167F801BF5,8.8.8.8,1

Но как мне заставить его сбросить верхнюю строку, в которой нет дубликатов MA C, так как только один IP-адрес, и сделать вывод, который выглядит следующим образом?

    00085D20A469,1.2.3.4,3,8.8.8.8,1
    64167F801BF5,1.2.3.4,3,8.8.8.8,1

Или это, если было 3 IP-адресов ..

    64167F801BF5,1.2.3.4,3,8.8.8.8,1,9.9.9.9,1

Я хочу наименьшее количество слева , Я подозреваю, что awk может сделать это, но я действительно боролся.

Ответы [ 4 ]

0 голосов
/ 26 апреля 2020

Это типичная проблема SQL, поэтому вы можете решить ее, используя sqlite3 в linux. Попробуйте это.

$ cat a.sh
#!/bin/sh
sqlite3 << EOF
create table t1(id, ip_addr,dt);
.separator ,
.import $1 t1
select id, group_concat(ip_addr||','||c1) from (
select id, ip_addr, count(*) c1 from t1 where id in (
select id from ( select id, ip_addr, count(*) c from t1 group by id, ip_addr) t group by id having count(id) >1)
group by id, ip_addr )
group by id
;

EOF
$ cat ip.dat
 0004F2426603,74.214.224.150,16/Apr/2020
 0004F2426603,74.214.224.150,17/Apr/2020
 0004F2426603,74.214.224.150,18/Apr/2020
 00085D20A469,1.2.3.4,16/Apr/2020
 00085D20A469,1.2.3.4,17/Apr/2020
 00085D20A469,1.2.3.4,18/Apr/2020
 00085D20A469,8.8.8.8,16/Apr/2020
 64167F801BF5,1.2.3.4,16/Apr/2020
 64167F801BF5,1.2.3.4,17/Apr/2020
 64167F801BF5,1.2.3.4,18/Apr/2020
 64167F801BF5,8.8.8.8,16/Apr/2020
$ a.sh ip.dat  # Execute a.sh by passing the file as parameter
 00085D20A469,1.2.3.4,3,8.8.8.8,1
 64167F801BF5,1.2.3.4,3,8.8.8.8,1
$
0 голосов
/ 23 апреля 2020

Вы можете использовать каналы для загрузки данных из CSV. Я сохранил их в файл fil1.txt, чтобы не затемнять решение.

Входные данные (fil1.txt):

0004F2426603,74.214.224.150,3
00085D20A469,1.2.3.4,3
00085D20A469,8.8.8.8,1
64167F801BF5,4.3.2.1,3,3.3.3.3,2
64167F801BF5,9.9.9.9,1
0004F2426603,74.214.224.150,4

Сценарий Awk (fil1.awk):

// {
  if (l==$1) {
    print($0","r)
  }
  l = $1
  r = $2
}

Вызов:

cat fil1.txt |sed 's/,/ /' |awk -f fil1.awk

Вывод:

00085D20A469 8.8.8.8,1,1.2.3.4,3
64167F801BF5 9.9.9.9,1,4.3.2.1,3,3.3.3.3,2

Объяснение:

// match to each line (default action) 
If (l == $1) checks if variable l is equal to first field ($1),
For 1 line in the txt file, l has no value so it bypasses the content of 
brackets {} and assigns the first field of the first line to variable l 
and the second field of the first line to variable r
For the second line of the txt file l and $1 are different, therefore 
body if is not performed again
For 3 line of the txt file l and $1 are the same:
print($0","r) prints the entire line 3 (field $0), a literal comma 
and the stored field 2 from the previous line.

and everything repeats for the next lines of the txt file

Как вы и хотели в комментарии, версия работает для любое количество отсортированных строк повторяющихся адресов MA C:

// {
  if (l == $1) {
  s = s","r
  }
  else {
    if (s != "") {
      printf("%s %s%s\n", l, r, s)
      s = ""
    }
  }
  l = $1
  r = $2
}
0 голосов
/ 23 апреля 2020

С GNU awk для массивов массивов:

$ cat tst.awk
BEGIN { FS="," }
{ mac_ips[$1][$2]++ }
END {
    for ( mac in mac_ips ) {
        if ( length(mac_ips[mac]) > 1 ) {
            printf "%s", mac
            for ( ip in mac_ips[mac] ) {
                printf ",%s,%d", ip, mac_ips[mac][ip]
            }
            print ""
        }
    }
}

$ awk -f tst.awk file
00085D20A469,1.2.3.4,3,8.8.8.8,1
64167F801BF5,1.2.3.4,3,8.8.8.8,1
0 голосов
/ 23 апреля 2020

Чтобы свернуть значения, вы можете взять выходные данные своей второй команды, заменить первый , на другой разделитель, например @ с sed, и снова передать вывод в datama sh и свернуть на второе поле (это field2, field3 et c. вместе).

$ datamash -st@ --output-delimiter=, -g1 collapse 2 \
  < <(datamash -st, -g1,2 count 2 < test.csv | sed 's/,/@/')
    0004F2426603,74.214.224.150,3
    00085D20A469,1.2.3.4,3,8.8.8.8,1
    64167F801BF5,1.2.3.4,3,8.8.8.8,1

Если вы хотите удалить первую запись, в которой есть три поля, если я вас правильно понял, вы можете использовать awk и выведите строки с более чем тремя полями:

$ datamash -st@ --output-delimiter=, -g1 collapse 2\
  < <(datamash -st, -g1,2 count 2 < test.csv | sed 's/,/@/') | awk -F, 'NF>3'
    00085D20A469,1.2.3.4,3,8.8.8.8,1
    64167F801BF5,1.2.3.4,3,8.8.8.8,1
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...