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 -f
ou set -o noglob
ou shopt -op noglob
ou 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:
IFS=$'\n'
"${array[*]}"
<<<
sort
sorted=($(...))
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 sort
funciona (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 IFS
para o valor padrão e é apenas uma boa prática.
É para garantir que não causemos problemas em nada que se apóia IFS
mais tarde em nosso script. (Caso contrário, precisaríamos lembrar que trocamos as coisas - algo que pode ser impraticável para scripts complexos.)
IFS
, ele dividirá seus elementos em pequenos pedaços se eles tiverem espaços em branco. Experimente o exemplo comIFS=$'\n'
omitido e veja!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 :-)unset IFS
necessário? Eu pensei que anexarIFS=
a um comando no escopo da alteração somente nesse comando, retornando ao seu valor anterior automaticamente depois.sorted=()
não é um comando, mas uma segunda atribuição de variável.Resposta original:
resultado:
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:
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
readarray
estaria correto , porquereadarray
não tem opção para usar emNUL
vez de novas linhas regulares como separadores de linha.fonte
readarray -t sorted < <(printf '%s\n' "${array[@]}" | sort)
sort -z
é uma melhoria útil, suponho que a-z
opção seja uma extensão de classificação GNU.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.<
) combinado com substituição de processo<(...)
. Ou, de forma intuitiva: porque(printf "bla")
não é um arquivo.Aqui está uma implementação pura do quicksort do Bash:
Use como, por exemplo,
Essa implementação é recursiva ... então, aqui está uma rápida descrição iterativa:
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,
Então você pode ter esta função de comparação:
E use:
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.).
fonte
sort
oferecidas for suficiente, uma soluçãosort
+read -a
será 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
).if [ "$i" -lt "$pivot" ]; then
necessário, caso contrário, o "2" <"10" resolvido retornou verdadeiro. Eu acredito que seja POSIX vs. Lexicográfico; ou talvez Link embutido .Se você não precisar manipular caracteres especiais do shell nos elementos da matriz:
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:
O ksh precisa
set -s
classificar ASCIIbetically .fonte
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.tl; dr :
Classifique a matriz
a_in
e armazene o resultadoa_out
(os elementos não devem ter novas linhas incorporadas [1] ):Bash v4 +:
Bash v3:
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 -f
eset +f
restaurá-lo mais tarde).Você não precisa se preocupar em redefinir
IFS
comunset 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.)
printf '%s\n' "${a_in[@]}" | sort
executa a classificação (lexicamente, por padrão - consultesort
a especificação POSIX ):"${a_in[@]}"
expande com segurança para os elementos da matriza_in
como 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 pararead
/readarray
(via redirecionamento para stdin<
), porqueread
/readarray
deve ser executado no shell atual (não deve ser executado em um subshell ) para que a variável de saídaa_out
seja visível para o shell atual (para que a variável permaneça definida no restante do script).sort
Saída da leitura em um variável de matriz :Bash v4 +:
readarray -t a_out
lê as linhas individuais geradassort
nos elementos da variável da matriza_out
, sem incluir o final\n
de cada elemento (-t
).Bash v3:
readarray
não existe, por issoread
deve ser usado:IFS=$'\n' read -d '' -r -a a_out
informaread
para ler a-a
variá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 comread
, desativa o tratamento inesperado de\
caracteres.)Código de amostra anotado:
Devido ao uso de
sort
sem 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):Se você quisesse a classificação numérica pelo 1º campo, usaria, em
sort -k1,1n
vez de apenassort
, o que gera (classificação sem números antes dos números e classificação corretamente):[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, aIFS
mudança é global .fonte
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.):
Isso imprime:
A mesma saída é criada a partir de
Observe que provavelmente o Bash usa internamente ponteiros inteligentes, portanto a operação de troca pode ser barata (embora eu duvide). No entanto,
bubble_sort
demonstra que funções mais avançadas comomerge_sort
também estão ao alcance da linguagem shell.fonte
local -n BSORT="$1"
no início da função. Então você pode correrbubble_sort myarray
para classificar minha matriz .Outra solução que utiliza externos
sort
lida e com quaisquer caracteres especiais (excepto para NULs :)). Deve funcionar com o bash-3.2 e GNU ou BSDsort
(infelizmente, o POSIX não inclui-z
).Primeiro, observe o redirecionamento de entrada no final. Estamos usando o
printf
built-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 shellprintf
fazem com que reutilize a última parte da string de formato para cada parâmetro restante. Ou seja, é equivalente a algo como:A lista de elementos terminados por nulo é então passada para
sort
. A-z
opçã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,-u
pois é mais portátil queuniq -z
. OLC_ALL=C
garante 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 dowhile
loop 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
read
built-in lê a saída do stdin redirecionado. Definir vazioIFS
desativa a divisão de palavras que é desnecessária aqui - como resultado,read
lê toda a 'linha' de entrada para a única variável fornecida.-r
A opção desativa o processamento de escape que também é indesejado aqui. Por fim,-d ''
define o delimitador de linha como NUL - ou seja, informaread
para 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 :).
fonte
e
e configurar o IFS vazio, use a variável REPLY.tente isto:
A saída será:
Problema resolvido.
fonte
Se você pode calcular um número inteiro exclusivo para cada elemento da matriz, assim:
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:
fonte
classificação mínima:
fonte
O conteúdo do eco de new_array será:
fonte
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:
Isso classificará a matriz:
Isso reclamará que a matriz de origem contém o caractere da solução alternativa:
descrição
wa
(solução alternativa) e um IFS nulo$*
.[[ $* =~ [$wa] ]]
.exit 1
set -f
IFS=$'\n'
) uma variável de loopx
e uma nova linha var (nl=$'\n'
).$@
)."${@//$nl/$wa}"
.sort -n
.set --
.for x
sorted+=(…)
"${x//$wa/$nl}"
.fonte
Esta questão parece intimamente relacionada. E, a propósito, aqui está uma fusão no Bash (sem processos externos):
fonte
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.
Isso deve imprimir:
fonte
O(n^2)
. Parece que me lembro que a maioria dos algoritmos de classificação usa umO(n lg(n))
até a última dúzia de elementos. Para os elementos finais, a classificação de seleção é usada.fonte
sorted=($(echo ${array[@]} | tr " " "\n" | sort))
No espírito do bash / linux, eu daria a melhor ferramenta de linha de comando para cada etapa.
sort
faz 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 matrizNota : como @sorontar mencionado em um comentário para uma pergunta diferente:
fonte
mapfile -t sorted < <(printf '%s\n' "${array[@]}" | sort)
caso contráriosorted=(); while IFS= read -r line; do sorted+=( "$line" ); done < <(printf '%s\n' | sort)
.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 porecho
ser burro, ele será quebrado se o seu array começar com-e
,-E
ou-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.