Wählen Sie eindeutige oder unterschiedliche Werte aus einer Liste im UNIX-Shell-Skript aus


238

Ich habe ein ksh-Skript, das eine lange Liste von Werten zurückgibt, Newline getrennt, und ich möchte nur die eindeutigen / eindeutigen Werte sehen. Ist das möglich?

Angenommen, meine Ausgabe besteht aus Dateisuffixen in einem Verzeichnis:

tar
gz
java
gz
java
tar
class
class

Ich möchte eine Liste sehen wie:

tar
gz
java
class

Antworten:


431

Vielleicht möchten Sie sich die uniqund sortAnwendungen ansehen .

./IhrSkript.ksh | sortieren | uniq

(FYI, ja, die Sortierung ist in dieser Befehlszeile erforderlich, entfernt uniqnur doppelte Zeilen, die unmittelbar nacheinander liegen)

BEARBEITEN:

Im Gegensatz zu dem, was Aaron Digulla in Bezug auf uniqdie Befehlszeilenoptionen gepostet hat :

Angesichts der folgenden Eingabe:

Klasse
Krug
Krug
Krug
Behälter
Behälter
Java

uniq gibt alle Zeilen genau einmal aus:

Klasse
Krug
Behälter
Java

uniq -d gibt alle Zeilen aus, die mehr als einmal erscheinen, und druckt sie einmal:

Krug
Behälter

uniq -u gibt alle Zeilen aus, die genau einmal angezeigt werden, und druckt sie einmal aus:

Klasse
Java

2
Nur zu Ihrer Information für Nachzügler: @ AaronDigullas Antwort wurde inzwischen korrigiert.
mklement0

2
Sehr guter Punkt, diese `Sortierung ist in dieser Befehlszeile notwendig, Uniq entfernt nur doppelte Zeilen, die unmittelbar nacheinander liegen`, was ich gerade gelernt habe !!
HattrickNZ

4
GNU sortbietet eine -uVersion, mit der auch die eindeutigen Werte angegeben werden können.
Arthur2e5

Ich habe herausgefunden, dass uniqNähte nur benachbarte Linien verarbeiten (zumindest standardmäßig), was bedeutet, dass man sie sortvor dem Zuführen eingeben kann uniq.
Stphane

85
./script.sh | sort -u

Dies entspricht der Antwort von Monoxid , ist jedoch etwas prägnanter.


6
Sie sind bescheiden: Ihre Lösung wird auch eine bessere Leistung erbringen (wahrscheinlich nur bei großen Datenmengen erkennbar).
mklement0

Ich denke, das sollte effizienter sein, als ... | sort | uniqweil es in einem Schuss ausgeführt wird
Adrian Antunez

10

Für größere Datenmengen, bei denen eine Sortierung möglicherweise nicht wünschenswert ist, können Sie auch das folgende Perl-Skript verwenden:

./yourscript.ksh | perl -ne 'if (!defined $x{$_}) { print $_; $x{$_} = 1; }'

Dies speichert im Grunde nur jede Zeilenausgabe, so dass sie nicht erneut ausgegeben wird.

Es hat den Vorteil gegenüber der " sort | uniq" Lösung, dass im Voraus keine Sortierung erforderlich ist.


2
Beachten Sie, dass das Sortieren einer sehr großen Datei per se kein Problem beim Sortieren darstellt. Es kann Dateien sortieren, die größer als der verfügbare RAM + Swap sind. Perl, OTOH, schlägt fehl, wenn nur wenige Duplikate vorhanden sind.
Aaron Digulla

1
Ja, es ist ein Kompromiss, der von den erwarteten Daten abhängt. Perl ist besser für große Datenmengen mit vielen Duplikaten (kein festplattenbasierter Speicher erforderlich). Ein riesiger Datensatz mit wenigen Duplikaten sollte sort (und Festplattenspeicher) verwenden. Kleine Datensätze können beide verwenden. Persönlich würde ich zuerst Perl ausprobieren und zum Sortieren wechseln, wenn es fehlschlägt.
Paxdiablo

Da sort nur dann einen Vorteil bietet, wenn es auf die Festplatte wechseln muss.
Paxdiablo

5
Dies ist großartig, wenn ich das erste Auftreten jeder Zeile möchte. Sortieren würde das brechen.
Bluu

10

Mit zsh können Sie dies tun:

