GNU/Linux >> Tutoriales Linux >  >> Linux

¿Cómo puedo obtener valores únicos de una matriz en Bash?

Me doy cuenta de que esto ya fue respondido, pero apareció bastante alto en los resultados de búsqueda y podría ayudar a alguien.

printf "%s\n" "${IDS[@]}" | sort -u

Ejemplo:

~> IDS=( "aa" "ab" "aa" "ac" "aa" "ad" )
~> echo  "${IDS[@]}"
aa ab aa ac aa ad
~>
~> printf "%s\n" "${IDS[@]}" | sort -u
aa
ab
ac
ad
~> UNIQ_IDS=($(printf "%s\n" "${IDS[@]}" | sort -u))
~> echo "${UNIQ_IDS[@]}"
aa ab ac ad
~>

Si los elementos de su matriz tienen espacios en blanco o cualquier otro carácter especial de shell (¿y puede estar seguro de que no lo tienen?), entonces, para capturarlos en primer lugar (y siempre debe hacer esto), ¡exprese su matriz entre comillas dobles! p.ej. "${a[@]}" . Bash literalmente interpretará esto como "cada elemento de la matriz en un argumento separado ". Dentro de bash esto simplemente siempre funciona, siempre.

Luego, para obtener una matriz ordenada (y única), tenemos que convertirla a un formato que comprenda la clasificación y poder convertirla nuevamente en elementos de matriz bash. Esto es lo mejor que se me ocurrió:

eval a=($(printf "%q\n" "${a[@]}" | sort -u))

Desafortunadamente, esto falla en el caso especial de la matriz vacía, convirtiendo la matriz vacía en una matriz de 1 elemento vacío (porque printf tenía 0 argumentos pero aún se imprime como si tuviera un argumento vacío; consulte la explicación). Así que tienes que atrapar eso en un si o algo así.

Explicación:¡El formato %q para printf "shell escapa" del argumento impreso, de tal manera que bash puede recuperarse en algo como eval! Debido a que cada elemento se imprime shell escapado en su propia línea, el único separador entre elementos es la nueva línea y la asignación de matriz toma cada línea como un elemento, analizando los valores escapados en texto literal.

por ejemplo

> a=("foo bar" baz)
> printf "%q\n" "${a[@]}"
'foo bar'
baz
> printf "%q\n"
''

La evaluación es necesaria para eliminar el escape de cada valor que regresa a la matriz.


Si está ejecutando Bash versión 4 o superior (que debería ser el caso en cualquier versión moderna de Linux), puede obtener valores de matriz únicos en bash creando una nueva matriz asociativa que contenga cada uno de los valores de la matriz original. Algo como esto:

$ a=(aa ac aa ad "ac ad")
$ declare -A b
$ for i in "${a[@]}"; do b["$i"]=1; done
$ printf '%s\n' "${!b[@]}"
ac ad
ac
aa
ad

Esto funciona porque en cualquier matriz (asociativa o tradicional, en cualquier idioma), cada clave solo puede aparecer una vez. Cuando el for el bucle llega al segundo valor de aa en a[2] , sobrescribe b[aa] que se configuró originalmente para a[0] .

Hacer cosas en bash nativo puede ser más rápido que usar canalizaciones y herramientas externas como sort y uniq , aunque para conjuntos de datos más grandes probablemente verá un mejor rendimiento si usa un lenguaje más potente como awk, python, etc.

Si te sientes seguro, puedes evitar el for bucle usando printf la capacidad de reciclar su formato para múltiples argumentos, aunque esto parece requerir eval . (Deja de leer ahora si estás de acuerdo con eso).

$ eval b=( $(printf ' ["%s"]=1' "${a[@]}") )
$ declare -p b
declare -A b=(["ac ad"]="1" [ac]="1" [aa]="1" [ad]="1" )

La razón por la que esta solución requiere eval es que los valores de la matriz se determinan antes de la división de palabras. Eso significa que la salida de la sustitución del comando se considera una sola palabra en lugar de un conjunto de pares clave=valor.

Si bien esto usa una subcapa, solo usa bash incorporados para procesar los valores de la matriz. Asegúrese de evaluar su uso de eval con ojo critico. Si no está 100 % seguro de que Chepner, Glenn Jackman o Greycat no encontrarán fallas en su código, use el ciclo for en su lugar.


Un poco raro, pero esto debería funcionar:

echo "${ids[@]}" | tr ' ' '\n' | sort -u | tr '\n' ' '

Para guardar los resultados únicos ordenados en una matriz, realice la asignación de matriz:

sorted_unique_ids=($(echo "${ids[@]}" | tr ' ' '\n' | sort -u | tr '\n' ' '))

Si su shell admite herestrings (bash debería), puede ahorrar un echo proceso modificándolo a:

tr ' ' '\n' <<< "${ids[@]}" | sort -u | tr '\n' ' '

Una nota del 28 de agosto de 2021:

Según ShellCheck wiki 2207 a read -a se debe usar la tubería para evitar la división. Por lo tanto, en bash el comando sería:

IFS=" " read -r -a ids <<< "$(echo "${ids[@]}" | tr ' ' '\n' | sort -u | tr '\n' ' ')"

o

IFS=" " read -r -a ids <<< "$(tr ' ' '\n' <<< "${ids[@]}" | sort -u | tr '\n' ' ')"

Entrada:

ids=(aa ab aa ac aa ad)

Salida:

aa ab ac ad

Explicación:

  • "${ids[@]}" - Sintaxis para trabajar con arreglos de shell, ya sea que se usen como parte de echo o una herestring. El @ parte significa "todos los elementos de la matriz"
  • tr ' ' '\n' - Convierte todos los espacios en nuevas líneas. Debido a que Shell ve su matriz como elementos en una sola línea, separados por espacios; y porque sort espera que la entrada esté en líneas separadas.
  • sort -u - ordenar y retener solo elementos únicos
  • tr '\n' ' ' - convertir las líneas nuevas que agregamos anteriormente a espacios.
  • $(...) - Sustitución de comandos
  • Aparte:tr ' ' '\n' <<< "${ids[@]}" es una forma más eficiente de hacerlo:echo "${ids[@]}" | tr ' ' '\n'

Linux
  1. ¿Cómo crear una matriz de elementos únicos a partir de una cadena/matriz en Bash?

  2. Obtener todos los archivos excepto los archivos en matriz:¿Bash?

  3. ¿Cómo verificar si Bash puede imprimir colores?

  4. Cómo declarar una matriz 2D en bash

  5. ¿Cómo puedo obtener información del contenedor de Docker Linux desde el propio contenedor?

Bash break:cómo salir de un bucle

¿Cómo puedo obtener el último número de una cadena en bash?

¿Cómo puedo filtrar resultados únicos de la salida grep?

¿Cómo puedo obtener la duración de un archivo de video desde la consola?

¿Cómo obtener máscara de red de bash?

¿Cómo puedo obtener un binario de un archivo .py?