Odpowiedzi:
Naprawdę nie potrzebujesz całego kodu:
IFS=$'\n' sorted=($(sort <<<"${array[*]}"))
unset IFS
Obsługuje spacje w elementach (o ile nie jest to nowa linia) i działa w Bash 3.x.
na przykład:
$ array=("a c" b f "3 5")
$ IFS=$'\n' sorted=($(sort <<<"${array[*]}")); unset IFS
$ printf "[%s]\n" "${sorted[@]}"
[3 5]
[a c]
[b]
[f]
Uwaga: @sorontar wskazał, że należy zachować ostrożność, jeśli elementy zawierają symbole wieloznaczne, takie jak *
lub ?
:
Część posortowana = ($ (...)) używa operatora „split and glob”. Powinieneś wyłączyć glob:
set -f
lubset -o noglob
lubshopt -op noglob
lub element tablicy, taki jak,*
zostanie rozszerzony do listy plików.
Rezultatem jest sześć rzeczy, które dzieją się w podanej kolejności:
IFS=$'\n'
"${array[*]}"
<<<
sort
sorted=($(...))
unset IFS
IFS=$'\n'
Jest to ważna część naszej operacji, która wpływa na wynik 2 i 5 w następujący sposób:
Dany:
"${array[*]}"
rozwija się do każdego elementu rozdzielanego pierwszym znakiem IFS
sorted=()
tworzy elementy dzieląc się na każdy znak IFS
IFS=$'\n'
ustawia rzeczy w taki sposób, że elementy są rozwijane przy użyciu nowej linii jako separatora, a następnie tworzone w taki sposób, że każda linia staje się elementem. (tj. Dzielenie w nowej linii).
Ograniczanie przez nową linię jest ważne, ponieważ tak sort
działa (sortowanie według linii). Dzielenie tylko nową linią nie jest tak ważne, ale jest potrzebne, aby zachować elementy zawierające spacje lub tabulatory.
Wartością domyślną IFS
jest spacja , tabulator , po którym następuje nowy wiersz , i byłoby to nieodpowiednie dla naszej operacji.
sort <<<"${array[*]}"
część<<<
, nazywany tutaj ciągami , pobiera rozwinięcie "${array[*]}"
, jak wyjaśniono powyżej, i przekazuje je na standardowe wejście sort
.
W naszym przykładzie sort
jest podawany następujący ciąg:
a c
b
f
3 5
Od sort
swego rodzaju produkuje:
3 5
a c
b
f
sorted=($(...))
część$(...)
Część, zwana na zmianę polecenia powoduje jego zawartość ( sort <<<"${array[*]}
), w celu działania jako normalne polecenia podczas stosowania otrzymanego standardowe wyjście jako dosłownym który przechodzi gdziekolwiek $(...)
jest.
W naszym przykładzie daje to coś podobnego do zwykłego pisania:
sorted=(3 5
a c
b
f
)
sorted
następnie staje się tablicą utworzoną przez podzielenie tego literału na każdy nowy wiersz.
unset IFS
Spowoduje to zresetowanie wartości IFS
do wartości domyślnej i jest to po prostu dobra praktyka.
Ma to na celu zapewnienie, że nie będziemy powodować problemów z czymkolwiek, na czym polega IFS
później w naszym skrypcie. (W przeciwnym razie musielibyśmy pamiętać, że zmieniliśmy rzeczy - coś, co może być niepraktyczne w przypadku złożonych skryptów).
IFS
, dzieli twoje elementy na małe części, jeśli mają tylko jeden określony rodzaj białych znaków. Dobry; nie idealne :-)
unset IFS
konieczne? Pomyślałem, że dołączenie IFS=
do polecenia ograniczyło zmianę tylko do tego polecenia, po czym automatycznie powróciło do poprzedniej wartości.
sorted=()
nie jest to polecenie, ale raczej drugie przypisanie zmiennej.
Oryginalna odpowiedź:
array=(a c b "f f" 3 5)
readarray -t sorted < <(for a in "${array[@]}"; do echo "$a"; done | sort)
wynik:
$ for a in "${sorted[@]}"; do echo "$a"; done
3
5
a
b
c
f f
Uwaga ta wersja radzi sobie z wartościami zawierającymi znaki specjalne lub spacje ( z wyjątkiem nowych linii)
Uwaga readarray jest obsługiwany w bash 4+.
Edytuj Na podstawie sugestii @Dimitre zaktualizowałem go do:
readarray -t sorted < <(printf '%s\0' "${array[@]}" | sort -z | xargs -0n1)
który ma tę zaletę, że nawet zrozumie sortowanie elementów z prawidłowo osadzonymi znakami nowej linii. Niestety, jak poprawnie zasygnalizował @ruakh, nie oznaczało to, że wynik readarray
będzie poprawny , ponieważ readarray
nie ma opcji użycia NUL
zamiast zwykłych znaków nowej linii jako separatorów linii.
readarray -t sorted < <(printf '%s\n' "${array[@]}" | sort)
sort -z
jest to przydatne ulepszenie, przypuszczam, że -z
opcją jest rozszerzenie sortowania GNU.
sorted=(); while read -d $'\0' elem; do sorted[${#sorted[@]}]=$elem; done < <(printf '%s\0' "${array[@]}" | sort -z)
. Działa to również w przypadku, gdy używasz bash v3 zamiast bash v4, ponieważ readarray nie jest dostępne w bash v3.
<
) połączone z podstawianiem procesu <(...)
. Albo mówiąc intuicyjnie: ponieważ (printf "bla")
to nie jest plik.
Oto czysta implementacja quicksort 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[@]}" )
}
Użyj jako np.
$ 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")'
Ta implementacja jest rekurencyjna… więc oto iteracyjne szybkie sortowanie:
#!/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
}
W obu przypadkach możesz zmienić kolejność, z której korzystasz: użyłem porównań ciągów, ale możesz użyć porównań arytmetycznych, porównać czas modyfikacji pliku wrt itp. Po prostu użyj odpowiedniego testu; możesz nawet uczynić go bardziej ogólnym i poprosić go o użycie pierwszego argumentu, którym jest funkcja testowa, np.
#!/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
}
Następnie możesz mieć tę funkcję porównania:
compare_mtime() { [[ $1 -nt $2 ]]; }
I użyć:
$ qsort compare_mtime *
$ declare -p qsort_ret
aby pliki w bieżącym folderze były posortowane według czasu modyfikacji (od najnowszych).
UWAGA. Te funkcje to czysty Bash! żadnych zewnętrznych narzędzi i podpowłok! są one bezpieczne dla wszystkich zabawnych symboli, które możesz mieć (spacje, znaki nowej linii, znaki globu itp.).
sort
oferuje, jest wystarczające, rozwiązanie sort
+ read -a
będzie szybsze, zaczynając od, powiedzmy, 20 pozycji i coraz szybciej, im więcej elementów masz do czynienia. Na przykład na moim komputerze iMac z końca 2012 roku z systemem OSX 10.11.1 i dyskiem Fusion Drive: 100-elementowa tablica: ok. 0,03 s ( qsort()
) vs. ok. 0,005 sek. ( sort
+ read -a
); 1000-elementowa tablica: ok. 0.375 sek. ( qsort()
) vs. ok. 0,014 s ( sort
+ read -a
).
if [ "$i" -lt "$pivot" ]; then
było wymagane, w przeciwnym razie rozwiązane „2” <„10” zwróciło prawdę. Uważam, że to jest POSIX kontra leksykograficzne; lub może Inline Link .
Jeśli nie potrzebujesz obsługiwać specjalnych znaków powłoki w elementach tablicy:
array=(a c b f 3 5)
sorted=($(printf '%s\n' "${array[@]}"|sort))
W przypadku basha i tak będziesz potrzebować zewnętrznego programu do sortowania.
Z zsh nie są potrzebne żadne zewnętrzne programy, a specjalne znaki powłoki są łatwo obsługiwane:
% array=('a a' c b f 3 5); printf '%s\n' "${(o)array[@]}"
3
5
a a
b
c
f
ksh musi set -s
sortować ASCIIbetically .
set -A array x 'a a' d; set -s -- "${array[@]}"; set -A sorted "$@"
I oczywiście polecenie set zresetuje bieżące parametry pozycyjne, jeśli takie istnieją.
tl; dr :
Sortuj tablicę a_in
i przechowuj wynik w a_out
(elementy nie mogą mieć osadzonych znaków nowej linii [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)
Zalety w stosunku do rozwiązania Antak :
Nie musisz martwić się przypadkowym globowaniem (przypadkową interpretacją elementów tablicy jako wzorców nazw plików), więc nie jest potrzebne żadne dodatkowe polecenie, aby wyłączyć globowanie ( set -f
i set +f
przywrócić je później).
Nie musisz się martwić o resetowanie za IFS
pomocą unset IFS
. [2]
Powyższe łączy kod Bash z zewnętrznym narzędziem sort
dla rozwiązania, które działa z dowolnymi elementami jednowierszowymi i sortowaniem leksykalnym lub numerycznym (opcjonalnie według pola) :
Wydajność : w przypadku około 20 lub więcej elementów będzie to szybsze niż zwykłe rozwiązanie Bash - znacznie i coraz częściej, gdy przekroczysz około 100 elementów.
(Dokładne progi będą zależeć od konkretnych danych wejściowych, maszyny i platformy).
printf '%s\n' "${a_in[@]}" | sort
wykonuje sortowanie (domyślnie leksykalnie - patrz sort
specyfikacja POSIX ):
"${a_in[@]}"
bezpiecznie interpretuje elementy tablicy a_in
jako pojedyncze argumenty , niezależnie od tego, co zawierają (w tym spacje).
printf '%s\n'
następnie wypisuje każdy argument - tj. każdy element tablicy - w osobnym wierszu, tak jak jest.
Zwróć uwagę na użycie substytucji procesu ( <(...)
), aby zapewnić posortowane dane wyjściowe jako dane wejściowe do read
/ readarray
(przez przekierowanie do standardowego wejścia <
), ponieważ read
/ readarray
musi działać w bieżącej powłoce (nie może działać w podpowłoce ), aby zmienna wyjściowa a_out
była widoczna do bieżącej powłoki (aby zmienna pozostała zdefiniowana w pozostałej części skryptu).
Odczytywanie sort
danych wyjściowych do zmiennej tablicowej :
Bash v4 +: readarray -t a_out
wczytuje poszczególne wiersze wyjściowe sort
do elementów zmiennej tablicowej a_out
, bez uwzględniania końcowego \n
w każdym elemencie ( -t
).
Bash v3: readarray
nie istnieje, więc read
musi być użyty:
IFS=$'\n' read -d '' -r -a a_out
mówi, read
aby wczytać do -a
zmiennej array ( ) a_out
, odczytać całe wejście, przez linie ( -d ''
), ale podzielić je na elementy tablicy za pomocą znaków nowej linii ( IFS=$'\n'
. $'\n'
, Co tworzy literalny znak nowej linii (LF ), jest tak zwanym ciągiem znaków ANSI w cudzysłowie C ).
( -r
opcja, która praktycznie zawsze powinna być używana z read
, wyłącza nieoczekiwaną obsługę \
znaków.)
Przykładowy kod z adnotacjami:
#!/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[@]}"
Ze względu na użycie sort
bez opcji daje to sortowanie leksykalne (cyfry sortują się przed literami, a sekwencje cyfr są traktowane leksykalnie, a nie jako liczby):
*
10
5
a c
b
f
Jeśli chcesz sortować liczbowo według pierwszego pola, użyjesz sort -k1,1n
zamiast just sort
, które daje (sortowanie nieliczbowe przed liczbami, a liczby sortowane poprawnie):
*
a c
b
f
5
10
[1] do elementów uchwytu z osadzonymi nowej linii, można użyć następującego wariantu (atakujących V4 +, jak GNU sort
)
readarray -d '' -t a_out < <(printf '%s\0' "${a_in[@]}" | sort -z)
.
Pomocna odpowiedź Michała Górnego ma rozwiązanie Bash v3.
[2] Podczas gdy IFS
jest ustawiony w wariancie Bash v3, zmiana jest ograniczona do polecenia .
W przeciwieństwie do tego, co następuje IFS=$'\n'
w odpowiedzi Antaka, jest raczej przypisaniem niż poleceniem, w którym to przypadku IFS
zmiana jest globalna .
Podczas 3-godzinnej podróży pociągiem z Monachium do Frankfurtu (do którego miałem kłopoty, bo jutro zaczyna się Oktoberfest) myślałem o swoim pierwszym poście. Wykorzystanie tablicy globalnej jest znacznie lepszym pomysłem na ogólną funkcję sortowania. Następująca funkcja obsługuje dowolne ciągi znaków (znaki nowej linii, spacje itp.):
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[@]}
To drukuje:
3 5 a b c z y
Ten sam wynik jest tworzony z
BSORT=(a c b 'z y' 3 5)
bubble_sort
echo ${BSORT[@]}
Zauważ, że prawdopodobnie Bash wewnętrznie używa inteligentnych wskaźników, więc operacja zamiany może być tania (chociaż wątpię). Jednak bubble_sort
pokazuje, że bardziej zaawansowane funkcje, takie jak, merge_sort
są również w zasięgu języka powłoki.
local -n BSORT="$1"
na początku funkcji. Następnie możesz pobiec bubble_sort myarray
posortować moją tablicę .
Kolejne rozwiązanie wykorzystujące zewnętrzne sort
i radzące sobie z dowolnymi znakami specjalnymi (z wyjątkiem NUL-ów :)). Powinien działać z bash-3.2 i GNU lub BSD sort
(niestety POSIX nie zawiera -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)
Najpierw spójrz na przekierowanie danych wejściowych na końcu. Używamy funkcji printf
wbudowanej do zapisywania elementów tablicy, zakończonych zerem. Cytowanie zapewnia, że elementy tablicy są przekazywane bez zmian, a specyfika powłoki printf
powoduje, że dla każdego pozostałego parametru ponownie wykorzystuje ostatnią część ciągu formatu. Oznacza to, że jest to odpowiednik czegoś takiego:
for e in "${array[@]}"; do
printf "%s\0" "${e}"
done
Lista elementów zakończona znakiem null jest następnie przekazywana do sort
. Ta -z
opcja powoduje, że odczytuje elementy zakończone znakiem null, sortuje je i wyświetla również elementy zakończone znakiem null. Jeśli potrzebujesz tylko unikalnych elementów, możesz przejść, -u
ponieważ jest bardziej przenośny niż uniq -z
. LC_ALL=C
Zapewnia stabilny porządek niezależnie od lokalizacji - czasami przydatne dla skryptów. Jeśli chcesz, sort
aby szanował ustawienia regionalne, usuń to.
<()
Konstrukcja uzyskuje deskryptor odczytu z wywoływanych rurociągu i <
przekierowuje standardowe wejście while
pętli do niego. Jeśli potrzebujesz dostępu do standardowego wejścia wewnątrz potoku, możesz użyć innego deskryptora - ćwiczenie dla czytelnika :).
Wróćmy teraz do początku. read
Wbudowaną czyta wyjście z przekierowanych stdin. Ustawienie pustego IFS
wyłącza dzielenie na słowa, które jest tu niepotrzebne - w rezultacie read
czyta całą „linię” danych wejściowych do pojedynczej podanej zmiennej. -r
opcja wyłącza przetwarzanie ucieczki, które jest tutaj niepożądane. Na koniec -d ''
ustawia ogranicznik linii na NUL - to znaczy mówi, read
aby czytać ciągi zakończone zerami.
W rezultacie pętla jest wykonywana raz dla każdego kolejnego elementu tablicy zakończonego zerem, a wartość jest przechowywana w e
. Przykład po prostu umieszcza elementy w innej tablicy, ale możesz chcieć przetworzyć je bezpośrednio :).
Oczywiście to tylko jeden z wielu sposobów osiągnięcia tego samego celu. Jak widzę, jest to prostsze niż implementacja pełnego algorytmu sortowania w bashu, aw niektórych przypadkach będzie szybsze. Obsługuje wszystkie znaki specjalne, w tym znaki nowej linii i powinien działać na większości popularnych systemów. Co najważniejsze, może nauczyć Cię czegoś nowego i niesamowitego o bashu :).
e
i ustawiać pusty IFS, użyj zmiennej REPLY.
Spróbuj tego:
echo ${array[@]} | awk 'BEGIN{RS=" ";} {print $1}' | sort
Wynik będzie:
3 5 za b do fa
Problem rozwiązany.
Jeśli możesz obliczyć unikalną liczbę całkowitą dla każdego elementu tablicy, na przykład:
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 -> $?"
wtedy możesz użyć tych liczb całkowitych jako indeksów tablic, ponieważ Bash zawsze używa rzadkich tablic, więc nie musisz się martwić o nieużywane indeksy:
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
Istnieje obejście zwykłego problemu ze spacjami i znakami nowej linii:
Użyj znak, który nie jest w oryginalnej tablicy (jak $'\1'
lub $'\4'
lub podobny).
Ta funkcja wykonuje zadanie:
# 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
}
To posortuje tablicę:
$ array=( a b 'c d' $'e\nf' $'g\1h')
$ sortarray "${array[@]}"
$ printf '<%s>\n' "${sorted[@]}"
<a>
<b>
<c d>
<e
f>
<gh>
Spowoduje to narzekanie, że tablica źródłowa zawiera znak obejścia:
$ array=( a b 'c d' $'e\nf' $'g\4h')
$ sortarray "${array[@]}"
./script: error: array contains the workaround char
wa
(znak obejścia) i pusty IFS$*
.[[ $* =~ [$wa] ]]
.exit 1
set -f
IFS=$'\n'
) jako zmienną pętlową x
i nową linię var ( nl=$'\n'
).$@
)."${@//$nl/$wa}"
.sort -n
.set --
.for x
sorted+=(…)
"${x//$wa/$nl}"
.To pytanie jest ściśle powiązane. A tak przy okazji, oto połączenie w Bash (bez procesów zewnętrznych):
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[@]}"
Nie jestem przekonany, że będziesz potrzebować zewnętrznego programu do sortowania w Bash.
Oto moja implementacja prostego algorytmu sortowania bąbelkowego.
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[@]})"
To powinno wydrukować:
input: a c b f 3 5
output: 3 5 a b c f
O(n^2)
. Wydaje mi się, że większość algorytmów sortujących używa O(n lg(n))
do ostatnich kilkunastu elementów. W przypadku elementów końcowych stosowane jest sortowanie przez wybór.
a=(e b 'c d')
shuf -e "${a[@]}" | sort >/tmp/f
mapfile -t g </tmp/f
sorted=($(echo ${array[@]} | tr " " "\n" | sort))
W duchu bash / linux, dla każdego kroku chciałbym potokować najlepsze narzędzie wiersza poleceń. sort
wykonuje główną pracę, ale potrzebuje danych wejściowych oddzielonych znakiem nowej linii zamiast spacji, więc bardzo prosty potok powyżej po prostu wykonuje:
Zawartość tablicy echa -> zastąp spację nową linią -> sortuj
$()
jest powtórzenie wyniku
($())
polega na umieszczeniu w tablicy „wyniku wyświetlonego echo”
Uwaga : jak wspomniał @sorontar w komentarzu do innego pytania:
Część posortowana = ($ (...)) używa operatora „split and glob”. Powinieneś wyłączyć glob: set -f lub set -o noglob lub shopt -op noglob lub element tablicy, taki jak *, zostanie rozszerzony do listy plików.
mapfile -t sorted < <(printf '%s\n' "${array[@]}" | sort)
w przeciwnym razie sorted=(); while IFS= read -r line; do sorted+=( "$line" ); done < <(printf '%s\n' | sort)
.
echo ${array[@]} | tr " " "\n"
to się zepsuje, jeśli pola tablicy zawierają białe spacje i znaki globu. Poza tym tworzy podpowłokę i używa bezużytecznego polecenia zewnętrznego. I ze względu na echo
bycie niemy, zostaną przerwane, jeśli tablica rozpoczyna się -e
, -E
lub -n
. Zamiast używać: printf '%s\n' "${array[@]}"
. Innym antywzorem jest: ($())
umieszczenie "wyniku echo" w tablicy . Zdecydowanie nie! jest to okropny anty-wzór, który pęka z powodu rozwijania nazw plików (globbing) i dzielenia słów. Nigdy nie używaj tego horroru.
IFS
, podzieli twoje elementy na małe części, jeśli będą miały w sobie białe spacje. Wypróbuj np. ZIFS=$'\n'
pominiętymi i zobacz!