2016-04-13 25 views
1

Mein Bash-Foo ist jetzt ein wenig eingerostet, also wollte ich sehen, ob es eine clevere Möglichkeit gibt, teilweise Duplikate aus einer Datei zu entfernen. Ich habe eine Reihe von Dateien Tausende von Zeilen mit folgendem Format enthalten:Entfernen Sie teilweise Duplikate aus der Textdatei

String1|String2|String3|String4|String5|String6|...|String22|09-Apr-2016 05:28:03|x 

Im Wesentlichen ist es ein Bündel von Rohr begrenzten Zeichenfolge, wobei die letzten beiden Spalten sind ein Zeitstempel und x. Was ich tun möchte, ist, alle meine Dateien zu verketten und dann alle Teilduplikate zu entfernen. Ich definiere partielles Duplikat als eine Zeile in der Datei, die von String1 bis String22 übereinstimmt, aber der Zeitstempel kann unterschiedlich sein.

Zum Beispiel wird eine Datei mit:

String1|String2|String3|String4|String5|String6|...|String22|09-Apr-2016 05:28:03|x 
String1|String2|String3|String4|String5|String6|...|String22|09-Apr-2016 12:12:12|x 
String124|String2|String3|String4|String5|String6|...|String22|09-Apr-2016 05:28:03|x 

würde:

String1|String2|String3|String4|String5|String6|...|String22|09-Apr-2016 05:28:03|x 
String124|String2|String3|String4|String5|String6|...|String22|09-Apr-2016 05:28:03|x 

(Es spielt keine Rolle, welche Zeitstempel gewählt wird).

Irgendwelche Ideen?

Antwort

3

Mit awk können Sie dies tun:

awk '{k=$0; gsub(/(\|[^|]*){2}$/, "", k)} !seen[k]++' file 

String1|String2|String3|String4|String5|String6|String7|09-Apr-2016 05:28:03|x 
String124|String2|String3|String4|String5|String6|String7|09-Apr-2016 05:28:03|x 

awk Befehl macht zunächst eine Variable k durch aus jeder Zeile letzten 2 Felder zu entfernen. Dann verwendet es ein assoziatives Array seen mit Schlüssel wie k, wo es nur die erste Instanz des Schlüssels druckt, indem jeder Prozessschlüssel im Array gespeichert wird.

0

Wenn Sie Bash Version 4, die assoziative Arrays unterstützt, kann es ziemlich effizient in reiner Bash erfolgen:

declare -A found 
while IFS= read -r line || [[ -n $line ]] ; do 
    strings=${line%|*|*} 
    if ((! ${found[$strings]-0})) ; then 
     printf '%s\n' "$line" 
     found[$strings]=1 
    fi 
done < "$file" 
+1

Ersetzen Sie "ziemlich effizient" durch "sehr ineffizient" - dies würde für eine relativ große Datei eine Größenordnung langsamer als ein äquivalentes awk-Skript sein. Siehe [why-is-using-a-shell-loop-to-process-text-built-bad-practice] (http://unix.stackexchange.com/questions/169716/why-is-using-a-shell) -loop-to-process-text-betrachtet-schlechte-Praxis). –

0

gleiche Idee mit @anubhava, aber ich denke, mehr idiomatische

$ awk -F'|' '{line=$0;$NF=$(NF-1)=""} !a[$0]++{print line}' file 

String1|String2|String3|String4|String5|String6|...|String22|09-Apr-2016 05:28:03|x 
String124|String2|String3|String4|String5|String6|...|String22|09-Apr-2016 05:28:03|x 
+1

Das ist weniger idiomatisch (der idiomatische Array-Name ist 'seen []' und Sie möchten nicht eine Liste von Feldern, die auf Null gesetzt werden wollen, fest codieren) und ist zerbrechlich, weil es alle '|' s in '$ 0' ersetzt mit leeren Zeichen, also könntest du 'a | bc' nicht mehr von' ab | c' unterscheiden - sie würden beide 'abc' werden, wenn sie in' a [$ 0] ++ 'verwendet werden. –

Verwandte Themen