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
ou set -o noglob
ou shopt -op noglob
ou un élément du tableau comme *
sera développé en une liste de fichiers.
Que ce passe-t-il:
Le résultat est un aboutissement de six choses qui se produisent dans cet ordre:
IFS=$'\n'
"${array[*]}"
<<<
sort
sorted=($(...))
unset IFS
Premièrement la 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.
Ensuite, la 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
Ensuite, la sorted=($(...))
partie
La $(...)
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.
Finalement, le 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
, cela divisera vos éléments en petits morceaux s'ils contiennent des espaces blancs. Essayez le par exemple avecIFS=$'\n'
omis et voyez!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éfixeIFS=
à 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:
production:
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:
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 , carreadarray
il n'y a pas d'option à utiliser à laNUL
place de nouvelles lignes régulières comme séparateurs de ligne.la source
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:
Utiliser comme, par exemple,
Cette implémentation est récursive ... alors voici un tri rapide itératif:
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,
Ensuite, vous pouvez avoir cette fonction de comparaison:
et utilise:
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.).
la source
sort
est suffisant, une solutionsort
+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:
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:
ksh doit
set -s
trier ASCIIbétiquement .la source
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 dansa_out
(les éléments ne doivent pas avoir de sauts de ligne incorporés [1] ):Bash v4 +:
Bash v3:
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
etset +f
pour le restaurer plus tard).Vous n'avez pas à vous soucier de la réinitialisation
IFS
avecunset IFS
. [2]Lecture facultative: explication et exemple de code
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 - voirsort
les spécifications POSIX de ):"${a_in[@]}"
se développe en toute sécurité aux éléments du tableau ena_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 versread
/readarray
(via la redirection vers stdin,<
), carread
/readarray
doit s'exécuter dans le shell actuel (ne doit pas s'exécuter dans un sous - shell ) pour que la variable de sortiea_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 parsort
dans les éléments de la variable de tableaua_out
, sans inclure la fin\n
dans chaque élément (-t
).Bash v3:
readarray
n'existe pas, doncread
doit être utilisé:IFS=$'\n' read -d '' -r -a a_out
ditread
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 avecread
, désactive la gestion inattendue des\
caractères.)Exemple de code annoté:
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):Si vous vouliez un tri numérique par le premier champ, vous utiliseriez
sort -k1,1n
au lieu de justesort
, ce qui donne (les non-nombres trient avant les nombres et les nombres trient correctement):[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 leIFS
changement est global .la source
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.):
Cela imprime:
La même sortie est créée à partir de
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 commemerge_sort
sont également à la portée du langage shell.la source
local -n BSORT="$1"
à- dire au début de la fonction. Ensuite, vous pouvez exécuterbubble_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 BSDsort
(malheureusement, POSIX n'inclut pas-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 leprintf
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: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 queuniq -z
. LeLC_ALL=C
garantit un ordre de tri stable indépendamment des paramètres régionaux - parfois utile pour les scripts. Si vous voulez que lesort
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 lawhile
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 videIFS
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, indiqueread
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 :).
la source
e
et de définir un IFS vide, utilisez la variable REPLY.essaye ça:
La sortie sera:
Problème résolu.
la source
Si vous pouvez calculer un entier unique pour chaque élément du tableau, comme ceci:
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:
la source
tri min:
la source
Le contenu de l'écho de new_array sera:
la source
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:
Cela triera le tableau:
Cela se plaindra que le tableau source contient le caractère de contournement:
la description
wa
(char de contournement) et un IFS nul$*
.[[ $* =~ [$wa] ]]
.exit 1
set -f
IFS=$'\n'
), une variable de bouclex
et une nouvelle ligne var (nl=$'\n'
).$@
)."${@//$nl/$wa}"
.sort -n
.set --
.for x
sorted+=(…)
"${x//$wa/$nl}"
.la source
Cette question semble étroitement liée. Et BTW, voici un mergesort dans Bash (sans processus externes):
la source
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.
Cela doit imprimer:
la source
O(n^2)
. Il me semble que la plupart des algorithmes de tri utilisent unO(n lg(n))
jusqu'à la douzaine d'éléments finaux. Pour les éléments finaux, le tri par sélection est utilisé.la source
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 tableauRemarque : comme @sorontar l'a mentionné dans un commentaire à une autre question:
la source
mapfile -t sorted < <(printf '%s\n' "${array[@]}" | sort)
sinonsorted=(); 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.