Como criar uma matriz de elementos exclusivos de uma string / array no bash?

7

Se eu tiver uma string "1 2 3 2 1" - ou uma matriz [1,2,3,2,1] - como posso selecionar os valores exclusivos, ou seja,

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

ou

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

Semelhante ao uniq, mas o uniq parece funcionar em linhas inteiras, não em padrões dentro de uma linha ...

    
por Michael Durrant 10.11.2014 / 18:17

5 respostas

4

Com o GNU awk (isso também mantém o pedido original)

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

Para read em uma matriz bash

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
    
por 10.11.2014 / 18:35
7

Se você estiver usando zsh:

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

ou (se a opção KSH_ARRAYS não estiver definida), mesmo

$ echo ${(u)array}
1 2 3
    
por 10.11.2014 / 18:26
3

Para um array com valores arbitrários, é bastante complicado com bash , pois ele não tem um operador interno para isso.

No entanto,

bash não suporta o armazenamento de caracteres NUL em suas variáveis, portanto, você pode fazer uso disso para outros comandos:

O equivalente a zsh :

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

em um sistema GNU recente, poderia ser:

eval "new_array=($(
  printf "%s
unset hash
typeset -A hash
for i in "${array[@]}"; do
  hash[$i]=
done
new_array=("${!hash[@]}")
" "${array[@]}" | LC_ALL=C sort -zu | xargs -r0 bash -c 'printf "%q\n" "$@"' sh ))"

Como alternativa, com versões recentes de bash e supondo que nenhum dos elementos da matriz esteja vazio, você pode usar matrizes associativas:

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

Com o bash 4.4 e mais recente e com o GNU sort :

set -l new_array = ($array:q)

A ordem dos elementos não seria a mesma nessas diferentes soluções.

com tcsh :

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

Manteria o f primeiro elemento ( a b a = > a b ) como o sinalizador de zsh (u) de expansão.

eval "new_array=($(
  printf "%s
unset hash
typeset -A hash
for i in "${array[@]}"; do
  hash[$i]=
done
new_array=("${!hash[@]}")
" "${array[@]}" | LC_ALL=C sort -zu | xargs -r0 bash -c 'printf "%q\n" "$@"' sh ))"

Reteria o último ( a b a = > b a ). Aqueles, no entanto, removem elementos vazios da matriz.

    
por 10.11.2014 / 21:05
1

Esta solução funcionou para mim.

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

O acima produz 1 2 3 como a saída.

A versão mais curta sugerida por Costas poderia ser

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

Para armazenar os resultados finais em uma matriz, você pode fazer algo como

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

Agora, quando faço um eco em arr , esta é a saída que recebo.

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

Referências

link link

    
por 10.11.2014 / 18:20
0

Para fazer isso inteiramente no shell e colocar o resultado em uma 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[@]}"

Em palavras: se ainda não vimos uma palavra, adicione-o à matriz result e marque-o como visto. Uma vez que uma palavra tenha sido vista, ignore as aparições subseqüentes dela.

    
por 10.11.2014 / 19:43