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

8

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 ...

Michael Durrant
fonte

Respostas:

4

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

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

Para readem uma 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
fonte
Como posso fazer disso uma matriz?
Michael Durrant
@MichaelDurrant, se você quer dizer um basharray, acrescentou uma maneira
Iruvar
Veja aqui se sua matriz contém espaço em branco
Tom Hale
@iruvar, você pode explicar o que isso realmente significa? Eu sou novo no awk scripting e seria útil se você pudesse esclarecer o que realmente acontece quando diz isso! a [$ 0] ++
Abhishek
@iruvar, se não for possível explicar nos comentários, qualquer site que explique a sintaxe acima pelo menos seria benéfico.
Abhishek
9

Se você estiver usando o zsh:

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

ou (se a KSH_ARRAYSopção não estiver definida)

$ echo ${(u)array}
1 2 3
jimmij
fonte
1
Se a matriz puder conter elementos vazios, você deve usar "${(u)array[@]}"ou "${(@u)array}"(observe as aspas).
Stéphane Chazelas
Estou usando o zsh 5.1.1 (x86_64-ubuntu-linux-gnu) e ${(u)array}funciona mesmo se a matriz estiver vazia ou contiver uma string vazia, sem aspas.
Kievlaluno #
4

Para uma matriz com valores arbitrários, é bastante complicado bash, pois não possui um operador interno para isso.

bash no entanto, não suporta o armazenamento de caracteres NUL em suas variáveis; portanto, você pode usá-lo para passar isso para outros comandos:

O equivalente a zsh's:

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

em um sistema GNU recente, pode ser:

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

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

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

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

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

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

Com tcsh:

set -f new_array = ($array:q)

Reteria o f elemento irst ( a b a=> a b) como zsh's (u)flag expansão.

set -l new_array = ($array:q)

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

Stéphane Chazelas
fonte
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, conforme sugerido por Costas, pode 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 eu faço um eco arr, essa é a saída que eu recebo.

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

Referências

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

Ramesh
fonte
@ Costas, obrigado. Eu o incorporei à resposta.
Ramesh 10/11
Como posso fazer com que o resultado final seja uma matriz?
Michael Durrant
@ MichaelDurrant, consulte a resposta atualizada e deixe-me saber se está tudo bem.
Ramesh
Se você quer colocar resultado em matriz que você pode tira último comandotr '\n' ' '
Costas
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-a à resultmatriz e sinalize-a como tendo sido vista. Depois que uma palavra for vista, ignore as aparências subseqüentes.

Scott
fonte
2
Observe que você precisa unset seenantes declare -A seen, caso $seentenha sido definido anteriormente (mesmo como uma variável escalar do ambiente).
Stéphane Chazelas