2013-10-08 46 views
2

我從一個命令中獲得stdout,我希望以相反的順序去除重複項。僅打印最後一次出現的重複行

也就是說,我希望重複的行從頭開始而不是從結尾開始。例如,從端部帶我可能會使用的經典技術與awk

awk '!a[$0]++' 

雖然輝煌,它去除錯行:

$ 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 

我該怎麼辦呢?在shell中有一個簡單的方法嗎?

+0

注:perl的被髮明出來,以改善用awk + ​​sed的+ 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,不過,我像貝殼做的事情。如果你在bash(4或後續版本),你不那麼在意性能,您可以直接在你的shell執行數組:

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

無需外部工具。 :-)

UPDATE:

靈感(或者挑戰)由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'永遠不是一個好的選擇。 –

+0

@Graham,真的。用更多選項更新答案。 :)請注意,任何*將您的輸入讀入內存中的數組*將在倒置大文件時使用大量內存。 – 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 ** ...榮譽。我試圖弄清楚如何在awk中做到這一點,這使我的大腦受到傷害。 :-) – ghoti