Selezionare valori univoci o distinti da un elenco nello script della shell UNIX


238

Ho uno script ksh che restituisce un lungo elenco di valori, newline separati, e voglio vedere solo i valori univoci / distinti. È possibile farlo?

Ad esempio, supponiamo che il mio output sia suffissi di file in una directory:

tar
gz
java
gz
java
tar
class
class

Voglio vedere un elenco come:

tar
gz
java
class

Risposte:


432

Potresti voler guardare il uniqesort applicazioni .

./yourscript.ksh | ordina | uniq

(Cordiali saluti, sì, l'ordinamento è necessario in questa riga di comando, uniq rimuove solo le righe duplicate che si trovano immediatamente una dopo l'altra)

MODIFICARE:

Contrariamente a quanto pubblicato da Aaron Digulla in relazione auniq opzioni della riga di comando:

Dato il seguente input:

classe
vaso
vaso
vaso
bidone
bidone
Giava

uniq produrrà tutte le righe esattamente una volta:

classe
vaso
bidone
Giava

uniq -d produrrà tutte le righe che appaiono più di una volta e le stamperà una volta:

vaso
bidone

uniq -u produrrà tutte le righe che appaiono esattamente una volta e le stamperà una volta:

classe
Giava

2
Solo un FYI per ritardatari: la risposta di @ AaronDigulla da allora è stata corretta.
mklement0

2
ottimo punto questo `ordinamento è necessario in questa riga di comando, uniq elimina solo le linee duplicate che sono immediatamente una dopo l'altra` che ho appena imparato !!
HattrickNZ,

4
GNU sortpresenta una -uversione per fornire anche valori univoci.
Arthur2e5,

Ho capito che le uniqcuciture per elaborare solo linee adiacenti (almeno per impostazione predefinita), il che significa che si può sortinserire prima dell'alimentazione uniq.
Stphane,

85
./script.sh | sort -u

È la stessa della risposta del monossido , ma un po 'più concisa.


6
Ti stai comportando modesto: la soluzione sarà anche svolgere meglio (probabilmente solo evidente con grandi insiemi di dati).
mklement0

Penso che dovrebbe essere più efficiente di ... | sort | uniqperché viene eseguito in un colpo solo
Adrian Antunez il

10

Per set di dati più grandi in cui l'ordinamento potrebbe non essere desiderabile, è anche possibile utilizzare il seguente script perl:

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

Questo in sostanza ricorda solo ogni output di linea in modo che non lo ritorni più.

Ha il vantaggio rispetto alla " sort | uniq" soluzione in quanto non è necessario un ordinamento anticipato.


2
Si noti che l'ordinamento di un file molto grande non è di per sé un problema con l'ordinamento; può ordinare file più grandi della RAM + swap disponibile. Perl, OTOH, fallirà se ci sono solo pochi duplicati.
Aaron Digulla,

1
Sì, è un compromesso a seconda dei dati previsti. Perl è migliore per enormi set di dati con molti duplicati (non è richiesta alcuna memoria basata su disco). Enorme set di dati con pochi duplicati dovrebbe utilizzare l'ordinamento (e l'archiviazione su disco). È possibile utilizzare anche set di dati di piccole dimensioni. Personalmente, proverei prima Perl, passare all'ordinamento se fallisce.
paxdiablo,

Poiché l'ordinamento ti dà un vantaggio solo se deve passare al disco.
paxdiablo,

5
Questo è fantastico quando voglio la prima occorrenza di ogni riga. L'ordinamento lo spezzerebbe.
Bluu,

10

Con zsh puoi farlo:

% 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

Oppure puoi usare AWK:

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

2
Soluzioni intelligenti che non comportano l'ordinamento dell'input. Avvertenze: la soluzione molto intelligente ma criptica awk(vedi stackoverflow.com/a/21200722/45375 per una spiegazione) funzionerà con file di grandi dimensioni purché il numero di linee univoche sia abbastanza piccolo (poiché le linee univoche vengono mantenute in memoria ). La zshsoluzione legge prima l'intero file in memoria, che potrebbe non essere un'opzione con file di grandi dimensioni. Inoltre, come scritto, solo le linee senza spazi incorporati vengono gestite correttamente; per risolvere questo problema, utilizzare IFS=$'\n' read -d '' -r -A u <file; print -l ${(u)u}invece.
mklement0

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

1
Grazie, è più semplice (supponendo che non sia necessario impostare le variabili necessarie al di fuori della subshell). Sono curioso di sapere quando è necessario il [@]suffisso per fare riferimento a tutti gli elementi di un array - sembra che - almeno a partire dalla versione 5 - funziona senza di esso; o l'hai appena aggiunto per chiarezza?
mklement0

1
@ mklement0, hai ragione! Non ci ho pensato quando ho scritto il post. In realtà, questo dovrebbe essere sufficiente:print -l "${(fu)$(<infile)}"
Dimitre Radoulov,

1
Fantastico, grazie per aver aggiornato il tuo post - Mi sono preso la libertà di correggere anche l' awkoutput del campione.
mklement0

9

Instradali sorte uniq. Questo rimuove tutti i duplicati.

uniq -ddà solo i duplicati, uniq -udà solo quelli unici (rimuove i duplicati).


Devo ordinare prima per aspetto
Brabster

1
Si. O più precisamente, è necessario raggruppare tutte le righe duplicate. L'ordinamento lo fa per definizione però;)
Matthew Scharley

Inoltre, uniq -uNON è il comportamento predefinito (vedere la modifica nella mia risposta per i dettagli)
Matthew Scharley

7

Con AWK puoi farlo, lo trovo più veloce dell'ordinamento

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

Questo è sicuramente il mio modo preferito di fare il lavoro, grazie mille! Soprattutto per file più grandi, le soluzioni sort | uniq probabilmente non sono quelle che desideri.
Schmitzi,

1

Unico, come richiesto, (ma non ordinato);
utilizza meno risorse di sistema per meno di ~ 70 elementi (come testato nel tempo);
scritto per ricevere input da stdin,
(o modificare e includere in un altro script):
(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

Ottengo suggerimenti migliori per ottenere voci non duplicate in un file

awk '$0 != x ":FOO" && NR>1 {print x} {x=$0} END {print}' file_name | uniq -f1 -u
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.