Как сделать так, чтобы uniq рассматривал только первое поле?

Я использую FreeBSD 3.2-RELEASE

Если у меня есть какой-то отсортированный текст, как это last выход-

zikla13:Oct:20:22:34
zikla13:Oct:5:00:31
zikla14:Oct:17:22:01
zikla14:Oct:12:23:35
zikla14:Oct:12:23:34
zikla14:Oct:12:00:11
zikla14:Oct:11:23:52
zikla14:Oct:5:22:22
zilka13:Oct:13:23:48
zilka13:Oct:11:00:28
zilka13:Oct:9:22:40

Есть ли способ получить uniq -c рассмотреть только первое поле (может быть, с -s)? В этом случае вывод должен быть таким:

2 zikla13:Oct:20:22:34
6 zikla14:Oct:17:22:01
3 zilka13:Oct:13:23:48

Или каким-то другим способом, используя awk?

2 ответа

Решение

С GNU uniq, который поддерживает -w опция:

$ cat data
zikla13:Oct:20:22:34
zikla13:Oct:5:00:31
zikla14:Oct:17:22:01
zikla14:Oct:12:23:35
zikla14:Oct:12:23:34
zikla14:Oct:12:00:11
zikla14:Oct:11:23:52
zikla14:Oct:5:22:22
zilka13:Oct:13:23:48
zilka13:Oct:11:00:28
zilka13:Oct:9:22:40
$ uniq -c -w7 data
  2 zikla13:Oct:20:22:34
  6 zikla14:Oct:17:22:01
  3 zilka13:Oct:13:23:48

Как отмечено в комментариях, предполагается, что первое поле всегда состоит из семи символов, что в вашем примере, но если это не в реальной жизни, я не думаю, что есть способ сделать это с помощью Uniq (плюс, если вы не не имеет GNU Uniq, даже -w не будет работать), так что вот решение Perl:

$ perl -ne '/(.*?):(.*)/;unless (exists $x{$1}){$x{$1}=[0,$2];push @x, $1};$x{$1}[0]++;END{printf("%8d %s:%s\n",$x{$_}[0],$_,$x{$_}[1]) foreach @x}' <data
   2 zikla13:Oct:20:22:34
   6 zikla14:Oct:17:22:01
   3 zilka13:Oct:13:23:48

Вот как это работает:

$ perl -ne

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

/(.*?):(.*)/

Разбить строку ввода на материал перед первым двоеточием и материал после первого двоеточия в $1 а также $2, split будет работать здесь также.

unless (exists $x{$1}){$x{$1}=[0,$2];push @x, $1}

Хеш %x будет использоваться для унификации строк и массива @x держать их в порядке (вы могли бы просто использовать sort keys %x, но это предполагает Perl sort будет сортироваться так же, как сортируются входные данные.) Поэтому, если мы никогда не видели текущий "ключ" (материал перед первым двоеточием), инициализируйте запись хеша для ключа и нажмите клавишу на @x, Запись хеша для каждого ключа представляет собой двухэлементный массив, содержащий количество и первое значение, увиденное после двоеточия, поэтому выходные данные могут содержать это значение.

$x{$1}[0]++

Увеличьте счет.

END{

Начать блок, который будет запущен после прочтения всех вводимых данных.

printf("%8d %s:%s\n",$x{$_}[0],$_,$x{$_}[1])

Напечатайте счетчик, дополненный пробелами, пробелом, "ключом", двоеточием и прочим после двоеточия.

foreach @x}

Сделайте это для каждого увиденного ключа по порядку и завершите блок END.

<data

Прочитайте из файла с именем data в текущем каталоге, чтобы получить ввод. Вы также можете просто передать в perl, если у вас есть другая команда или конвейер, производящий данные.

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

awk -F: '!seen[$1]++ { line[$1]=$0; if(prev){printf "%d\t%s\n",seen[prev],line[prev]}; prev=$1} END {if(prev){printf "%d\t%s\n",seen[prev],line[prev]}}' data

awk Скрипт можно развернуть так:

# Count the occurrences of the first field. If first time then...
!seen[$1]++ {
    # save the line
    line[$1]=$0;
    # maybe print the previous line
    if (prev) {
        printf "%d\t%s\n", seen[prev], line[prev]
    };
    prev=$1
}

# End of file, so print any previous line we have got saved
END {
    if (prev) {
        printf "%d\t%s\n", seen[prev], line[prev]
    }
}

Если вы можете изменить данные, предоставленные в awk, добавив завершающую пустую строку, вы можете обойтись без всего END {...} блок, упрощая awk код и удаление дублирования:

( cat data; echo ) | awk ...
Другие вопросы по тегам