Respostas:
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
ouset -o noglob
oushopt -op noglob
ou um elemento da matriz como*
será expandido para uma lista de arquivos.
O resultado é o culminar de seis coisas que acontecem nesta ordem:
IFS=$'\n'
"${array[*]}"
<<<
sort
sorted=($(...))
unset IFS
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.
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
sorted=($(...))
parteA $(...)
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.
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 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 anexar IFS=
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:
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 readarray
estaria correto , porque readarray
não tem opção para usar em NUL
vez de novas linhas regulares como separadores de linha.
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:
#!/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.).
sort
oferecidas for suficiente, uma solução sort
+ 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:
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 -s
classificar ASCIIbetically .
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 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 -f
e set +f
restaurá-lo mais tarde).
Você não precisa se preocupar em redefinir IFS
com unset IFS
. [2]
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 - consulte sort
a especificação POSIX ):
"${a_in[@]}"
expande com segurança para os elementos da matriz a_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 para read
/ readarray
(via redirecionamento para stdin <
), porque read
/ readarray
deve ser executado no shell atual (não deve ser executado em um subshell ) para que a variável de saída a_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 geradas sort
nos elementos da variável da matriz a_out
, sem incluir o final\n
de cada elemento ( -t
).
Bash v3: readarray
não existe, por isso read
deve ser usado:
IFS=$'\n' read -d '' -r -a a_out
informa read
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 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 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):
*
10
5
a c
b
f
Se você quisesse a classificação numérica pelo 1º campo, usaria, em sort -k1,1n
vez 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 IFS
mudança é global .
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_sort
demonstra que funções mais avançadas como merge_sort
também estão ao alcance da linguagem shell.
local -n BSORT="$1"
no início da função. Então você pode correr bubble_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 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 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 shell printf
fazem 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 -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 que uniq -z
. O LC_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 do while
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 vazio IFS
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, informa read
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 :).
e
e configurar o IFS vazio, use a variável REPLY.
tente isto:
echo ${array[@]} | awk 'BEGIN{RS=" ";} {print $1}' | sort
A saída será:
3 5 uma b c f
Problema resolvido.
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[@]}"
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
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
wa
(solução alternativa) e um IFS nulo$*
.[[ $* =~ [$wa] ]]
.exit 1
set -f
IFS=$'\n'
) uma variável de loop x
e uma nova linha var ( nl=$'\n'
).$@
)."${@//$nl/$wa}"
.sort -n
.set --
.for x
sorted+=(…)
"${x//$wa/$nl}"
.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[@]}"
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
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.
a=(e b 'c d')
shuf -e "${a[@]}" | sort >/tmp/f
mapfile -t g </tmp/f
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 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.
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)
.
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 echo
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.
IFS
, ele dividirá seus elementos em pequenos pedaços se eles tiverem espaços em branco. Experimente o exemplo comIFS=$'\n'
omitido e veja!