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

264
Da No

Я использую 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?

3
Добро пожаловать в Супер пользователя! Я [отредактировал ваш вопрос] (https://superuser.com/review/suggested-edits/455721) для ясности и актуальности тегов. Обратите внимание, что этот сайт (и [другие нравится) (https://stackexchange.com/sites)) специализируется на задании вопросов и ответах на них; такие вещи, как «спасибо» в сообщениях, не рекомендуется в пользу [upvoting] (https://superuser.com/help/why-vote) и [принятия] (https://superuser.com/help/accepted-answer) полезных ответы. Blacklight Shining 8 лет назад 0
Существует несколько различных реализаций `uniq` - в частности, GNU` uniq` (встречается в большинстве систем на основе Linux) отличается от uniq, найденного на BSD (включая Mac OS X). Пожалуйста [отредактируйте свой вопрос] (https://superuser.com/posts/992668/edit), чтобы указать, о какой реализации `uniq` вы спрашиваете. Blacklight Shining 8 лет назад 0

2 ответа на вопрос

1
blm

With GNU uniq, which supports the -w option:

$ 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 

As pointed out in the comments, that assumes the first field is always seven characters, which it is in your example, but if it's not in real life, I don't think there's a way to do it with uniq (plus if you don't have GNU uniq, even -w won't work), so here's a perl solution:

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

Here's how that works:

$ perl -ne 

Run perl, not printing each line by default, and use the next argument as the script.

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

Split the input line into the stuff before the first colon and the stuff after the first colon, into $1 and $2. split would work here as well.

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

The hash %x is going to be used to uniquify the lines and array @x to keep them in order (you could just use sort keys %x, but that assumes perl's sort will sort in the same way as the input is sorted.) So if we've never seen the current "key" (the stuff before the first colon), initialize a hash entry for the key and push the key on @x. The hash entry for each key is a two-element array containing the count and the first value seen after the colon, so the output can contain that value.

$x{$1}[0]++ 

Increment the count.

END{ 

Start a block that will be run after all the input has been read.

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

Print the count, padded with spaces, a space, the "key", a colon, and the stuff from after the colon.

foreach @x} 

Do that for each key seen, in order and end the END block.

<data 

Read from the file called data in the current directory to get the input. You could also just pipe into perl if you have some other command or pipeline producing the data.

Это приведет к тому, что `uniq` будет рассматривать только первые семь символов __. Это будет работать для примера запрашивающего, но, скорее всего, оно сломается, если первое поле имеет длину не более семи символов. Blacklight Shining 8 лет назад 0
@ BlacklightShining Хороший вопрос. Я добавлю Perl-решение, которое обрабатывает символы до: как поле для uniq, независимо от их длины. blm 8 лет назад 0
uniq: недопустимый параметр - извините за ошибку в операторе `-w` FreeBSD 3.2-RELEASE - не поддерживает` -w` Da No 8 лет назад 0
Да, когда вы добавили, что используете FreeBSD, я подумал, что `-w` не сработает. Я добавил версию Perl, хотя она должна работать где угодно и не полагаться на «ключ», состоящий из 7 символов. blm 8 лет назад 0
0
roaima

I'd use awk. Filter and count the first colon-separated field, when it changes or we hit EOF print the entire previously saved line and count:

awk -F: '!seen[$1]++ { line[$1]=$0; if(prev); prev=$1} END }' data 

The awk script can be expanded out like this:

# 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] } } 

If you can alter the data supplied to awk by adding a trailing blank line you can dispense with the entire END {...} block, simplifying the awk code and removing the duplication:

( cat data; echo ) | awk ... 
Извините, но увидели [: Событие не найдено. Это действительно старый BSD. Я использую bash2. Da No 8 лет назад 0
@ Да, вы использовали одинарные кавычки вокруг выражения `awk`, как показано в одной строке? roaima 8 лет назад 0
да я копирую всю команду и вставляю в терминал. , , Da No 8 лет назад 0