2013-10-08 2 views
2

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

То есть, я хотел бы, чтобы повторяющиеся линии были удалены с самого начала не от конца. Например, чтобы лишить с конца, я мог бы использовать классическую технику с awk:

awk '!a[$0]++' 

While гениального, он удаляет неправильные строки:

$ printf 'one\nfour\ntwo\nthree\nfour\n' | awk '!a[$0]++' 
one 
four 
two 
three 

Я хотел бы последнее вхождение four печати т.е.

$ printf 'one\nfour\ntwo\nthree\nfour\n' | <script> 
one 
two 
three 
four 

Как мне это сделать? Есть ли простой способ с одним слоем в оболочке?

+0

NB: Perl был изобретен, чтобы улучшить, используя AWK + СЭД + shell + ... – Dogweather

ответ

5

Используя ваш пример для создания входа для тестирования:

printf 'one\nfour\ntwo\nthree\nfour\n' 

Самый простой способ справиться с этим просто отменить данные, дважды. Следующие работы в BSD и OS X:

command | tail -r | awk '!a[$0]++' | tail -r 

Но вариант -r не является универсальным. Если вы на Linux, вы можете создать такой же эффект с tac команды (напротив cat), который является частью Coreutils:

command | tac | awk '!a[$0]++' | tac 

Если ни одна из этих работ (то есть вы на HP/UX или старше Solaris и т.д.), вы можете быть в состоянии полностью изменить вещи, используя sed:

command | sed '1!G;h;$!d' | awk '!a[$0]++' | sed '1!G;h;$!d' 

конечно, вы могли бы сделать это с Perl, а также:

command | perl -e 'print reverse <>' | awk '!a[$0]++' | perl -e 'print reverse <>' 

Но если Perl доступен на вашей системе, вы можете также упростить трубу и пропустить AWK полностью:

command | perl -e '$a{$_}++ or print for reverse <>' 

Я никогда не любил Perl, хотя, и я делать как делать вещи в скорлупе. Если вы находитесь в Баше (версии 4 или выше), и вы не заботитесь о производительности, вы можете реализовать массив прямо в оболочке:

mapfile -t a < <(command) 
declare -A b; 
for ((i=${#a[@]}-1 ; i>=0; i--)); do ((b[${a[$i]}]++)) || echo "${a[$i]}"; done 

нет внешних инструментов, необходимых. :-)

UPDATE:

Inspired (или, возможно, вызов) от sudo_O's answer, вот еще один вариант, который работает в чистом AWK на BSD (т.е. не требует GNU AWK):

command | awk '{a[NR]=$0;b[$0]=NR} END {for(i=1;i<=NR;i++) if(i==b[a[i]]) print a[i]}' 

Обратите внимание, что это сохраняет все входные данные в памяти дважды, поэтому может быть неуместным для больших наборов данных.

+0

Это похоже на много труб. – Graham

+0

+1 для оригинального ответа 'tail' и' rev'. 'sed | awk | sed' и 'perl | awk | perl' никогда не является хорошим вариантом IMHO. –

+0

@Graham, True. Обновленный ответ с дополнительными опциями. :) Обратите внимание, что все, что * читает ваш вход в массив в памяти *, будет использовать большую память при обращении больших файлов. – ghoti

2

На практике я бы использовал ghoti техники (rev) но вот один GNU awk сценария для печати последних вхождений:

command | awk '{a[$0]=NR;b[NR]=$0}END{n=asort(a);for(i=1;i<=n;i++)print b[a[i]]}' 
one 
two 
three 
four 
+1

** + 1 ** ... Kudos. Я пытался выяснить, как это сделать в awk, и это заставило мой мозг больно. :-) – ghoti

Смежные вопросы