Antworten:
Sie brauchen nicht wirklich so viel Code:
IFS=$'\n' sorted=($(sort <<<"${array[*]}"))
unset IFS
Unterstützt Leerzeichen in Elementen (solange es sich nicht um eine neue Zeile handelt) und funktioniert in Bash 3.x.
z.B:
$ array=("a c" b f "3 5")
$ IFS=$'\n' sorted=($(sort <<<"${array[*]}")); unset IFS
$ printf "[%s]\n" "${sorted[@]}"
[3 5]
[a c]
[b]
[f]
Hinweis: @sorontar hat darauf hingewiesen, dass Vorsicht geboten ist, wenn Elemente Platzhalter wie *
oder enthalten ?
:
Der sortierte = ($ (...)) Teil verwendet den Operator "split and glob". Sie sollten glob deaktivieren:
set -f
oderset -o noglob
odershopt -op noglob
oder ein Element des Arrays wie*
wird zu einer Liste von Dateien erweitert.
Das Ergebnis ist ein Höhepunkt von sechs Dingen, die in dieser Reihenfolge geschehen:
IFS=$'\n'
"${array[*]}"
<<<
sort
sorted=($(...))
unset IFS
IFS=$'\n'
Dies ist ein wichtiger Teil unserer Operation, der das Ergebnis von 2 und 5 folgendermaßen beeinflusst:
Gegeben:
"${array[*]}"
Erweitert sich auf jedes Element, das durch das erste Zeichen von begrenzt ist IFS
sorted=()
Erstellt Elemente durch Aufteilen auf jedes Zeichen von IFS
IFS=$'\n'
Richtet die Dinge so ein, dass Elemente mit einer neuen Zeile als Trennzeichen erweitert und später so erstellt werden, dass jede Zeile zu einem Element wird. (dh Aufteilen in eine neue Zeile.)
Das Abgrenzen durch eine neue Zeile ist wichtig, da dies so sort
funktioniert (Sortieren pro Zeile). Das Teilen durch nur eine neue Zeile ist nicht so wichtig, muss jedoch Elemente beibehalten, die Leerzeichen oder Tabulatoren enthalten.
Der Standardwert von IFS
ist ein Leerzeichen , eine Registerkarte , gefolgt von einer neuen Zeile , und wäre für unseren Vorgang nicht geeignet.
sort <<<"${array[*]}"
Teil<<<
, hier Strings genannt , nimmt die Erweiterung von "${array[*]}"
, wie oben erläutert, und speist sie in die Standardeingabe von ein sort
.
In unserem Beispiel sort
wird folgende Zeichenfolge eingegeben:
a c
b
f
3 5
Da sort
sortiert , produziert es:
3 5
a c
b
f
sorted=($(...))
TeilDer $(...)
Teil, der als Befehlssubstitution bezeichnet wird , bewirkt, dass content ( sort <<<"${array[*]}
) wie ein normaler Befehl ausgeführt wird, während die resultierende Standardausgabe als Literal verwendet wird, das dorthin geht, wo immer es $(...)
war.
In unserem Beispiel ergibt dies etwas Ähnliches wie das einfache Schreiben:
sorted=(3 5
a c
b
f
)
sorted
wird dann zu einem Array, das durch Aufteilen dieses Literals in jede neue Zeile erstellt wird.
unset IFS
Dies setzt den Wert von IFS
auf den Standardwert zurück und ist nur eine gute Vorgehensweise.
Es soll sicherstellen, dass wir keine Probleme mit irgendetwas verursachen, auf das wir uns IFS
später in unserem Skript stützen. (Andernfalls müssten wir uns daran erinnern, dass wir die Dinge umgestellt haben - etwas, das für komplexe Skripte möglicherweise unpraktisch ist.)
IFS
teilt es Ihre Elemente in kleine Teile auf, wenn sie nur eine bestimmte Art von Leerzeichen enthalten. Gut; nicht perfekt :-)
unset IFS
notwendig? Ich dachte, das Voranstellen IFS=
eines Befehls umfasste nur die Änderung dieses Befehls und kehrte danach automatisch zum vorherigen Wert zurück.
sorted=()
es sich nicht um einen Befehl handelt, sondern um eine zweite Variablenzuweisung.
Ursprüngliche Antwort:
array=(a c b "f f" 3 5)
readarray -t sorted < <(for a in "${array[@]}"; do echo "$a"; done | sort)
Ausgabe:
$ for a in "${sorted[@]}"; do echo "$a"; done
3
5
a
b
c
f f
Beachten Sie, dass diese Version mit Werten umgeht , die Sonderzeichen oder Leerzeichen enthalten ( außer Zeilenumbrüche).
Hinweis: Readarray wird in Bash 4+ unterstützt.
Bearbeiten Basierend auf dem Vorschlag von @Dimitre hatte ich es aktualisiert auf:
readarray -t sorted < <(printf '%s\0' "${array[@]}" | sort -z | xargs -0n1)
Dies hat den Vorteil, dass Sortierelemente mit korrekt eingebetteten Zeilenumbruchszeichen sogar verstanden werden. Leider, wie korrekt signalisiert @ruakh bedeute dies nicht , das das Ergebnis readarray
wäre richtig , weil readarray
keine andere Wahl hat zu verwenden , die NUL
anstelle der normalen Zeilenumbrüche als Line-Separatoren.
readarray -t sorted < <(printf '%s\n' "${array[@]}" | sort)
sort -z
ist eine nützliche Verbesserung. Ich nehme an, die -z
Option ist eine GNU-Sortiererweiterung.
sorted=(); while read -d $'\0' elem; do sorted[${#sorted[@]}]=$elem; done < <(printf '%s\0' "${array[@]}" | sort -z)
. Dies funktioniert auch, wenn Sie bash v3 anstelle von bash v4 verwenden, da Readarray in bash v3 nicht verfügbar ist.
<
) kombiniert mit Prozessersetzung <(...)
. Oder intuitiv ausgedrückt: weil (printf "bla")
es keine Datei ist.
Hier ist eine reine Bash-Quicksort-Implementierung:
#!/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[@]}" )
}
Verwenden Sie als z.
$ 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")'
Diese Implementierung ist rekursiv. Hier ist eine iterative Quicksortierung:
#!/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
}
In beiden Fällen können Sie die Reihenfolge ändern, die Sie verwenden: Ich habe Zeichenfolgenvergleiche verwendet, aber Sie können arithmetische Vergleiche verwenden, die Änderungszeit der WRT-Datei vergleichen usw. Verwenden Sie einfach den entsprechenden Test. Sie können es sogar allgemeiner gestalten und ein erstes Argument verwenden lassen, das die Testfunktion verwendet, z.
#!/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
}
Dann können Sie diese Vergleichsfunktion haben:
compare_mtime() { [[ $1 -nt $2 ]]; }
und verwenden:
$ qsort compare_mtime *
$ declare -p qsort_ret
Damit die Dateien im aktuellen Ordner nach Änderungszeit sortiert werden (neueste zuerst).
HINWEIS. Diese Funktionen sind reine Bash! Keine externen Dienstprogramme und keine Subshells! Sie sind sicher für alle lustigen Symbole, die Sie möglicherweise haben (Leerzeichen, Zeilenumbruchzeichen, Glob-Zeichen usw.).
sort
ausreicht, ist eine sort
+ read -a
-Lösung ab etwa 20 Elementen schneller und umso schneller, je mehr Elemente Sie bearbeiten. Beispiel: Auf meinem iMac Ende 2012 wird OSX 10.11.1 mit einem Fusion Drive ausgeführt: Array mit 100 Elementen: Ca. 0,03 s sek. ( qsort()
) vs. 0,005 Sekunden. ( sort
+ read -a
); 1000-Elemente-Array: Ca. 0,375 Sekunden ( qsort()
) vs. 0,014 Sekunden ( sort
+ read -a
).
if [ "$i" -lt "$pivot" ]; then
war andernfalls die aufgelöste "2" <"10" true zurückgegeben. Ich glaube, das ist POSIX vs. Lexicographical; oder vielleicht Inline Link .
Wenn Sie keine speziellen Shell-Zeichen in den Array-Elementen verarbeiten müssen:
array=(a c b f 3 5)
sorted=($(printf '%s\n' "${array[@]}"|sort))
Mit bash benötigen Sie ohnehin ein externes Sortierprogramm.
Mit zsh werden keine externen Programme benötigt und spezielle Shell-Zeichen sind einfach zu handhaben:
% array=('a a' c b f 3 5); printf '%s\n' "${(o)array[@]}"
3
5
a a
b
c
f
ksh muss ASCIIbetischset -s
sortieren .
set -A array x 'a a' d; set -s -- "${array[@]}"; set -A sorted "$@"
Und natürlich setzt der Befehl set die aktuellen Positionsparameter zurück, falls vorhanden.
tl; dr :
Sortieren Sie das Array a_in
und speichern Sie das Ergebnis in a_out
(Elemente dürfen keine eingebetteten Zeilenumbrüche enthalten [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)
Vorteile gegenüber der Lösung von Antak :
Sie müssen sich keine Gedanken über versehentliches Globbing machen (versehentliche Interpretation der Array-Elemente als Dateinamenmuster), sodass kein zusätzlicher Befehl erforderlich ist, um das Globbing zu deaktivieren ( set -f
und set +f
es später wiederherzustellen).
Sie müssen sich keine Gedanken über das Zurücksetzen IFS
mit machen unset IFS
. [2]
Der oben Mähdrescher Bash Code mit externem Dienstprogramm sort
für eine Lösung , die Arbeiten mit beliebigen einzelnen -Linie Elementen und entweder lexikalischer oder numerischer Sortier (gegebenenfalls durch Feld) :
Leistung : Bei etwa 20 Elementen oder mehr ist dies schneller als bei einer reinen Bash-Lösung - deutlich und zunehmend, sobald Sie mehr als 100 Elemente erreicht haben.
(Die genauen Schwellenwerte hängen von Ihrer spezifischen Eingabe, Maschine und Plattform ab.)
printf '%s\n' "${a_in[@]}" | sort
führt die Sortierung durch (standardmäßig lexikalisch - siehe sort
POSIX-Spezifikation ):
"${a_in[@]}"
Erweitert sich sicher auf die Elemente des Arrays a_in
als einzelne Argumente , unabhängig davon, was sie enthalten (einschließlich Leerzeichen).
printf '%s\n'
druckt dann jedes Argument - dh jedes Array-Element - unverändert in einer eigenen Zeile.
Beachten Sie die Verwendung einer Prozesssubstitution ( <(...)
) , um die sortierte Ausgabe als Eingabe für read
/ readarray
(über die Umleitung zu stdin ) bereitzustellen <
, da read
/ readarray
in der aktuellen Shell ausgeführt werden muss (darf nicht in einer Subshell ausgeführt werden ), damit die Ausgabevariable a_out
sichtbar ist auf die aktuelle Shell (damit die Variable im Rest des Skripts definiert bleibt).
Lesen sort
der Ausgabe in eine Array-Variable :
Bash v4 +: readarray -t a_out
Liest die einzelnen Zeilen aus, die von sort
in die Elemente der Array-Variablen ausgegeben werden a_out
, ohne die Nachfolge \n
in jedes Element ( -t
) aufzunehmen.
Bash v3: readarray
existiert nicht, read
muss also verwendet werden:
IFS=$'\n' read -d '' -r -a a_out
weist read
an, in die -a
Variable array ( ) zu lesen a_out
, die gesamte Eingabe über Zeilen ( -d ''
) zu lesen , sie jedoch durch Zeilenumbrüche ( IFS=$'\n'
. ) In Array-Elemente aufzuteilen $'\n'
, wodurch eine wörtliche Zeilenumbruch (LF) erzeugt wird ) ist eine sogenannte ANSI C-Zeichenfolge ).
( -r
Eine Option, die praktisch immer verwendet werden sollte read
, deaktiviert die unerwartete Behandlung von \
Zeichen.)
Kommentierter Beispielcode:
#!/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[@]}"
Aufgrund der Verwendung von sort
ohne Optionen ergibt sich eine lexikalische Sortierung (Ziffernsortierung vor Buchstaben und Ziffernfolgen werden lexikalisch und nicht als Zahlen behandelt):
*
10
5
a c
b
f
Wenn Sie eine numerische Sortierung nach dem 1. Feld wünschen, verwenden Sie sort -k1,1n
anstelle von nur sort
, was ergibt (Nicht-Zahlen werden vor Zahlen sortiert und Zahlen werden korrekt sortiert):
*
a c
b
f
5
10
[1] Verwenden Sie zum Behandeln von Elementen mit eingebetteten Zeilenumbrüchen die folgende Variante (Bash v4 + mit GNU sort
) :
readarray -d '' -t a_out < <(printf '%s\0' "${a_in[@]}" | sort -z)
.
Die hilfreiche Antwort von Michał Górny enthält eine Bash v3-Lösung.
[2] Während IFS
wird in der Schlag - v3 Variante gesetzt, wird die Änderung auf den Befehl scoped .
Im Gegensatz dazu folgt IFS=$'\n'
in Antaks Antwort eher eine Zuweisung als ein Befehl. In diesem Fall ist die IFS
Änderung global .
Bei der dreistündigen Zugfahrt von München nach Frankfurt (die ich nur schwer erreichen konnte, weil das Oktoberfest morgen beginnt) dachte ich an meinen ersten Beitrag. Die Verwendung eines globalen Arrays ist eine viel bessere Idee für eine allgemeine Sortierfunktion. Die folgende Funktion verarbeitet beliebige Zeichenfolgen (Zeilenumbrüche, Leerzeichen usw.):
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[@]}
Dies druckt:
3 5 a b c z y
Die gleiche Ausgabe wird aus erstellt
BSORT=(a c b 'z y' 3 5)
bubble_sort
echo ${BSORT[@]}
Beachten Sie, dass wahrscheinlich Bash intern Smart-Pointer verwendet, so dass der Swap-Betrieb könnte billig sein (obwohl ich bezweifle es). Zeigt jedoch, bubble_sort
dass erweiterte Funktionen wie merge_sort
auch in der Reichweite der Shell-Sprache liegen.
local -n BSORT="$1"
zu Beginn der Funktion. Dann können Sie laufen bubble_sort myarray
, um Myarray zu sortieren .
Eine weitere Lösung , die externe verwendet sort
und meistert mit allen Sonderzeichen (außer NULs :)). Sollte mit bash-3.2 und GNU oder BSD sort
funktionieren (leider enthält POSIX nicht -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)
Schauen Sie sich zuerst die Eingabeumleitung am Ende an. Wir verwenden die printf
eingebauten Array-Elemente mit Nullterminierung. Durch das Anführungszeichen wird sichergestellt, dass Array-Elemente unverändert übergeben werden. Durch die Besonderheiten der Shell wird printf
der letzte Teil der Formatzeichenfolge für jeden verbleibenden Parameter wiederverwendet. Das heißt, es entspricht so etwas wie:
for e in "${array[@]}"; do
printf "%s\0" "${e}"
done
Die nullterminierte Elementliste wird dann an übergeben sort
. Die -z
Option bewirkt, dass nullterminierte Elemente gelesen, sortiert und auch nullterminiert ausgegeben werden. Wenn Sie nur die eindeutigen Elemente benötigen, können Sie übergeben, -u
da es portabler ist als uniq -z
. Das LC_ALL=C
sorgt für eine stabile Sortierreihenfolge unabhängig von locale - manchmal nützlich für Skripte. Wenn Sie möchten, dass das sort
Gebietsschema respektiert wird, entfernen Sie das.
Das <()
Konstrukt erhält den zu lesenden Deskriptor aus der erzeugten Pipeline und <
leitet die Standardeingabe der while
Schleife an diese weiter. Wenn Sie auf die Standardeingabe in der Pipe zugreifen müssen, können Sie eine andere Deskriptorübung für den Leser verwenden :).
Nun zurück zum Anfang. Das read
eingebaute liest die Ausgabe vom umgeleiteten Standard. Wenn Sie leer setzen, wird die IFS
Wortaufteilung deaktiviert, was hier nicht read
erforderlich ist. Infolgedessen wird die gesamte Eingabezeile in die einzelne bereitgestellte Variable gelesen. -r
Option deaktiviert die Escape-Verarbeitung, die auch hier unerwünscht ist. Setzt schließlich -d ''
den Zeilenbegrenzer auf NUL - das heißt, es wird read
angewiesen, Zeichenfolgen mit Nulltermin zu lesen.
Infolgedessen wird die Schleife einmal für jedes aufeinanderfolgende nullterminierte Array-Element ausgeführt, wobei der Wert in gespeichert wird e
. Das Beispiel fügt die Elemente nur in ein anderes Array ein, aber Sie können es vorziehen, sie direkt zu verarbeiten :).
Dies ist natürlich nur eine der vielen Möglichkeiten, dasselbe Ziel zu erreichen. Aus meiner Sicht ist es einfacher als die Implementierung eines vollständigen Sortieralgorithmus in Bash und in einigen Fällen schneller. Es behandelt alle Sonderzeichen einschließlich Zeilenumbrüchen und sollte auf den meisten gängigen Systemen funktionieren. Am wichtigsten ist, dass es Ihnen etwas Neues und Fantastisches über Bash beibringen kann :).
e
Verwenden Sie die Variable REPLY, anstatt eine lokale Variable einzuführen und ein leeres IFS festzulegen.
Versuche dies:
echo ${array[@]} | awk 'BEGIN{RS=" ";} {print $1}' | sort
Ausgabe wird sein:
3 5 ein b c f
Problem gelöst.
Wenn Sie für jedes Element im Array eine eindeutige Ganzzahl berechnen können, gehen Sie wie folgt vor:
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 -> $?"
Dann können Sie diese Ganzzahlen als Array-Indizes verwenden, da Bash immer ein Array mit geringer Dichte verwendet, sodass Sie sich keine Gedanken über nicht verwendete Indizes machen müssen:
array=(a c b f 3 5)
for el in "${array[@]}"; do
sexy_int "$el"
sorted[$?]="$el"
done
echo "${sorted[@]}"
min sort:
#!/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
Es gibt eine Problemumgehung für das übliche Problem von Leerzeichen und Zeilenumbrüchen:
Verwenden , um ein Zeichen , das nicht in der ursprünglichen Anordnung ist (wie $'\1'
oder $'\4'
oder ähnliches).
Diese Funktion erledigt den Job:
# 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
}
Dadurch wird das Array sortiert:
$ array=( a b 'c d' $'e\nf' $'g\1h')
$ sortarray "${array[@]}"
$ printf '<%s>\n' "${sorted[@]}"
<a>
<b>
<c d>
<e
f>
<gh>
Dadurch wird beanstandet, dass das Quellarray das Problemumgehungszeichen enthält:
$ array=( a b 'c d' $'e\nf' $'g\4h')
$ sortarray "${array[@]}"
./script: error: array contains the workaround char
wa
(Workaround-Zeichen) und ein Null-IFS$*
.[[ $* =~ [$wa] ]]
.exit 1
set -f
IFS=$'\n'
), eine Schleifenvariable x
und eine Newline var ( nl=$'\n'
).$@
)."${@//$nl/$wa}"
.sort -n
.set --
.for x
sorted+=(…)
"${x//$wa/$nl}"
.Diese Frage sieht eng verwandt aus. Übrigens, hier ist ein Mergesort in Bash (ohne externe Prozesse):
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[@]}"
Ich bin nicht davon überzeugt, dass Sie in Bash ein externes Sortierprogramm benötigen.
Hier ist meine Implementierung für den einfachen Blasensortierungsalgorithmus.
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[@]})"
Dies soll drucken:
input: a c b f 3 5
output: 3 5 a b c f
O(n^2)
. Ich erinnere mich an die meisten Sortieralgorithmen, die O(n lg(n))
bis zum letzten Dutzend Elemente oder so verwenden. Für die letzten Elemente wird die Auswahlsortierung verwendet.
a=(e b 'c d')
shuf -e "${a[@]}" | sort >/tmp/f
mapfile -t g </tmp/f
sorted=($(echo ${array[@]} | tr " " "\n" | sort))
Im Geiste von Bash / Linux würde ich für jeden Schritt das beste Befehlszeilen-Tool verwenden. sort
erledigt die Hauptaufgabe, benötigt jedoch eine durch Zeilenumbruch getrennte Eingabe anstelle von Leerzeichen, sodass die oben beschriebene sehr einfache Pipeline einfach Folgendes tut:
Echo-Array-Inhalt -> Leerzeichen durch Zeilenumbruch ersetzen -> sortieren
$()
ist das Ergebnis zu wiederholen
($())
ist das "Echo-Ergebnis" in ein Array zu setzen
Hinweis : Wie @sorontar in einem Kommentar zu einer anderen Frage erwähnt hat:
Der sortierte = ($ (...)) Teil verwendet den Operator "split and glob". Sie sollten glob ausschalten: set -f oder set -o noglob oder shopt -op noglob oder ein Element des Arrays wie * wird zu einer Liste von Dateien erweitert.
mapfile -t sorted < <(printf '%s\n' "${array[@]}" | sort)
sonst sorted=(); while IFS= read -r line; do sorted+=( "$line" ); done < <(printf '%s\n' | sort)
.
echo ${array[@]} | tr " " "\n"
Dies wird unterbrochen, wenn die Felder des Arrays Leerzeichen und Glob-Zeichen enthalten. Außerdem erzeugt es eine Unterschale und verwendet einen nutzlosen externen Befehl. Und aufgrund echo
ist stumm, wird es brechen , wenn Ihr Array mit beginnt -e
, -E
oder -n
. Verwenden Sie stattdessen : printf '%s\n' "${array[@]}"
. Das andere Antimuster ist: ($())
Das "Echo-Ergebnis" wird in ein Array eingefügt . Sicherlich nicht! Dies ist ein schreckliches Antimuster, das aufgrund der Pfadnamenerweiterung (Globbing) und der Wortteilung zerbricht. Benutze niemals diesen Horror.
IFS
, es wird Ihre Elemente in kleine Teile teilen, wenn sie Leerzeichen enthalten. Versuchen Sie das zB mitIFS=$'\n'
weggelassen und sehen Sie!