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
lub set -o noglob
lub shopt -op noglob
lub element tablicy, taki jak, *
zostanie rozszerzony do listy plików.
Co się dzieje:
Rezultatem jest sześć rzeczy, które dzieją się w podanej kolejności:
IFS=$'\n'
"${array[*]}"
<<<
sort
sorted=($(...))
unset IFS
Po pierwsze 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.
Następnie 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
Następnie 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.
Wreszcie 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
, 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!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łączenieIFS=
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ź:
wynik:
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:
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życiaNUL
zamiast zwykłych znaków nowej linii jako separatorów linii.źródło
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:
Użyj jako np.
Ta implementacja jest rekurencyjna… więc oto iteracyjne szybkie sortowanie:
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.
Następnie możesz mieć tę funkcję porównania:
I użyć:
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.).
źródło
sort
oferuje, jest wystarczające, rozwiązaniesort
+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:
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:
ksh musi
set -s
sortować ASCIIbetically .źródło
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 wa_out
(elementy nie mogą mieć osadzonych znaków nowej linii [1] ):Bash v4 +:
Bash v3:
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
iset +f
przywrócić je później).Nie musisz się martwić o resetowanie za
IFS
pomocąunset IFS
. [2]Lektura opcjonalna: wyjaśnienie i przykładowy kod
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 - patrzsort
specyfikacja POSIX ):"${a_in[@]}"
bezpiecznie interpretuje elementy tablicya_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 doread
/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ściowaa_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ściowesort
do elementów zmiennej tablicoweja_out
, bez uwzględniania końcowego\n
w każdym elemencie (-t
).Bash v3:
readarray
nie istnieje, więcread
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 zread
, wyłącza nieoczekiwaną obsługę\
znaków.)Przykładowy kod z adnotacjami:
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):Jeśli chcesz sortować liczbowo według pierwszego pola, użyjesz
sort -k1,1n
zamiast justsort
, które daje (sortowanie nieliczbowe przed liczbami, a liczby sortowane poprawnie):[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 przypadkuIFS
zmiana jest globalna .źródło
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.):
To drukuje:
Ten sam wynik jest tworzony z
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.źródło
local -n BSORT="$1"
na początku funkcji. Następnie możesz pobiecbubble_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 BSDsort
(niestety POSIX nie zawiera-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łokiprintf
powoduje, że dla każdego pozostałego parametru ponownie wykorzystuje ostatnią część ciągu formatu. Oznacza to, że jest to odpowiednik czegoś takiego: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ściewhile
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 pustegoIFS
wyłącza dzielenie na słowa, które jest tu niepotrzebne - w rezultacieread
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 :).
źródło
e
i ustawiać pusty IFS, użyj zmiennej REPLY.Spróbuj tego:
Wynik będzie:
Problem rozwiązany.
źródło
Jeśli możesz obliczyć unikalną liczbę całkowitą dla każdego elementu tablicy, na przykład:
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:
źródło
min sort:
źródło
echo nowej tablicy będzie wyglądało następująco:
źródło
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:
To posortuje tablicę:
Spowoduje to narzekanie, że tablica źródłowa zawiera znak obejścia:
opis
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}"
.źródło
To pytanie jest ściśle powiązane. A tak przy okazji, oto połączenie w Bash (bez procesów zewnętrznych):
źródło
Nie jestem przekonany, że będziesz potrzebować zewnętrznego programu do sortowania w Bash.
Oto moja implementacja prostego algorytmu sortowania bąbelkowego.
To powinno wydrukować:
źródło
O(n^2)
. Wydaje mi się, że większość algorytmów sortujących używaO(n lg(n))
do ostatnich kilkunastu elementów. W przypadku elementów końcowych stosowane jest sortowanie przez wybór.źródło
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:
źródło
mapfile -t sorted < <(printf '%s\n' "${array[@]}" | sort)
w przeciwnym raziesorted=(); 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 naecho
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.