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 -foderset -o noglobodershopt -op nogloboder 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[*]}"<<<sortsorted=($(...))unset IFSIFS=$'\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 IFSsorted=() Erstellt Elemente durch Aufteilen auf jedes Zeichen von IFSIFS=$'\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 sortfunktioniert (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 IFSist 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 sortwird 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 IFSDies setzt den Wert von IFSauf den Standardwert zurück und ist nur eine gute Vorgehensweise.
Es soll sicherstellen, dass wir keine Probleme mit irgendetwas verursachen, auf das wir uns IFSspä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.)
IFSteilt es Ihre Elemente in kleine Teile auf, wenn sie nur eine bestimmte Art von Leerzeichen enthalten. Gut; nicht perfekt :-)
unset IFSnotwendig? 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 readarraywäre richtig , weil readarraykeine andere Wahl hat zu verwenden , die NULanstelle der normalen Zeilenumbrüche als Line-Separatoren.
readarray -t sorted < <(printf '%s\n' "${array[@]}" | sort)
sort -zist eine nützliche Verbesserung. Ich nehme an, die -zOption 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.).
sortausreicht, 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" ]; thenwar 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_inund 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 -fund set +fes später wiederherzustellen).
Sie müssen sich keine Gedanken über das Zurücksetzen IFSmit machen unset IFS. [2]
Der oben Mähdrescher Bash Code mit externem Dienstprogramm sortfü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 sortPOSIX-Spezifikation ):
"${a_in[@]}"Erweitert sich sicher auf die Elemente des Arrays a_inals 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/ readarrayin der aktuellen Shell ausgeführt werden muss (darf nicht in einer Subshell ausgeführt werden ), damit die Ausgabevariable a_outsichtbar ist auf die aktuelle Shell (damit die Variable im Rest des Skripts definiert bleibt).
Lesen sortder Ausgabe in eine Array-Variable :
Bash v4 +: readarray -t a_outLiest die einzelnen Zeilen aus, die von sortin die Elemente der Array-Variablen ausgegeben werden a_out, ohne die Nachfolge \nin jedes Element ( -t) aufzunehmen.
Bash v3: readarrayexistiert nicht, readmuss also verwendet werden:
IFS=$'\n' read -d '' -r -a a_outweist readan, in die -aVariable 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 ).
( -rEine 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 sortohne 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,1nanstelle 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_sortdass erweiterte Funktionen wie merge_sortauch 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 sortund meistert mit allen Sonderzeichen (außer NULs :)). Sollte mit bash-3.2 und GNU oder BSD sortfunktionieren (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 printfeingebauten Array-Elemente mit Nullterminierung. Durch das Anführungszeichen wird sichergestellt, dass Array-Elemente unverändert übergeben werden. Durch die Besonderheiten der Shell wird printfder 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 -zOption bewirkt, dass nullterminierte Elemente gelesen, sortiert und auch nullterminiert ausgegeben werden. Wenn Sie nur die eindeutigen Elemente benötigen, können Sie übergeben, -uda es portabler ist als uniq -z. Das LC_ALL=Csorgt für eine stabile Sortierreihenfolge unabhängig von locale - manchmal nützlich für Skripte. Wenn Sie möchten, dass das sortGebietsschema respektiert wird, entfernen Sie das.
Das <()Konstrukt erhält den zu lesenden Deskriptor aus der erzeugten Pipeline und <leitet die Standardeingabe der whileSchleife 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 readeingebaute liest die Ausgabe vom umgeleiteten Standard. Wenn Sie leer setzen, wird die IFSWortaufteilung deaktiviert, was hier nicht readerforderlich ist. Infolgedessen wird die gesamte Eingabezeile in die einzelne bereitgestellte Variable gelesen. -rOption deaktiviert die Escape-Verarbeitung, die auch hier unerwünscht ist. Setzt schließlich -d ''den Zeilenbegrenzer auf NUL - das heißt, es wird readangewiesen, 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 :).
eVerwenden 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 1set -fIFS=$'\n'), eine Schleifenvariable xund eine Newline var ( nl=$'\n').$@)."${@//$nl/$wa}".sort -n.set --.for xsorted+=(…)"${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. sorterledigt 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 echoist stumm, wird es brechen , wenn Ihr Array mit beginnt -e, -Eoder -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!