Seleccione valores únicos o distintos de una lista en el script de shell de UNIX

238

Tengo un script ksh que devuelve una larga lista de valores, separados por una nueva línea, y quiero ver solo los valores únicos / distintos. ¿Es posible hacer esto?

Por ejemplo, supongamos que mi salida son sufijos de archivo en un directorio:

tar
gz
java
gz
java
tar
class
class

Quiero ver una lista como:

tar
gz
java
class
brabster
fuente

Respuestas:

432

Es posible que desee ver las aplicaciones uniqy sort.

./yourscript.ksh | ordenar | uniq

(Para su información, sí, el orden es necesario en esta línea de comando, uniqsolo elimina las líneas duplicadas que están inmediatamente una detrás de la otra)

EDITAR:

Contrariamente a lo publicado por Aaron Digulla en relación con uniqlas opciones de la línea de comandos:

Dada la siguiente entrada:

clase
tarro
tarro
tarro
compartimiento
compartimiento
Java

uniq generará todas las líneas exactamente una vez:

clase
tarro
compartimiento
Java

uniq -d mostrará todas las líneas que aparecen más de una vez y las imprimirá una vez:

tarro
compartimiento

uniq -u generará todas las líneas que aparecen exactamente una vez, y las imprimirá una vez:

clase
Java
Matthew Scharley
fuente
2
Solo un FYI para los recién llegados: la respuesta de @ AaronDigulla ha sido corregida.
mklement0
2
Muy buen punto, este `ordenar es necesario en esta línea de comando, ¡uniq solo elimina las líneas duplicadas que están inmediatamente una detrás de la otra` que acabo de aprender!
HattrickNZ
44
GNU sortpresenta una -uversión para dar valores únicos también.
Arthur2e5
Descubrí que las uniqcosturas solo procesan líneas adyacentes (al menos por defecto), lo que significa que se puede sortingresar antes de alimentar uniq.
Stphane
85
./script.sh | sort -u

Esto es lo mismo que la respuesta de monóxido , pero un poco más conciso.

gpojd
fuente
66
Estás siendo modesto: tu solución también funcionará mejor (probablemente solo se note con grandes conjuntos de datos).
mklement0
Creo que debería ser más eficiente que ... | sort | uniqporque se realiza de una sola vez
Adrian Antunez
10

Para conjuntos de datos más grandes donde la ordenación puede no ser deseable, también puede usar el siguiente script perl:

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

Básicamente, esto solo recuerda cada salida de línea para que no vuelva a salir.

Tiene la ventaja sobre la " sort | uniq" solución en que no se requiere clasificación por adelantado.

paxdiablo
fuente
2
Tenga en cuenta que la ordenación de un archivo muy grande no es un problema per se con sort; puede ordenar archivos que son más grandes que el intercambio RAM + disponible. Perl, OTOH, fallará si solo hay unos pocos duplicados.
Aaron Digulla
1
Sí, es una compensación dependiendo de los datos esperados. Perl es mejor para un gran conjunto de datos con muchos duplicados (no se requiere almacenamiento basado en disco). El conjunto de datos enorme con pocos duplicados debe usar sort (y almacenamiento en disco). Pequeños conjuntos de datos pueden usar cualquiera. Personalmente, primero probaría Perl, cambiaría para ordenar si falla.
paxdiablo
Dado que la ordenación solo le da un beneficio si tiene que cambiar al disco.
paxdiablo
55
Esto es genial cuando quiero la primera aparición de cada línea. La clasificación rompería eso.
Bluu
10

Con zsh puedes hacer esto:

% 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

O puedes usar AWK:

% awk '!_[$0]++' infile    
tar
more than one word
gz
java
class
Dimitre Radoulov
fuente
2
Soluciones inteligentes que no implican ordenar la entrada. Advertencias: la awksolución muy inteligente pero críptica (consulte stackoverflow.com/a/21200722/45375 para obtener una explicación) funcionará con archivos grandes siempre que el número de líneas únicas sea lo suficientemente pequeño (ya que las líneas únicas se mantienen en la memoria ) La zshsolución lee primero todo el archivo en la memoria, lo que puede no ser una opción con archivos grandes. Además, como está escrito, solo las líneas sin espacios incrustados se manejan correctamente; para arreglar esto, use IFS=$'\n' read -d '' -r -A u <file; print -l ${(u)u}en su lugar.
mklement0
Correcto. O:(IFS=$'\n' u=($(<infile)); print -l "${(u)u[@]}")
Dimitre Radoulov
1
Gracias, eso es más simple (suponiendo que no necesite establecer variables necesarias fuera de la subshell). Tengo curiosidad por saber cuándo necesita el [@]sufijo para hacer referencia a todos los elementos de una matriz, parece que, al menos a partir de la versión 5, funciona sin él; o simplemente lo agregaste para mayor claridad?
mklement0
1
@ mklement0, tienes razón! No lo pensé cuando escribí la publicación. En realidad, esto debería ser suficiente:print -l "${(fu)$(<infile)}"
Dimitre Radoulov
1
Fantástico, gracias por actualizar tu publicación. También me tomé la libertad de arreglar la awksalida de muestra.
mklement0
9

Conducirlos a través de sorty uniq. Esto elimina todos los duplicados.

uniq -dda solo los duplicados, uniq -uda solo los únicos (tiras duplicadas).

Aaron Digulla
fuente
tengo
1
Si tu puedes. O más exactamente, debe agrupar todas las líneas duplicadas. Sin embargo, la clasificación lo hace por definición;)
Matthew Scharley
Además, uniq -uNO es el comportamiento predeterminado (ver la edición en mi respuesta para más detalles)
Matthew Scharley
7

Con AWK puedes hacerlo, lo encuentro más rápido que ordenar

 ./yourscript.ksh | awk '!a[$0]++'
Ajak6
fuente
Esa es definitivamente mi forma favorita de hacer el trabajo, ¡muchas gracias! Especialmente para archivos más grandes, las soluciones sort | uniq probablemente no sean lo que desea.
Schmitzi
1

Único, según lo solicitado, (pero no ordenado);
utiliza menos recursos del sistema para menos de ~ 70 elementos (según lo probado con el tiempo);
escrito para recibir información de stdin,
(o modificar e incluir en otro 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[@]}"
FGrose
fuente
0

Recibo mejores consejos para obtener entradas no duplicadas en un archivo

awk '$0 != x ":FOO" && NR>1 {print x} {x=$0} END {print}' file_name | uniq -f1 -u
Mary Marty
fuente