Réponses:
Vous n'avez pas vraiment besoin de beaucoup de code:
IFS=$'\n' sorted=($(sort <<<"${array[*]}"))
unset IFS
Prend en charge les espaces dans les éléments (tant qu'il ne s'agit pas d'une nouvelle ligne), et fonctionne dans Bash 3.x.
par exemple:
$ array=("a c" b f "3 5")
$ IFS=$'\n' sorted=($(sort <<<"${array[*]}")); unset IFS
$ printf "[%s]\n" "${sorted[@]}"
[3 5]
[a c]
[b]
[f]
Remarque: @sorontar a souligné que la prudence est de mise si les éléments contiennent des caractères génériques tels que *
ou ?
:
La partie triée = ($ (...)) utilise l'opérateur "split and glob". Vous devez désactiver glob:
set -f
ouset -o noglob
oushopt -op noglob
ou un élément du tableau comme*
sera développé en une liste de fichiers.
Le résultat est un aboutissement de six choses qui se produisent dans cet ordre:
IFS=$'\n'
"${array[*]}"
<<<
sort
sorted=($(...))
unset IFS
IFS=$'\n'
C'est une partie importante de notre opération qui affecte le résultat de 2 et 5 de la manière suivante:
Donné:
"${array[*]}"
s'étend à chaque élément délimité par le premier caractère de IFS
sorted=()
crée des éléments en divisant sur chaque caractère de IFS
IFS=$'\n'
configure les choses de manière à ce que les éléments soient développés à l'aide d' une nouvelle ligne comme délimiteur, puis créés ultérieurement de manière à ce que chaque ligne devienne un élément. (c.-à-d. fractionnement sur une nouvelle ligne.)
La délimitation par une nouvelle ligne est importante car c'est ainsi que sort
fonctionne (tri par ligne). Le fractionnement par une seule nouvelle ligne n'est pas aussi important, mais il est nécessaire de préserver les éléments qui contiennent des espaces ou des tabulations.
La valeur par défaut de IFS
est un espace , une tabulation , suivi d' une nouvelle ligne , et serait impropre à notre fonctionnement.
sort <<<"${array[*]}"
partie<<<
, appelé ici chaînes , prend l'expansion de "${array[*]}"
, comme expliqué ci-dessus, et la nourrit dans l'entrée standard desort
.
Avec notre exemple, sort
est alimenté cette chaîne suivante:
a c
b
f
3 5
Depuis les sort
tris , il produit:
3 5
a c
b
f
sorted=($(...))
partieLa $(...)
partie, appelée substitution de commande , fait sort <<<"${array[*]}
exécuter son contenu ( ) comme une commande normale, tout en prenant la sortie standard résultante comme le littéral qui va où que ce $(...)
soit.
Dans notre exemple, cela produit quelque chose de similaire à la simple écriture:
sorted=(3 5
a c
b
f
)
sorted
devient alors un tableau créé en divisant ce littéral à chaque nouvelle ligne.
unset IFS
Cela réinitialise la valeur de IFS
à la valeur par défaut et constitue simplement une bonne pratique.
C'est pour nous assurer que nous ne causons aucun problème avec tout ce qui s'appuie IFS
plus tard dans notre script. (Sinon, nous devons nous rappeler que nous avons changé les choses - ce qui pourrait être peu pratique pour les scripts complexes.)
IFS
, il divise vos éléments en petits morceaux s'ils ne contiennent qu'un seul type d'espaces blancs. Bien; pas parfait :-)
unset IFS
nécessaire? Je pensais que le préfixe IFS=
à une commande ne concernait que la modification de cette commande, revenant à sa valeur précédente automatiquement par la suite.
sorted=()
n'est pas une commande mais plutôt une deuxième affectation de variable.
Réponse originale:
array=(a c b "f f" 3 5)
readarray -t sorted < <(for a in "${array[@]}"; do echo "$a"; done | sort)
production:
$ for a in "${sorted[@]}"; do echo "$a"; done
3
5
a
b
c
f f
Notez que cette version gère les valeurs contenant des caractères spéciaux ou des espaces ( sauf les retours à la ligne)
Remarque readarray est pris en charge dans bash 4+.
Modifier Sur la base de la suggestion de @Dimitre, je l'avais mis à jour pour:
readarray -t sorted < <(printf '%s\0' "${array[@]}" | sort -z | xargs -0n1)
qui a l'avantage de même comprendre les éléments de tri avec des caractères de nouvelle ligne incorporés correctement. Malheureusement, comme correctement signalé par @ruakh, cela ne signifiait pas que le résultat de readarray
serait correct , car readarray
il n'y a pas d'option à utiliser à la NUL
place de nouvelles lignes régulières comme séparateurs de ligne.
readarray -t sorted < <(printf '%s\n' "${array[@]}" | sort)
sort -z
c'est une amélioration utile, je suppose que l' -z
option est une extension de tri GNU.
sorted=(); while read -d $'\0' elem; do sorted[${#sorted[@]}]=$elem; done < <(printf '%s\0' "${array[@]}" | sort -z)
. Cela fonctionne également lorsque vous utilisez bash v3 au lieu de bash v4, car readarray n'est pas disponible dans bash v3.
<
) combinée avec la substitution de processus <(...)
. Ou pour le dire intuitivement: parce que ce (printf "bla")
n'est pas un fichier.
Voici une implémentation pure de tri rapide 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[@]}" )
}
Utiliser comme, par exemple,
$ 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")'
Cette implémentation est récursive ... alors voici un tri rapide itératif:
#!/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
}
Dans les deux cas, vous pouvez changer l'ordre que vous utilisez: j'ai utilisé des comparaisons de chaînes, mais vous pouvez utiliser des comparaisons arithmétiques, comparer l'heure de modification du fichier, etc. utilisez simplement le test approprié; vous pouvez même le rendre plus générique et lui faire utiliser un premier argument qui est l'utilisation de la fonction de test, par exemple,
#!/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
}
Ensuite, vous pouvez avoir cette fonction de comparaison:
compare_mtime() { [[ $1 -nt $2 ]]; }
et utilise:
$ qsort compare_mtime *
$ declare -p qsort_ret
pour que les fichiers du dossier actuel soient triés par heure de modification (le plus récent en premier).
REMARQUE. Ces fonctions sont du pur Bash! pas d'utilitaires externes et pas de sous-shell! ils sont sûrs pour tous les symboles amusants que vous pourriez avoir (espaces, caractères de nouvelle ligne, caractères globaux, etc.).
sort
est suffisant, une solution sort
+ read -a
sera plus rapide à partir d'environ, disons, 20 éléments, et de plus en plus rapidement avec plus d'éléments que vous traitez. Par exemple, sur mon iMac de fin 2012 sous OSX 10.11.1 avec un Fusion Drive: matrice de 100 éléments: env. 0,03 s sec. ( qsort()
) contre ca. 0,005 s. ( sort
+ read -a
); Tableau de 1000 éléments: env. 0,375 s. ( qsort()
) contre ca. 0,014 s ( sort
+ read -a
).
if [ "$i" -lt "$pivot" ]; then
c'était nécessaire, sinon le "2" <"10" résolu retournait vrai. Je crois que c'est POSIX contre lexicographique; ou peut-être Inline Link .
Si vous n'avez pas besoin de gérer des caractères shell spéciaux dans les éléments du tableau:
array=(a c b f 3 5)
sorted=($(printf '%s\n' "${array[@]}"|sort))
Avec bash, vous aurez de toute façon besoin d'un programme de tri externe.
Avec zsh, aucun programme externe n'est nécessaire et les caractères spéciaux du shell sont facilement gérés:
% array=('a a' c b f 3 5); printf '%s\n' "${(o)array[@]}"
3
5
a a
b
c
f
ksh doit set -s
trier ASCIIbétiquement .
set -A array x 'a a' d; set -s -- "${array[@]}"; set -A sorted "$@"
Et, bien sûr, la commande set réinitialisera les paramètres de position actuels, le cas échéant.
tl; dr :
Trier le tableau a_in
et stocker le résultat dans a_out
(les éléments ne doivent pas avoir de sauts de ligne incorporés [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)
Avantages par rapport à la solution d'antak :
Vous n'avez pas à vous soucier du globbing accidentel (interprétation accidentelle des éléments du tableau en tant que modèles de nom de fichier), donc aucune commande supplémentaire n'est nécessaire pour désactiver le globbing ( set -f
et set +f
pour le restaurer plus tard).
Vous n'avez pas à vous soucier de la réinitialisation IFS
avec unset IFS
. [2]
Ce qui précède combine le code Bash avec un utilitaire externe sort
pour une solution qui fonctionne avec des éléments à une seule ligne arbitraires et un tri lexical ou numérique (éventuellement par champ) :
Performances : pour environ 20 éléments ou plus , ce sera plus rapide qu'une solution pure Bash - de manière significative et de plus en plus une fois que vous aurez dépassé les 100 éléments.
(Les seuils exacts dépendront de votre entrée, de votre machine et de votre plate-forme spécifiques.)
printf '%s\n' "${a_in[@]}" | sort
effectue le tri (lexicalement, par défaut - voir sort
les spécifications POSIX de ):
"${a_in[@]}"
se développe en toute sécurité aux éléments du tableau en a_in
tant qu'arguments individuels , quels qu'ils soient (y compris les espaces).
printf '%s\n'
puis imprime chaque argument - c'est-à-dire, chaque élément du tableau - sur sa propre ligne, tel quel.
Notez l' utilisation d'une substitution de processus ( <(...)
) pour fournir la sortie triée comme entrée vers read
/ readarray
(via la redirection vers stdin, <
), car read
/ readarray
doit s'exécuter dans le shell actuel (ne doit pas s'exécuter dans un sous - shell ) pour que la variable de sortie a_out
soit visible au shell courant (pour que la variable reste définie dans le reste du script).
Lecture de sort
la sortie dans une variable tableau :
Bash v4 +: readarray -t a_out
lit les lignes individuelles produites par sort
dans les éléments de la variable de tableau a_out
, sans inclure la fin \n
dans chaque élément ( -t
).
Bash v3: readarray
n'existe pas, donc read
doit être utilisé:
IFS=$'\n' read -d '' -r -a a_out
dit read
de lire dans la -a
variable array ( ) a_out
, lisant l'entrée entière, à travers les lignes ( -d ''
), mais en le divisant en éléments de tableau par des retours à la ligne ( IFS=$'\n'
. $'\n'
, Ce qui produit une nouvelle ligne littérale (LF ), est une chaîne dite ANSI C-quoted string ).
( -r
, une option qui devrait pratiquement toujours être utilisée avec read
, désactive la gestion inattendue des \
caractères.)
Exemple de code annoté:
#!/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[@]}"
En raison de l'utilisation de l' sort
option sans option, cela produit un tri lexical (les chiffres sont triés avant les lettres et les séquences de chiffres sont traitées lexicalement, pas comme des nombres):
*
10
5
a c
b
f
Si vous vouliez un tri numérique par le premier champ, vous utiliseriez sort -k1,1n
au lieu de juste sort
, ce qui donne (les non-nombres trient avant les nombres et les nombres trient correctement):
*
a c
b
f
5
10
[1] à des éléments de poignée avec des sauts de ligne incorporés, utiliser la variante suivante (Bash v4 +, avec GNU sort
)
readarray -d '' -t a_out < <(printf '%s\0' "${a_in[@]}" | sort -z)
.
La réponse utile de Michał Górny a une solution Bash v3.
[2] Tant qu'il IFS
est défini dans la variante Bash v3, le changement est limité à la commande .
En revanche, ce qui suit IFS=$'\n'
dans la réponse d'Antak est une affectation plutôt qu'une commande, auquel cas le IFS
changement est global .
Dans le voyage de 3 heures en train de Munich à Francfort (que j'ai eu du mal à atteindre car l'Oktoberfest commence demain), je pensais à mon premier message. L'utilisation d'un tableau global est une bien meilleure idée pour une fonction de tri générale. La fonction suivante gère les chaînes arbitraires (retours à la ligne, blancs, 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[@]}
Cela imprime:
3 5 a b c z y
La même sortie est créée à partir de
BSORT=(a c b 'z y' 3 5)
bubble_sort
echo ${BSORT[@]}
Notez que Bash utilise probablement en interne des pointeurs intelligents, de sorte que l'opération d'échange pourrait être bon marché (bien que j'en doute). Cependant, bubble_sort
démontre que des fonctions plus avancées comme merge_sort
sont également à la portée du langage shell.
local -n BSORT="$1"
à- dire au début de la fonction. Ensuite, vous pouvez exécuter bubble_sort myarray
pour trier mon tableau .
Une autre solution qui utilise externe sort
et gère tous les caractères spéciaux (sauf pour les NUL :)). Devrait fonctionner avec bash-3.2 et GNU ou BSD sort
(malheureusement, POSIX n'inclut pas -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)
Regardez d'abord la redirection d'entrée à la fin. Nous utilisons printf
intégré pour écrire les éléments du tableau, terminés par zéro. Les guillemets garantissent que les éléments du tableau sont passés tels quels, et les spécificités du shell le printf
font réutiliser la dernière partie de la chaîne de format pour chaque paramètre restant. Autrement dit, c'est équivalent à quelque chose comme:
for e in "${array[@]}"; do
printf "%s\0" "${e}"
done
La liste d'éléments terminés par null est ensuite transmise à sort
. L' -z
option l'oblige à lire les éléments terminés par NULL, à les trier et à afficher également les éléments terminés par NULL. Si vous n'aviez besoin que des éléments uniques, vous pouvez passer -u
car il est plus portable que uniq -z
. Le LC_ALL=C
garantit un ordre de tri stable indépendamment des paramètres régionaux - parfois utile pour les scripts. Si vous voulez que le sort
respecte les paramètres régionaux, supprimez-le.
La <()
construction obtient le descripteur à lire à partir du pipeline généré et y <
redirige l'entrée standard de la while
boucle. Si vous avez besoin d'accéder à l'entrée standard à l'intérieur du tube, vous pouvez utiliser un autre descripteur - exercice pour le lecteur :).
Maintenant, revenons au début. L' read
intégré lit la sortie du stdin redirigé. La définition de vide IFS
désactive la division de mot qui n'est pas nécessaire ici - par conséquent, read
lit toute la «ligne» d'entrée dans la variable fournie unique. -r
L'option désactive également le traitement d'échappement qui n'est pas souhaité ici. Enfin, -d ''
définit le délimiteur de ligne sur NUL - c'est-à-dire, indique read
de lire les chaînes terminées par zéro.
En conséquence, la boucle est exécutée une fois pour chaque élément de tableau terminé par zéro successif, la valeur étant stockée dans e
. L'exemple place simplement les éléments dans un autre tableau mais vous préférerez peut-être les traiter directement :).
Bien sûr, ce n'est qu'une des nombreuses façons d'atteindre le même objectif. Selon moi, c'est plus simple que d'implémenter un algorithme de tri complet dans bash et dans certains cas, ce sera plus rapide. Il gère tous les caractères spéciaux, y compris les retours à la ligne, et devrait fonctionner sur la plupart des systèmes courants. Plus important encore, cela peut vous apprendre quelque chose de nouveau et de génial à propos de bash :).
e
et de définir un IFS vide, utilisez la variable REPLY.
essaye ça:
echo ${array[@]} | awk 'BEGIN{RS=" ";} {print $1}' | sort
La sortie sera:
3 5 une b c F
Problème résolu.
Si vous pouvez calculer un entier unique pour chaque élément du tableau, comme ceci:
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 -> $?"
alors, vous pouvez utiliser ces entiers comme index de tableau, car Bash utilise toujours un tableau fragmenté, donc pas besoin de vous soucier des index inutilisés:
array=(a c b f 3 5)
for el in "${array[@]}"; do
sexy_int "$el"
sorted[$?]="$el"
done
echo "${sorted[@]}"
tri min:
#!/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
Il existe une solution de contournement pour le problème habituel des espaces et des retours à la ligne:
Utilisez un caractère qui n'est pas dans le tableau d'origine (comme $'\1'
ou$'\4'
ou similaire).
Cette fonction fait le travail:
# 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
}
Cela triera le tableau:
$ array=( a b 'c d' $'e\nf' $'g\1h')
$ sortarray "${array[@]}"
$ printf '<%s>\n' "${sorted[@]}"
<a>
<b>
<c d>
<e
f>
<gh>
Cela se plaindra que le tableau source contient le caractère de contournement:
$ array=( a b 'c d' $'e\nf' $'g\4h')
$ sortarray "${array[@]}"
./script: error: array contains the workaround char
wa
(char de contournement) et un IFS nul$*
.[[ $* =~ [$wa] ]]
.exit 1
set -f
IFS=$'\n'
), une variable de boucle x
et une nouvelle ligne var ( nl=$'\n'
).$@
)."${@//$nl/$wa}"
.sort -n
.set --
.for x
sorted+=(…)
"${x//$wa/$nl}"
.Cette question semble étroitement liée. Et BTW, voici un mergesort dans Bash (sans processus externes):
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[@]}"
Je ne suis pas convaincu que vous aurez besoin d'un programme de tri externe dans Bash.
Voici mon implémentation pour l'algorithme de tri à bulles simple.
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[@]})"
Cela doit imprimer:
input: a c b f 3 5
output: 3 5 a b c f
O(n^2)
. Il me semble que la plupart des algorithmes de tri utilisent un O(n lg(n))
jusqu'à la douzaine d'éléments finaux. Pour les éléments finaux, le tri par sélection est utilisé.
a=(e b 'c d')
shuf -e "${a[@]}" | sort >/tmp/f
mapfile -t g </tmp/f
sorted=($(echo ${array[@]} | tr " " "\n" | sort))
Dans l'esprit de bash / linux, je dirigerais le meilleur outil de ligne de commande pour chaque étape. sort
fait le travail principal mais a besoin d'une entrée séparée par une nouvelle ligne au lieu d'un espace, donc le pipeline très simple ci-dessus fait simplement:
Contenu du tableau d'écho -> remplacer l'espace par une nouvelle ligne -> trier
$()
est de faire écho au résultat
($())
est de mettre le "résultat écho" dans un tableau
Remarque : comme @sorontar l'a mentionné dans un commentaire à une autre question:
La partie triée = ($ (...)) utilise l'opérateur "split and glob". Vous devez désactiver glob: set -f ou set -o noglob ou shopt -op noglob ou un élément du tableau comme * sera développé en une liste de fichiers.
mapfile -t sorted < <(printf '%s\n' "${array[@]}" | sort)
sinon sorted=(); while IFS= read -r line; do sorted+=( "$line" ); done < <(printf '%s\n' | sort)
.
echo ${array[@]} | tr " " "\n"
cela se cassera si les champs du tableau contiennent des espaces et des caractères glob. De plus, il génère un sous-shell et utilise une commande externe inutile. Et en raison d' echo
être stupide, il se cassera si votre tableau commence par -e
, -E
ou -n
. Au lieu d' utiliser: printf '%s\n' "${array[@]}"
. L'autre antipattern est: ($())
est de mettre le "résultat écho" dans un tableau . Certainement pas! c'est un horrible anti-modèle qui se brise à cause de l'expansion du chemin (globbing) et de la division des mots. N'utilisez jamais cette horreur.
IFS
, cela divisera vos éléments en petits morceaux s'ils contiennent des espaces blancs. Essayez le par exemple avecIFS=$'\n'
omis et voyez!