¿Cómo crear una matriz de elementos únicos a partir de una cadena / matriz en bash?

8

Si tengo una cadena "1 2 3 2 1", o una matriz [1,2,3,2,1], ¿cómo puedo seleccionar los valores únicos?

"1 2 3 2 1" produces "1 2 3" 

o

[1,2,3,2,1] produces [1,2,3]

Similar a uniq pero uniq parece funcionar en líneas enteras, no en patrones dentro de una línea ...

Michael Durrant
fuente

Respuestas:

4

Con GNU awk(esto también conserva el orden original)

printf '%s\n' "1 2 3 2 1" | awk -v RS='[[:space:]]+' '!a[$0]++{printf "%s%s", $0, RT}'
1 2 3 

Para readen una bashmatriz

read -ra arr<<<$(printf '%s\n' "1 2 3 2 1" |
 awk -v RS='[[:space:]]+' '!a[$0]++{printf "%s%s", $0, RT}')
printf "%s\n"  "${arr[@]}"
1
2
3
iruvar
fuente
¿Cómo puedo hacer eso una matriz?
Michael Durrant
@MichaelDurrant, si te refieres a una bashmatriz, agregó una forma
iruvar
Vea aquí si su matriz contiene espacios en blanco
Tom Hale
@iruvar, ¿puedes explicar qué significa esto realmente? Soy nuevo en las secuencias de comandos awk y sería útil si pudieras aclarar lo que realmente sucede cuando dices esto. a [$ 0] ++
Abhishek
@iruvar si no es posible explicar en los comentarios, cualquier sitio web que explique la sintaxis anterior al menos sería beneficioso.
Abhishek
9

Si está usando zsh:

$ array=(1 2 3 2 1)
$ echo ${(u)array[@]}
1 2 3

o (si la KSH_ARRAYSopción no está configurada) incluso

$ echo ${(u)array}
1 2 3
jimmij
fuente
1
Si la matriz puede contener elementos vacíos, debe usar "${(u)array[@]}"o en su "${(@u)array}"lugar (tenga en cuenta las citas).
Stéphane Chazelas
Estoy usando zsh 5.1.1 (x86_64-ubuntu-linux-gnu) , y ${(u)array}funciona incluso si la matriz está vacía o contiene una cadena vacía, sin comillas.
kiamlaluno 01 de
4

Para una matriz con valores arbitrarios, es bastante complicado bashya que no tiene un operador incorporado para eso.

bash sin embargo, no admite el almacenamiento de caracteres NUL en sus variables, por lo que puede hacer uso de eso para pasar eso a otros comandos:

El equivalente de zsh's:

new_array=("${(@u}array}")

en un sistema GNU reciente, podría ser:

eval "new_array=($(
  printf "%s\0" "${array[@]}" |
    LC_ALL=C sort -zu |
    xargs -r0 bash -c 'printf "%q\n" "$@"' sh
  ))"

Alternativamente, con versiones recientes de bash, y suponiendo que ninguno de los elementos de la matriz esté vacío, podría usar matrices asociativas:

unset hash
typeset -A hash
for i in "${array[@]}"; do
  hash[$i]=
done
new_array=("${!hash[@]}")

Con bash 4.4 y más reciente y con GNU sort:

readarray -td '' new_array < <(
  printf '%s\0' "${array[@]}" | LC_ALL=C sort -zu)

El orden de los elementos no sería el mismo en esas diferentes soluciones.

Con tcsh:

set -f new_array = ($array:q)

Conservaría la f elemento rimero ( a b a=> a b) como zsh's (u)bandera de expansión.

set -l new_array = ($array:q)

Retendría el último ( a b a=> b a). Sin embargo, esos eliminan elementos vacíos de la matriz.

Stéphane Chazelas
fuente
1

Esta solución funcionó para mí.

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

Lo anterior produce 1 2 3 como salida.

La versión más corta como lo sugiere Costas podría ser,

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

Para almacenar los resultados finales en una matriz, puede hacer algo como,

IFS=$' '
arr=($(printf "%s\n" "${ids[@]}" | sort -u | tr '\n' ' '))
unset IFS

Ahora, cuando hago un eco arr, esta es la salida que obtengo.

echo "${arr[@]}"
1 2 3

Referencias

https://stackoverflow.com/a/13648438/1742825 https://stackoverflow.com/a/9449633/1742825

Ramesh
fuente
@ Costas, gracias. Lo he incorporado a la respuesta.
Ramesh
¿Cómo puedo hacer que el resultado final sea una matriz?
Michael Durrant
@ MichaelDurrant, vea la respuesta actualizada y avíseme si está bien.
Ramesh
Si desea poner el resultado en la matriz, puede quitar el último comandotr '\n' ' '
Costas
0

Para hacerlo completamente en el shell y poner el resultado en una matriz,

declare -A seen
for word in one two three two one
do
        if [ ! "${seen[$word]}" ]
        then
                result+=("$word")
                seen[$word]=1
        fi
done
echo "${result[@]}"

En palabras: si aún no hemos visto una palabra determinada, agréguela a la resultmatriz y márquela como si se hubiera visto. Una vez que se ha visto una palabra, ignore las apariencias posteriores de la misma.

Scott
fuente
2
Tenga en cuenta que necesita unset seenantes declare -A seenen caso de que $seense haya definido previamente (incluso como una variable escalar del entorno).
Stéphane Chazelas