Como classificar uma matriz no Bash

139

Eu tenho uma matriz no Bash, por exemplo:

array=(a c b f 3 5)

Eu preciso classificar a matriz. Não apenas exibindo o conteúdo de maneira classificada, mas para obter uma nova matriz com os elementos classificados. A nova matriz classificada pode ser completamente nova ou antiga.

u32004
fonte

Respostas:

208

Você realmente não precisa de muito código:

IFS=$'\n' sorted=($(sort <<<"${array[*]}"))
unset IFS

Oferece suporte a espaço em branco nos elementos (desde que não seja uma nova linha) e funciona no Bash 3.x.

por exemplo:

$ array=("a c" b f "3 5")
$ IFS=$'\n' sorted=($(sort <<<"${array[*]}")); unset IFS
$ printf "[%s]\n" "${sorted[@]}"
[3 5]
[a c]
[b]
[f]

Nota: @sorontar indicou que é necessário cuidado se os elementos contiverem curingas como *ou ?:

A parte classificada = ($ (...)) está usando o operador "split and glob". Você deve desativar o glob: set -fou set -o noglobou shopt -op noglobou um elemento da matriz como *será expandido para uma lista de arquivos.

O que está acontecendo:

O resultado é o culminar de seis coisas que acontecem nesta ordem:

  1. IFS=$'\n'
  2. "${array[*]}"
  3. <<<
  4. sort
  5. sorted=($(...))
  6. unset IFS

Primeiro, o IFS=$'\n'

Essa é uma parte importante de nossa operação que afeta o resultado de 2 e 5 da seguinte maneira:

Dado:

  • "${array[*]}" expande para todos os elementos delimitados pelo primeiro caractere de IFS
  • sorted=() cria elementos dividindo cada caractere de IFS

IFS=$'\n' configura as coisas para que os elementos sejam expandidos usando uma nova linha como delimitador e, posteriormente, criados de maneira que cada linha se torne um elemento. (ou seja, dividir em uma nova linha.)

A delimitação por uma nova linha é importante porque é assim que sortfunciona (classificação por linha). A divisão apenas por uma nova linha não é tão importante, mas é necessário preservar elementos que contenham espaços ou tabulações.

O valor padrão de IFSé um espaço , uma guia , seguido por uma nova linha , e seria inadequado para nossa operação.

Em seguida, a sort <<<"${array[*]}"parte

<<<, chamado aqui strings , pega a expansão de "${array[*]}", como explicado acima, e a alimenta na entrada padrão de sort.

Com o nosso exemplo, sorté alimentada esta seguinte string:

a c
b
f
3 5

Desde as sort sortes , produz:

3 5
a c
b
f

Em seguida, a sorted=($(...))parte

A $(...)parte, chamada substituição de comando , faz com que seu conteúdo ( sort <<<"${array[*]}) seja executado como um comando normal, considerando a saída padrão resultante como o literal que está onde quer que $(...)estivesse.

No nosso exemplo, isso produz algo semelhante a simplesmente escrever:

sorted=(3 5
a c
b
f
)

sorted torna-se uma matriz criada dividindo esse literal em cada nova linha.

finalmente, o unset IFS

Isso redefine o valor IFSpara o valor padrão e é apenas uma boa prática.

É para garantir que não causemos problemas em nada que se apóia IFSmais tarde em nosso script. (Caso contrário, precisaríamos lembrar que trocamos as coisas - algo que pode ser impraticável para scripts complexos.)

Antak
fonte
2
@xxor sem o IFS, ele dividirá seus elementos em pequenos pedaços se eles tiverem espaços em branco. Experimente o exemplo com IFS=$'\n' omitido e veja!
antak
3
Muito agradável. Você poderia explicar para o usuário comum do bash como essa solução funciona?
U32004
2
Agora, com o IFS, ele divide seus elementos em pequenos pedaços se eles tiverem apenas um tipo específico de espaço em branco. Boa; não é perfeito :-)
Expiação limitada
7
É unset IFSnecessário? Eu pensei que anexar IFS=a um comando no escopo da alteração somente nesse comando, retornando ao seu valor anterior automaticamente depois.
Mark H
10
@ MarkH É necessário porque sorted=()não é um comando, mas uma segunda atribuição de variável.
antak
35

Resposta original:

array=(a c b "f f" 3 5)
readarray -t sorted < <(for a in "${array[@]}"; do echo "$a"; done | sort)