% cat infile 
tar
more than one word
gz
java
gz
java
tar
class
class
zsh-5.0.0[t]% print -l "${(fu)$(<infile)}"
tar
more than one word
gz
java
class

Oder Sie können AWK verwenden:

% awk '!_[$0]++' infile    
tar
more than one word
gz
java
class

2
Clevere Lösungen, bei denen die Eingabe nicht sortiert wird. Vorsichtsmaßnahmen: Die sehr clevere, aber kryptische awkLösung ( eine Erklärung finden Sie unter stackoverflow.com/a/21200722/45375 ) funktioniert mit großen Dateien, solange die Anzahl der eindeutigen Zeilen klein genug ist (da eindeutige Zeilen im Speicher bleiben) ). Die zshLösung liest zuerst die gesamte Datei in den Speicher, was bei großen Dateien möglicherweise nicht möglich ist. Außerdem werden, wie geschrieben, nur Zeilen ohne eingebettete Leerzeichen korrekt behandelt. Um dies zu beheben, verwenden Sie IFS=$'\n' read -d '' -r -A u <file; print -l ${(u)u}stattdessen.
mklement0

Richtig. Oder:(IFS=$'\n' u=($(<infile)); print -l "${(u)u[@]}")
Dimitre Radoulov

1
Vielen Dank, das ist einfacher (vorausgesetzt, Sie müssen keine Variablen festlegen, die außerhalb der Subshell benötigt werden). Ich bin gespannt, wann Sie das [@]Suffix benötigen, um auf alle Elemente eines Arrays zu verweisen. Es scheint, dass es - zumindest ab Version 5 - ohne dieses Suffix funktioniert. oder hast du es nur aus Gründen der Klarheit hinzugefügt?
mklement0

1
@ mklement0, du hast recht! Ich habe nicht daran gedacht, als ich den Beitrag schrieb. Eigentlich sollte dies ausreichen:print -l "${(fu)$(<infile)}"
Dimitre Radoulov

1
Fantastisch, danke für die Aktualisierung Ihres Beitrags - ich habe mir erlaubt, auch die awkBeispielausgabe zu korrigieren.
mklement0

9

Pipe sie durch sortund uniq. Dadurch werden alle Duplikate entfernt.

uniq -dgibt nur die Duplikate, uniq -ugibt nur die eindeutigen (Streifen Duplikate).


Ich

1
Ja, das tust du. Oder genauer gesagt, Sie müssen alle doppelten Zeilen zusammenfassen. Das Sortieren tut dies jedoch per Definition;)
Matthew Scharley

Ebenfalls, uniq -u ist NICHT das Standardverhalten (siehe die Bearbeitung in meiner Antwort für Details)
Matthew Scharley

7

Mit AWK können Sie es tun, ich finde es schneller als sortieren

 ./yourscript.ksh | awk '!a[$0]++'

Das ist definitiv meine Lieblingsmethode, vielen Dank! Insbesondere für größere Dateien sind die sort | uniq-Lösungen wahrscheinlich nicht das, was Sie wollen.
Schmitzi

1

Einzigartig, wie gewünscht (aber nicht sortiert);
verwendet weniger Systemressourcen für weniger als ~ 70 Elemente (wie mit der Zeit getestet);
geschrieben, um Eingaben von stdin zu übernehmen
(oder zu ändern und in ein anderes Skript aufzunehmen):
(Bash)

bag2set () {
    # Reduce a_bag to a_set.
    local -i i j n=${#a_bag[@]}
    for ((i=0; i < n; i++)); do
        if [[ -n ${a_bag[i]} ]]; then
            a_set[i]=${a_bag[i]}
            a_bag[i]=$'\0'
            for ((j=i+1; j < n; j++)); do
                [[ ${a_set[i]} == ${a_bag[j]} ]] && a_bag[j]=$'\0'
            done
        fi
    done
}
declare -a a_bag=() a_set=()
stdin="$(</dev/stdin)"
declare -i i=0
for e in $stdin; do
    a_bag[i]=$e
    i=$i+1
done
bag2set
echo "${a_set[@]}"

0

Ich bekomme bessere Tipps, um nicht doppelte Einträge in einer Datei zu erhalten

awk '$0 != x ":FOO" && NR>1 {print x} {x=$0} END {print}' file_name | uniq -f1 -u
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.