resultado:

$ for a in "${sorted[@]}"; do echo "$a"; done
3
5
a
b
c
f f

Observe que esta versão lida com valores que contêm caracteres especiais ou espaços em branco ( exceto novas linhas)

Nota O readarray é suportado no bash 4+.


Editar Com base na sugestão de @Dimitre, eu a atualizei para:

readarray -t sorted < <(printf '%s\0' "${array[@]}" | sort -z | xargs -0n1)

que tem o benefício de entender até mesmo os elementos de classificação com caracteres de nova linha incorporados corretamente. Infelizmente, como sinalizado corretamente por @ruakh, isso não significa que o resultado de readarrayestaria correto , porque readarraynão tem opção para usar em NULvez de novas linhas regulares como separadores de linha.

ver
fonte
5
Bom, deve-se notar também que o readarray está disponível desde a versão 4 do bash. Pode ser reduzido um pouco:readarray -t sorted < <(printf '%s\n' "${array[@]}" | sort)
Dimitre Radoulov
1
@ Dimitre: Aceitei sua sugestão e consertei a manipulação de espaço em branco para trabalhar com qualquer coisa (usando nullchar-delimitadores internamente). Cheers
veja
1
Sim, sort -zé uma melhoria útil, suponho que a -zopção seja uma extensão de classificação GNU.
Dimitre Radoulov
2
Se você quiser lidar com novas linhas incorporadas, poderá rolar sua própria matriz de leitura. Por exemplo: sorted=(); while read -d $'\0' elem; do sorted[${#sorted[@]}]=$elem; done < <(printf '%s\0' "${array[@]}" | sort -z). Isso também funciona quando você está usando o bash v3 em vez do bash v4, porque o readarray não está disponível no bash v3.
Bob Bell
1
@ user1527227 É redirecionamento de entrada ( <) combinado com substituição de processo <(...) . Ou, de forma intuitiva: porque (printf "bla")não é um arquivo.
sehe
33

Aqui está uma implementação pura do quicksort do Bash:

#!/bin/bash

# quicksorts positional arguments
# return is in array qsort_ret
qsort() {
   local pivot i smaller=() larger=()
   qsort_ret=()
   (($#==0)) && return 0
   pivot=$1
   shift
   for i; do
      if (( i < pivot )); then
         smaller+=( "$i" )
      else
         larger+=( "$i" )
      fi
   done
   qsort "${smaller[@]}"
   smaller=( "${qsort_ret[@]}" )
   qsort "${larger[@]}"
   larger=( "${qsort_ret[@]}" )
   qsort_ret=( "${smaller[@]}" "$pivot" "${larger[@]}" )
}

Use como, por exemplo,

$ array=(a c b f 3 5)
$ qsort "${array[@]}"
$ declare -p qsort_ret
declare -a qsort_ret='([0]="3" [1]="5" [2]="a" [3]="b" [4]="c" [5]="f")'

Essa implementação é recursiva ... então, aqui está uma rápida descrição iterativa:

#!/bin/bash

# quicksorts positional arguments
# return is in array qsort_ret
# Note: iterative, NOT recursive! :)
qsort() {
   (($#==0)) && return 0
   local stack=( 0 $(($#-1)) ) beg end i pivot smaller larger
   qsort_ret=("$@")
   while ((${#stack[@]})); do
      beg=${stack[0]}
      end=${stack[1]}
      stack=( "${stack[@]:2}" )
      smaller=() larger=()
      pivot=${qsort_ret[beg]}
      for ((i=beg+1;i<=end;++i)); do
         if [[ "${qsort_ret[i]}" < "$pivot" ]]; then
            smaller+=( "${qsort_ret[i]}" )
         else
            larger+=( "${qsort_ret[i]}" )
         fi
      done
      qsort_ret=( "${qsort_ret[@]:0:beg}" "${smaller[@]}" "$pivot" "${larger[@]}" "${qsort_ret[@]:end+1}" )
      if ((${#smaller[@]}>=2)); then stack+=( "$beg" "$((beg+${#smaller[@]}-1))" ); fi
      if ((${#larger[@]}>=2)); then stack+=( "$((end-${#larger[@]}+1))" "$end" ); fi
   done
}

Nos dois casos, você pode alterar a ordem que usa: usei comparações de strings, mas você pode usar comparações aritméticas, comparar o tempo de modificação do arquivo wrt, etc. apenas use o teste apropriado; você pode até torná-lo mais genérico e usar um primeiro argumento que seja o uso da função de teste, por exemplo,

#!/bin/bash

# quicksorts positional arguments
# return is in array qsort_ret
# Note: iterative, NOT recursive! :)
# First argument is a function name that takes two arguments and compares them
qsort() {
   (($#<=1)) && return 0
   local compare_fun=$1
   shift
   local stack=( 0 $(($#-1)) ) beg end i pivot smaller larger
   qsort_ret=("$@")
   while ((${#stack[@]})); do
      beg=${stack[0]}
      end=${stack[1]}
      stack=( "${stack[@]:2}" )
      smaller=() larger=()
      pivot=${qsort_ret[beg]}
      for ((i=beg+1;i<=end;++i)); do
         if "$compare_fun" "${qsort_ret[i]}" "$pivot"; then
            smaller+=( "${qsort_ret[i]}" )
         else
            larger+=( "${qsort_ret[i]}" )
         fi
      done
      qsort_ret=( "${qsort_ret[@]:0:beg}" "${smaller[@]}" "$pivot" "${larger[@]}" "${qsort_ret[@]:end+1}" )
      if ((${#smaller[@]}>=2)); then stack+=( "$beg" "$((beg+${#smaller[@]}-1))" ); fi
      if ((${#larger[@]}>=2)); then stack+=( "$((end-${#larger[@]}+1))" "$end" ); fi
   done
}

Então você pode ter esta função de comparação:

compare_mtime() { [[ $1 -nt $2 ]]; }

E use:

$ qsort compare_mtime *
$ declare -p qsort_ret

para que os arquivos na pasta atual sejam classificados pela hora da modificação (a mais nova primeiro).

NOTA. Essas funções são pura Bash! sem utilitários externos e sem sub-conchas! eles são seguros com quaisquer símbolos engraçados que você possa ter (espaços, caracteres de nova linha, caracteres glob etc.).

gniourf_gniourf
fonte
1
Parabéns pelo impressionante Bashing, que oferece grande flexibilidade com relação aos elementos de entrada e aos critérios de classificação. Se a classificação baseada em linha com as opções de classificação sortoferecidas for suficiente, uma solução sort+ read -aserá mais rápida, iniciando em torno de, digamos, 20 itens e cada vez mais e significativamente mais rápida quanto mais elementos você estiver lidando. Por exemplo, no iMac de final de 2012 executando o OSX 10.11.1 com uma matriz Fusion Drive: 100 elementos: ca. 0,03s segs. ( qsort()) vs. ca. 0,005 seg. ( sort+ read -a); Matriz de 1000 elementos: ca. 0,375 seg. ( qsort()) vs. ca. 0,014 segundos ( sort+ read -a).
mklement0
Agradável. Lembro-me do tipo rápido dos dias de faculdade, mas também pesquisarei o tipo de bolha. Para minhas necessidades de classificação, tenho o primeiro e o segundo elementos formando a chave, seguidos por um elemento de dados (que pode ser expandido posteriormente). Seu código pode ser aprimorado com o número de elementos-chave (parm1) e o número de elementos de dados (parm2). Para OP, os parâmetros seriam 1 e 0. Para mim, os parâmetros seriam 2 e 1. De qualquer forma, sua resposta tem mais promessas.
WinEunuuchs2Unix
1
Com um conjunto de dados de números inteiros de cadeia não transmitidos, achei if [ "$i" -lt "$pivot" ]; thennecessário, caso contrário, o "2" <"10" resolvido retornou verdadeiro. Eu acredito que seja POSIX vs. Lexicográfico; ou talvez Link embutido .
usar o seguinte código
27

Se você não precisar manipular caracteres especiais do shell nos elementos da matriz:

array=(a c b f 3 5)
sorted=($(printf '%s\n' "${array[@]}"|sort))

Com o bash, você precisará de um programa de classificação externo de qualquer maneira.

Com o zsh, nenhum programa externo é necessário e caracteres shell especiais são facilmente manipulados:

% array=('a a' c b f 3 5); printf '%s\n' "${(o)array[@]}" 
3
5
a a
b
c
f

O ksh precisa set -sclassificar ASCIIbetically .

Dimitre Radoulov
fonte
Muito boa informação de fundo. Eu quase pedir uma demonstração sobre como ksh usaria o conjunto -s bandeira ... mas, novamente, a questão está em bash, de modo que seria bastante off-topic
sehe
Isso deve funcionar com a maioria das implementações do KornShell (por exemplo, ksh88 e pdksh ): set -A array x 'a a' d; set -s -- "${array[@]}"; set -A sorted "$@" E, é claro, o comando set redefinirá os parâmetros posicionais atuais, se houver.
Dimitre Radoulov
Você é uma verdadeira fonte de conhecimento da concha. Tenho certeza que você deve ter memória photographics ou algo assim, porque este tipo de diferenças sutis iludir a maioria dos outros membros da espécie humana :), um para o pacote completo de informações
sehe
10

tl; dr :

Classifique a matriz a_ine armazene o resultado a_out(os elementos não devem ter novas linhas incorporadas [1] ):

Bash v4 +:

readarray -t a_out < <(printf '%s\n' "${a_in[@]}" | sort)

Bash v3:

IFS=$'\n' read -d '' -r -a a_out < <(printf '%s\n' "${a_in[@]}" | sort)

Vantagens sobre a solução da antak :

  • Você não precisa se preocupar com globbing acidental (interpretação acidental dos elementos da matriz como padrões de nome de arquivo), portanto, nenhum comando extra é necessário para desativar o globbing ( set -fe set +frestaurá-lo mais tarde).

  • Você não precisa se preocupar em redefinir IFScom unset IFS. [2]


Leitura opcional: explicação e código de amostra

O acima combina código Bash com utilitário externo sort para uma solução que funciona com elementos arbitrários de linha única e classificação lexical ou numérica (opcionalmente por campo) :

  • Desempenho : Para cerca de 20 elementos ou mais , isso será mais rápido que uma solução Bash pura - de forma significativa e crescente, assim que você ultrapassar cerca de 100 elementos.
    (Os limites exatos dependerão de sua entrada, máquina e plataforma específicas.)

    • O motivo é rápido: evita os loops do Bash .
  • printf '%s\n' "${a_in[@]}" | sort executa a classificação (lexicamente, por padrão - consulte sorta especificação POSIX ):

    • "${a_in[@]}"expande com segurança para os elementos da matriz a_incomo argumentos individuais , independentemente do que eles contenham (incluindo espaço em branco).

    • printf '%s\n' depois imprime cada argumento - ou seja, cada elemento da matriz - em sua própria linha, como está.

  • Observe o uso de uma substituição de processo ( <(...)) para fornecer a saída classificada como entrada para read/ readarray(via redirecionamento para stdin <), porque read/ readarraydeve ser executado no shell atual (não deve ser executado em um subshell ) para que a variável de saída a_outseja visível para o shell atual (para que a variável permaneça definida no restante do script).

  • sortSaída da leitura em um variável de matriz :

    • Bash v4 +: readarray -t a_outlê as linhas individuais geradas sortnos elementos da variável da matriz a_out, sem incluir o final\n de cada elemento ( -t).

    • Bash v3: readarraynão existe, por isso readdeve ser usado:
      IFS=$'\n' read -d '' -r -a a_outinforma readpara ler a -avariável array ( ) a_out, ler toda a entrada, entre linhas ( -d ''), mas dividi-la em elementos da matriz por novas linhas ( IFS=$'\n'. $'\n', Que produz uma nova linha literal (LF) ), é uma sequência de caracteres citada por ANSI C ).
      ( -r, uma opção que quase sempre deve ser usada com read, desativa o tratamento inesperado de \caracteres.)

Código de amostra anotado:

#!/usr/bin/env bash

# Define input array `a_in`:
# Note the element with embedded whitespace ('a c')and the element that looks like
# a glob ('*'), chosen to demonstrate that elements with line-internal whitespace
# and glob-like contents are correctly preserved.
a_in=( 'a c' b f 5 '*' 10 )

# Sort and store output in array `a_out`
# Saving back into `a_in` is also an option.
IFS=$'\n' read -d '' -r -a a_out < <(printf '%s\n' "${a_in[@]}" | sort)
# Bash 4.x: use the simpler `readarray -t`:
# readarray -t a_out < <(printf '%s\n' "${a_in[@]}" | sort)

# Print sorted output array, line by line:
printf '%s\n' "${a_out[@]}"

Devido ao uso de sortsem opções, isso gera classificação lexical (classificação de dígitos antes das letras e sequências de dígitos são tratadas lexicamente, não como números):

*
10
5
a c
b
f

Se você quisesse a classificação numérica pelo 1º campo, usaria, em sort -k1,1nvez de apenas sort, o que gera (classificação sem números antes dos números e classificação corretamente):

*
a c
b
f
5
10

[1] elementos identificador para com novas linhas incorporados, utilizar a seguinte variante (Bash V4 +, com GNU sort ):
readarray -d '' -t a_out < <(printf '%s\0' "${a_in[@]}" | sort -z).
A resposta útil de Michał Górny tem uma solução Bash v3.

[2] Embora IFS seja definido na variante v3 Bash, a alteração é delimitado para o comando .
Por outro lado, o que se segue IFS=$'\n' na resposta da antak é uma atribuição e não um comando; nesse caso, a IFSmudança é global .

mklement0
fonte
8

Na viagem de trem de três horas de Munique a Frankfurt (que tive problemas para alcançar porque a Oktoberfest começa amanhã), eu estava pensando no meu primeiro post. Empregar uma matriz global é uma ideia muito melhor para uma função de classificação geral. A função a seguir lida com sequências arbóreas (novas linhas, espaços em branco etc.):

declare BSORT=()
function bubble_sort()
{   #
    # @param [ARGUMENTS]...
    #
    # Sort all positional arguments and store them in global array BSORT.
    # Without arguments sort this array. Return the number of iterations made.
    #
    # Bubble sorting lets the heaviest element sink to the bottom.
    #
    (($# > 0)) && BSORT=("$@")
    local j=0 ubound=$((${#BSORT[*]} - 1))
    while ((ubound > 0))
    do
        local i=0
        while ((i < ubound))
        do
            if [ "${BSORT[$i]}" \> "${BSORT[$((i + 1))]}" ]
            then
                local t="${BSORT[$i]}"
                BSORT[$i]="${BSORT[$((i + 1))]}"
                BSORT[$((i + 1))]="$t"
            fi
            ((++i))
        done
        ((++j))
        ((--ubound))
    done
    echo $j
}

bubble_sort a c b 'z y' 3 5
echo ${BSORT[@]}

Isso imprime:

3 5 a b c z y

A mesma saída é criada a partir de

BSORT=(a c b 'z y' 3 5) 
bubble_sort
echo ${BSORT[@]}

Observe que provavelmente o Bash usa internamente ponteiros inteligentes, portanto a operação de troca pode ser barata (embora eu duvide). No entanto, bubble_sortdemonstra que funções mais avançadas como merge_sorttambém estão ao alcance da linguagem shell.

Andreas Spindler
fonte
5
Tipo de bolha? Uau .. Obama diz "bubble sort seria o caminho errado para ir" -> youtube.com/watch?v=k4RRi_ntQc8
Robottinosino
1
Bem, parece que enquanto o O-cara queria ser inteligente, ele não sentia que essa não era uma questão de 50/50 de chance. Um predecessor na posição de O-guy, vamos dizer a ele que o B-guy, uma vez se saiu muito melhor (Reynoldsburg, Ohio, outubro de 2000): "Acho que se você sabe no que acredita, fica muito mais fácil responder a perguntas. Não consigo responder sua pergunta. " Então esse cara B realmente sabe algo sobre lógica booleana. O cara-O não.
Andreas Spindler
A função pode ser tornada mais facilmente portátil, tornando o BSORT um array local com um nameref para qualquer array a ser classificado. ou seja, local -n BSORT="$1"no início da função. Então você pode correr bubble_sort myarraypara classificar minha matriz .
johnraff
7

Outra solução que utiliza externos sortlida e com quaisquer caracteres especiais (excepto para NULs :)). Deve funcionar com o bash-3.2 e GNU ou BSD sort(infelizmente, o POSIX não inclui -z).

local e new_array=()
while IFS= read -r -d '' e; do
    new_array+=( "${e}" )
done < <(printf "%s\0" "${array[@]}" | LC_ALL=C sort -z)

Primeiro, observe o redirecionamento de entrada no final. Estamos usando o printfbuilt-in para escrever os elementos da matriz, terminados em zero. A citação garante que os elementos da matriz sejam passados ​​como estão e as especificidades do shell printffazem com que reutilize a última parte da string de formato para cada parâmetro restante. Ou seja, é equivalente a algo como:

for e in "${array[@]}"; do
    printf "%s\0" "${e}"
done

A lista de elementos terminados por nulo é então passada para sort. A -zopção faz com que ele leia elementos terminados em nulo, os ordene e também emita saída. Se você precisava obter apenas os elementos exclusivos, pode passar, -upois é mais portátil que uniq -z. O LC_ALL=Cgarante ordenação estável independentemente do local - às vezes útil para scripts. Se você quer osort respeitar a localidade, remova-a.

A <()construção obtém o descritor para ler a partir do pipeline gerado e <redireciona a entrada padrão do whileloop para ele. Se você precisar acessar a entrada padrão dentro do canal, poderá usar outro descritor - exercício para o leitor :).

Agora, de volta ao começo. O readbuilt-in lê a saída do stdin redirecionado. Definir vazio IFSdesativa a divisão de palavras que é desnecessária aqui - como resultado, readlê toda a 'linha' de entrada para a única variável fornecida. -rA opção desativa o processamento de escape que também é indesejado aqui. Por fim, -d ''define o delimitador de linha como NUL - ou seja, informa readpara ler cadeias terminadas em zero.

Como resultado, o loop é executado uma vez para cada elemento da matriz terminado em zero sucessivo, com o valor sendo armazenado em e . O exemplo apenas coloca os itens em outra matriz, mas você pode preferir processá-los diretamente :).

Claro, essa é apenas uma das muitas maneiras de alcançar o mesmo objetivo. A meu ver, é mais simples do que implementar o algoritmo de classificação completo no bash e, em alguns casos, será mais rápido. Ele lida com todos os caracteres especiais, incluindo novas linhas e deve funcionar na maioria dos sistemas comuns. Mais importante ainda, isso pode lhe ensinar algo novo e impressionante sobre o bash :).

Michał Górny
fonte
Ótima solução e explicação muito útil, obrigado. Uma extensão: sem definir o IFS como vazio, os espaços em branco à esquerda também serão eliminados - mesmo que de outra forma nenhuma divisão de palavras tenha sido feita.
Dirk Herrmann
Em vez de introduzir a variável local ee configurar o IFS vazio, use a variável REPLY.
Robin A. Meade
2

tente isto:

echo ${array[@]} | awk 'BEGIN{RS=" ";} {print $1}' | sort

A saída será:

3
5
uma
b
c
f

Problema resolvido.

rsingh
fonte
3
Deve editar isso para colocar a saída em uma nova matriz para responder totalmente à sua pergunta.
Peter Oram
2

Se você pode calcular um número inteiro exclusivo para cada elemento da matriz, assim:

tab='0123456789abcdefghijklmnopqrstuvwxyz'

# build the reversed ordinal map
for ((i = 0; i < ${#tab}; i++)); do
    declare -g ord_${tab:i:1}=$i
done

function sexy_int() {
    local sum=0
    local i ch ref
    for ((i = 0; i < ${#1}; i++)); do
        ch="${1:i:1}"
        ref="ord_$ch"
        (( sum += ${!ref} ))
    done
    return $sum
}

sexy_int hello
echo "hello -> $?"
sexy_int world
echo "world -> $?"

então, você pode usar esses números inteiros como índices de matriz, porque o Bash sempre usa matriz esparsa; portanto, não é necessário se preocupar com índices não utilizados:

array=(a c b f 3 5)
for el in "${array[@]}"; do
    sexy_int "$el"
    sorted[$?]="$el"
done

echo "${sorted[@]}"
  • Pros. Rápido.
  • Cons. Os elementos duplicados são mesclados e pode ser impossível mapear o conteúdo para números inteiros exclusivos de 32 bits.
Xiè Jìléi
fonte
Técnica interessante, usei uma variante para encontrar valores máximos / mínimos sem comparação / classificação explícitas. Porém , a adição não ponderada sem considerar o comprimento não funcionará: "z" é classificado antes de "aaaa", portanto, você não pode usá-lo para palavras como mostra acima.
mr.spuratic
2

classificação mínima:

#!/bin/bash
array=(.....)
index_of_element1=0

while (( ${index_of_element1} < ${#array[@]} )); do

    element_1="${array[${index_of_element1}]}"

    index_of_element2=$((index_of_element1 + 1))
    index_of_min=${index_of_element1}

    min_element="${element_1}"

        for element_2 in "${array[@]:$((index_of_element1 + 1))}"; do
            min_element="`printf "%s\n%s" "${min_element}" "${element_2}" | sort | head -n+1`"      
            if [[ "${min_element}" == "${element_2}" ]]; then
                index_of_min=${index_of_element2}
            fi
            let index_of_element2++
        done

        array[${index_of_element1}]="${min_element}"
        array[${index_of_min}]="${element_1}"

    let index_of_element1++
done
MathQues
fonte
1
array=(a c b f 3 5)
new_array=($(echo "${array[@]}" | sed 's/ /\n/g' | sort))    
echo ${new_array[@]}

O conteúdo do eco de new_array será:

3 5 a b c f
blp
fonte
1

Existe uma solução alternativa para o problema usual de espaços e novas linhas:

Use um caractere que não esteja na matriz original (como $'\1'ou$'\4' ou similar).

Esta função realiza o trabalho:

# Sort an Array may have spaces or newlines with a workaround (wa=$'\4')
sortarray(){ local wa=$'\4' IFS=''
             if [[ $* =~ [$wa] ]]; then
                 echo "$0: error: array contains the workaround char" >&2
                 exit 1
             fi

             set -f; local IFS=$'\n' x nl=$'\n'
             set -- $(printf '%s\n' "${@//$nl/$wa}" | sort -n)
             for    x
             do     sorted+=("${x//$wa/$nl}")
             done
       }

Isso classificará a matriz:

$ array=( a b 'c d' $'e\nf' $'g\1h')
$ sortarray "${array[@]}"
$ printf '<%s>\n' "${sorted[@]}"
<a>
<b>
<c d>
<e
f>
<gh>

Isso reclamará que a matriz de origem contém o caractere da solução alternativa:

$ array=( a b 'c d' $'e\nf' $'g\4h')
$ sortarray "${array[@]}"
./script: error: array contains the workaround char

descrição

  • Definimos duas variáveis ​​locais wa(solução alternativa) e um IFS nulo
  • Então (com ifs null) testamos que toda a matriz $*.
  • Não contém nenhum char woreound [[ $* =~ [$wa] ]].
  • Nesse caso, levante uma mensagem e sinalize um erro: exit 1
  • Evite expansões de nome de arquivo: set -f
  • Defina um novo valor de IFS ( IFS=$'\n') uma variável de loop xe uma nova linha var ( nl=$'\n').
  • Imprimimos todos os valores dos argumentos recebidos (a matriz de entrada $@).
  • mas substituímos qualquer nova linha pelo caractere de solução alternativa "${@//$nl/$wa}".
  • envie esses valores para serem classificados sort -n .
  • e coloque de volta todos os valores classificados nos argumentos posicionais set --.
  • Em seguida, atribuímos cada argumento um por um (para preservar novas linhas).
  • em um loop for x
  • para uma nova matriz: sorted+=(…)
  • aspas internas para preservar qualquer nova linha existente.
  • restaurando a solução alternativa para uma nova linha "${x//$wa/$nl}".
  • feito
Isaac
fonte
1

Esta questão parece intimamente relacionada. E, a propósito, aqui está uma fusão no Bash (sem processos externos):

mergesort() {
  local -n -r input_reference="$1"
  local -n output_reference="$2"
  local -r -i size="${#input_reference[@]}"
  local merge previous
  local -a -i runs indices
  local -i index previous_idx merged_idx \
           run_a_idx run_a_stop \
           run_b_idx run_b_stop

  output_reference=("${input_reference[@]}")
  if ((size == 0)); then return; fi

  previous="${output_reference[0]}"
  runs=(0)
  for ((index = 0;;)) do
    for ((++index;; ++index)); do
      if ((index >= size)); then break 2; fi
      if [[ "${output_reference[index]}" < "$previous" ]]; then break; fi
      previous="${output_reference[index]}"
    done
    previous="${output_reference[index]}"
    runs+=(index)
  done
  runs+=(size)

  while (("${#runs[@]}" > 2)); do
    indices=("${!runs[@]}")
    merge=("${output_reference[@]}")
    for ((index = 0; index < "${#indices[@]}" - 2; index += 2)); do
      merged_idx=runs[indices[index]]
      run_a_idx=merged_idx
      previous_idx=indices[$((index + 1))]
      run_a_stop=runs[previous_idx]
      run_b_idx=runs[previous_idx]
      run_b_stop=runs[indices[$((index + 2))]]
      unset runs[previous_idx]
      while ((run_a_idx < run_a_stop && run_b_idx < run_b_stop)); do
        if [[ "${merge[run_a_idx]}" < "${merge[run_b_idx]}" ]]; then
          output_reference[merged_idx++]="${merge[run_a_idx++]}"
        else
          output_reference[merged_idx++]="${merge[run_b_idx++]}"
        fi
      done
      while ((run_a_idx < run_a_stop)); do
        output_reference[merged_idx++]="${merge[run_a_idx++]}"
      done
      while ((run_b_idx < run_b_stop)); do
        output_reference[merged_idx++]="${merge[run_b_idx++]}"
      done
    done
  done
}

declare -ar input=({z..a}{z..a})
declare -a output

mergesort input output

echo "${input[@]}"
echo "${output[@]}"
Andrej Podzimek
fonte
0

Não estou convencido de que você precisará de um programa de classificação externa no Bash.

Aqui está minha implementação para o algoritmo simples de classificação de bolhas.

function bubble_sort()
{   #
    # Sorts all positional arguments and echoes them back.
    #
    # Bubble sorting lets the heaviest (longest) element sink to the bottom.
    #
    local array=($@) max=$(($# - 1))
    while ((max > 0))
    do
        local i=0
        while ((i < max))
        do
            if [ ${array[$i]} \> ${array[$((i + 1))]} ]
            then
                local t=${array[$i]}
                array[$i]=${array[$((i + 1))]}
                array[$((i + 1))]=$t
            fi
            ((i += 1))
        done
        ((max -= 1))
    done
    echo ${array[@]}
}

array=(a c b f 3 5)
echo " input: ${array[@]}"
echo "output: $(bubble_sort ${array[@]})"

Isso deve imprimir:

 input: a c b f 3 5
output: 3 5 a b c f
Andreas Spindler
fonte
O tipo de bolha é O(n^2). Parece que me lembro que a maioria dos algoritmos de classificação usa um O(n lg(n))até a última dúzia de elementos. Para os elementos finais, a classificação de seleção é usada.
jww 31/05
0
a=(e b 'c d')
shuf -e "${a[@]}" | sort >/tmp/f
mapfile -t g </tmp/f
Steven Penny
fonte
-1

sorted=($(echo ${array[@]} | tr " " "\n" | sort))

No espírito do bash / linux, eu daria a melhor ferramenta de linha de comando para cada etapa. sortfaz o trabalho principal, mas precisa de entrada separada por nova linha em vez de espaço, portanto, o pipeline muito simples acima simplesmente faz:

Conteúdo da matriz de eco -> substituir espaço por nova linha -> classificação

$() é ecoar o resultado

($()) é colocar o "resultado ecoado" em uma matriz

Nota : como @sorontar mencionado em um comentário para uma pergunta diferente:

A parte classificada = ($ (...)) está usando o operador "split and glob". Você deve desativar o glob: set -f ou set -o noglob ou shopt -op noglob ou um elemento da matriz como * será expandido para uma lista de arquivos.

Michael
fonte
No espírito do bash / linux : acho que você não entendeu o espírito. Seu código está completamente quebrado (expansão do nome do caminho e divisão de palavras). Isso seria melhor (Bash≥4):, mapfile -t sorted < <(printf '%s\n' "${array[@]}" | sort)caso contrário sorted=(); while IFS= read -r line; do sorted+=( "$line" ); done < <(printf '%s\n' | sort).
gniourf_gniourf
Os antipadrões que você está usando são:: echo ${array[@]} | tr " " "\n"isso será interrompido se os campos da matriz contiverem espaços em branco e caracteres glob. Além disso, gera um subshell e usa um comando externo inútil. E por echoser burro, ele será quebrado se o seu array começar com -e, -Eou -n. Em vez disso usar: printf '%s\n' "${array[@]}". O outro antipadrão é: ($())é colocar o "resultado ecoado" em uma matriz . Certamente não! esse é um antipadrão horrível que quebra por causa da expansão do nome do caminho (globbing) e da divisão de palavras. Nunca use esse horror.
gniourf_gniourf
A resposta principal tem o "antipadrão horrível". E uma maneira de diminuir a resposta de outra pessoa à pergunta que você mesmo respondeu.
Michael