Як сортувати масив у Bash


139

У мене є масив у Bash, наприклад:

array=(a c b f 3 5)

Мені потрібно сортувати масив. Не просто відображення вмісту відсортовано, а для отримання нового масиву з відсортованими елементами. Новий відсортований масив може бути абсолютно новим або старим.

Відповіді:


208

Вам не дуже потрібен весь стільки код:

IFS=$'\n' sorted=($(sort <<<"${array[*]}"))
unset IFS

Підтримує пробіли в елементах (доки це не новий рядок) і працює в Bash 3.x.

наприклад:

$ array=("a c" b f "3 5")
$ IFS=$'\n' sorted=($(sort <<<"${array[*]}")); unset IFS
$ printf "[%s]\n" "${sorted[@]}"
[3 5]
[a c]
[b]
[f]

Примітка: @sorontar вже вказав, що догляд потрібен , якщо елементи містять спеціальні символи , такі як *або ?:

У відсортованій = ($ (...)) частині використовується оператор "спліт і глоб". Вам слід вимкнути глобус: set -fабо set -o noglobабо shopt -op noglobелемент масиву на зразок *буде розширено до списку файлів.

Що відбувається:

Результат - кульмінація шести речей, які відбуваються в такому порядку:

  1. IFS=$'\n'
  2. "${array[*]}"
  3. <<<
  4. sort
  5. sorted=($(...))
  6. unset IFS

По-перше, IFS=$'\n'

Це важлива частина нашої операції, яка впливає на результат 2 та 5 наступним чином:

Подано:

  • "${array[*]}" розширюється на кожен елемент, відмежований першим символом IFS
  • sorted=() створює елементи, розбиваючи на кожного персонажа IFS

IFS=$'\n' встановлює речі так, що елементи розгортаються, використовуючи новий рядок як роздільник, а потім створюються таким чином, що кожен рядок стає елементом. (тобто розділення на новий рядок.)

Розмежування нового рядка важливо, оскільки саме так sortфункціонує (сортування за рядком). Розщеплення лише на новий рядок не є важливим, але потрібно зберегти елементи, що містять пробіли чи вкладки.

Значенням за замовчуванням IFSє пробіл , вкладка , за яким слід новий рядок , і це буде непридатним для нашої роботи.

Далі sort <<<"${array[*]}"частина

<<<, що називається тут рядками , приймає розширення "${array[*]}", як було пояснено вище, і подає його на стандартний вхід sort.

З нашим прикладом sortподається наступна рядок:

a c
b
f
3 5

Оскільки sort сортує , він виробляє:

3 5
a c
b
f

Далі sorted=($(...))частина

$(...)Частина, яка називається підстановкою команд , викликає його зміст ( sort <<<"${array[*]}) для запуску в якості звичайної команди, приймаючи отриманий стандартний висновок , як в буквальному сенсі , що йде туди , де ніколи НЕ $(...)було.

У нашому прикладі це створює щось подібне до простого написання:

sorted=(3 5
a c
b
f
)

sorted потім перетворюється на масив, що створюється шляхом поділу цього буквалу на кожен новий рядок.

Нарешті, unset IFS

Це скидає значення до значення IFSза замовчуванням і є лише хорошою практикою.

Це для того, щоб ми не створювали проблем ні з чим, на що покладається IFSпізніше в нашому сценарії. (Інакше нам потрібно пам’ятати, що ми переключили речі - те, що може бути недоцільним для складних сценаріїв.)


2
@xxor без цього IFS, він розділить ваші елементи на невеликі шматочки, якщо в них є пробіли. Спробуйте напр. З IFS=$'\n' пропущеним і дивіться!
антак

3
Дуже хороша. Чи можете ви пояснити середньому користувачеві bash, як працює це рішення?
u32004

2
Тепер, з IFS, він розбиває ваші елементи на невеликі шматочки, якщо в них є лише один особливий пробіл. Добре; не ідеально :-)
Обмежене спокутування

7
Чи unset IFSпотрібно? Я подумав, що, передчуваючи IFS=команду, отримав зміни лише до цієї команди, автоматично повернувшись до свого попереднього значення.
Марк Н

10
@MarkH Це необхідно, оскільки sorted=()це не команда, а скоріше призначення другої змінної.
антак

35

Оригінальна відповідь:

array=(a c b "f f" 3 5)
readarray -t sorted < <(for a in "${array[@]}"; do echo "$a"; done | sort)

вихід:

$ for a in "${sorted[@]}"; do echo "$a"; done
3
5
a
b
c
f f

Зверніть увагу, що ця версія справляється зі значеннями, що містять спеціальні символи або пробіли ( крім нових рядків)

Примітка readarray підтримується в Баш 4+.


Редагувати На підставі пропозиції @Dimitre я оновив її до:

readarray -t sorted < <(printf '%s\0' "${array[@]}" | sort -z | xargs -0n1)

що має перевагу навіть розуміти елементи сортування із символами нового рядка. На жаль, як правильно сигналізував @ruakh, це не означає, що результат readarrayбув би правильним , оскільки readarrayне має можливості використовувати NULзамість регулярних нових рядків як роздільники ліній.


5
Приємно, слід також відзначити, що readarray доступний з версії 4 bash. Це можна було б трохи скоротити:readarray -t sorted < <(printf '%s\n' "${array[@]}" | sort)
Димитре Радулов

1
@Dimitre: Я прийняв вашу пропозицію і виправив обробку пробілів працювати з чим завгодно (використовуючи нульові роздільники внутрішньо). Привітання
sehe

1
Так sort -z, це корисне поліпшення, я вважаю, що це -zваріант розширення GNU.
Димитрій Радулов

2
Якщо ви хочете обробляти вбудовані нові рядки, ви можете прокрутити власний читальний масив. Наприклад: sorted=(); while read -d $'\0' elem; do sorted[${#sorted[@]}]=$elem; done < <(printf '%s\0' "${array[@]}" | sort -z). Це також працює у тому, що ви використовуєте bash v3 замість bash v4, оскільки readhray недоступний у bash v3.
Боб Белл

1
@ user1527227 Це перенаправлення входу ( <) у поєднанні з заміною процесу <(...) . Або кажучи інтуїтивно: бо (printf "bla")це не файл.
sehe

33

Ось чиста реалізація Bash quicksort:

#!/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[@]}" )
}

Використовувати як, наприклад,

$ 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")'

Ця реалізація є рекурсивною… тож ось ітеративний швидкий вибір:

#!/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
}

В обох випадках ви можете змінити порядок, який ви використовуєте: я використовував порівняння рядків, але ви можете використовувати арифметичні порівняння, порівнювати час модифікації файлів wrt тощо. Просто використовуйте відповідний тест; ви навіть можете зробити це більш загальним і змусити його використовувати перший аргумент, який використовується тестовою функцією, наприклад,

#!/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
}

Тоді ви можете мати цю функцію порівняння:

compare_mtime() { [[ $1 -nt $2 ]]; }

і використовувати:

$ qsort compare_mtime *
$ declare -p qsort_ret

щоб файли в поточній папці були відсортовані за часом модифікації (найновіший перший).

ПРИМІТКА. Ці функції чисті Bash! жодних зовнішніх комунальних служб і жодних передплаток! вони є безпечними для всіх смішних символів (пробіли, символи нового рядка, символи глобуса тощо).


1
Kudos для вражаючого Bashing, який пропонує велику гнучкість щодо елементів введення та критерії сортування. Якщо лінійне сортування на основі варіантів сортування, яке sortпропонує, достатньо, рішення sort+ read -aбуде швидше, починаючи з, скажімо, 20 предметів, і все більше і значно швидше, чим більше елементів, з якими ви маєте справу. Наприклад, на моєму кінці 2012 року iMac під керуванням OSX 10.11.1 із Fusion Drive: 100-елементний масив: ca. 0,03 сек. ( qsort()) проти ca. 0,005 сек. ( sort+ read -a); Масив 1000 елементів: ca. 0,375 сек. ( qsort()) проти ca. 0,014 сек ( sort+ read -a).
mklement0

Приємно. Я пам’ятаю швидке сортування з коледжних днів, але також досліджуватиму сорти міхурів. Для моїх потреб у сортуванні у мене є перший та другий елементи формування ключа, а потім один елемент даних (який я можу розгорнути пізніше). Ваш код можна вдосконалити за допомогою кількості ключових елементів (parm1) та кількості елементів даних (parm2). Для ОП параметри були б 1 і 0. Для мене параметри були б 2 і 1. У будь-якому відношенні ваша відповідь найбільш обіцяє.
WinEunuuchs2Unix

1
З набором даних нерозподілених рядкових цілих чисел, які я знайшов, if [ "$i" -lt "$pivot" ]; thenпотрібно було, інакше вирішене значення "2" <"10" повернене "true". Я вважаю, що це POSIX проти лексикографічного; або, можливо, Inline Link .
Page2PagePro

27

Якщо вам не потрібно обробляти спеціальні символи оболонки в елементах масиву:

array=(a c b f 3 5)
sorted=($(printf '%s\n' "${array[@]}"|sort))

З bash вам все одно знадобиться програма зовнішнього сортування.

З zsh не потрібні зовнішні програми, і спеціальні символи оболонки легко обробляються:

% array=('a a' c b f 3 5); printf '%s\n' "${(o)array[@]}" 
3
5
a a
b
c
f

ksh має set -sсортувати ASCIIbetically .


Дуже приємна довідкова інформація. Я б майже попросити демо про те , як КШ буде використовувати опція -s ... настановних наборів , але знову ж , мова йде про Баш, так що було б вельми не по темі
sehe

Це повинно працювати з більшістю реалізацій KornShell (наприклад, ksh88 і pdksh ): set -A array x 'a a' d; set -s -- "${array[@]}"; set -A sorted "$@" І, звичайно, команда set буде скинути поточні позиційні параметри, якщо такі є.
Димитре Радулов

Ви справжній фонтан знань з оболонок. Я впевнений, що ви повинні мати фотографічну пам'ять чи щось подібне, тому що подібні тонкі відмінності ухиляються від більшості інших членів людського виду :), +1 для повного пакету інформації
1111

10

tl; dr :

Сортувати масив a_inі зберегти результат у a_out(елементи не повинні мати вбудовані нові рядки [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)

Переваги перед рішенням антака :

  • Вам не потрібно турбуватися про випадкове глобування (випадкова інтерпретація елементів масиву як шаблонів імен файлів), тому додаткова команда не потрібна для відключення глобулінгу ( set -fта set +fвідновлення його пізніше).

  • Вам не потрібно турбуватися про скидання IFSз unset IFS. [2]


Необов’язкове читання: пояснення та зразок коду

Вище комбінати Bash коду із зовнішнім утилітою sortдля вирішення , яке працює з довільними окремими -LINE елементів і або лексичної або числовий сортуванням (необов'язково по полю) :

  • Продуктивність : Приблизно 20 елементів і більше , це буде швидше, ніж чисте рішення Bash - значно і все частіше, коли ви перейдете понад 100 елементів.
    (Точні пороги залежать від конкретного входу, машини та платформи.)

    • Причина, чому це швидко, полягає в тому, що він уникає циклів Баша .
  • printf '%s\n' "${a_in[@]}" | sort виконує сортування (лексично, за замовчуванням - див sort. специфікацію POSIX ):

    • "${a_in[@]}"безпечно розширюється на елементи масиву a_inяк окремі аргументи , що б вони не містили (включаючи пробіли).

    • printf '%s\n' потім друкує кожен аргумент - тобто кожен елемент масиву - у власному рядку, як є.

  • Зауважте використання підстановки процесу ( <(...)) для надання відсортованого виводу як вхід до read/ readarray(через перенаправлення на stdin, <), оскільки read/ readarrayповинно працювати в поточній оболонці (не повинно запускатися в нижній частині ) для того, щоб вихідна змінна a_outбула видно до поточної оболонки (щоб змінна залишалася визначеною в решті сценарію).

  • Виведення читання sortу змінну масиву :

    • Bash v4 +: readarray -t a_outзчитує окремі рядки, що виводяться sortв елементи змінної масиву a_out, не включаючи трейлінг \nу кожному елементі ( -t).

    • Bash v3: readarrayне існує, тому readйого слід використовувати:
      IFS=$'\n' read -d '' -r -a a_outкаже readчитати в -aзмінну array ( ) a_out, читаючи весь вхід, по рядках ( -d ''), але розділяючи її на елементи масиву на нові рядки ( IFS=$'\n'. $'\n', Що створює буквальний новий рядок (LF ), - це так звана ANSI C-цитується рядок ).
      ( -r, опція, з якою практично завжди слід використовувати read, вимикає несподіване поводження з \персонажами.)

Кодований зразок коду:

#!/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[@]}"

Завдяки використанню sortбез варіантів це дає лексичне сортування (сортування цифр перед літерами та послідовності цифр трактуються лексично, а не як числа):

*
10
5
a c
b
f

Якщо ви хочете числове сортування за 1-м полем, ви використовуєте sort -k1,1nзамість просто sort, що дає (не-числа сортуйте перед числами, а числа сортуйте правильно):

*
a c
b
f
5
10

[1] для обробки елементів з вбудованими, переклади рядків використовувати наступний варіант (Bash V4 +, з ГНУ sort ):
readarray -d '' -t a_out < <(printf '%s\0' "${a_in[@]}" | sort -z).
Корисна відповідь Міхала Горни має рішення Bash v3.

[2] У той час як IFS це встановлено у варіанті v3 Bash, зміна області видимості команди .
Навпаки, IFS=$'\n' у відповіді антака випливає завдання, а не команда, і в цьому випадку IFSзміна є глобальною .


8

У тригодинній поїздці поїздом з Мюнхена до Франкфурта (до якої у мене виникли труднощі, оскільки Октоберфест починається завтра) я думав про своє перше повідомлення. Використання глобального масиву набагато краща ідея для загальної функції сортування. Наступна функція обробляє довільні рядки (нові рядки, пробіли тощо):

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[@]}

Це відбитки:

3 5 a b c z y

Той самий вихід створюється з

BSORT=(a c b 'z y' 3 5) 
bubble_sort
echo ${BSORT[@]}

Зауважте, що, ймовірно, Bash використовує внутрішньо смарт-покажчики, тому операція підкачки може бути дешевою (хоча я сумніваюся в цьому). Однак, bubble_sortдемонструє, що більш досконалі функції, подібні merge_sortтакож, знаходяться в межах досяжності мови оболонки.


5
Сорт бульбашки? Обама: Обама каже, що "сортування бульбашок було б неправильним шляхом" -> youtube.com/watch?v=k4RRi_ntQc8
Robottinosino

1
Ну, мабуть, поки О-хлопець хотів бути розумним, він не відчув, що це не випадковість 50/50. Попередник на посаді O-guy, скажімо йому B-guy, колись зробив набагато краще (Рейнольдсбург, Огайо, жовтень 2000 р.): "Я думаю, якщо ти знаєш, у що ти віриш, це набагато простіше відповідати на питання Я не можу відповісти на ваше запитання ". Тож цей B-хлопець справді знає щось про булеву логіку. О-хлопець ні.
Андреас Шпіндлер

Функцію можна зробити легше переносити, зробивши BSORT локальним масивом з іменем до будь-якого масиву, який слід сортувати. тобто local -n BSORT="$1"на початку функції. Тоді ви можете бігти, bubble_sort myarrayщоб сортувати міар .
johnraff

7

Ще одне рішення, яке використовує зовнішні sortта справляється з будь-якими спеціальними символами (крім NULs :)). Має працювати з bash-3.2 та GNU або BSD sort(на жаль, POSIX не включає -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)

Спочатку подивіться на вхідне перенаправлення в кінці. Ми використовуємо printfвбудований для виписування елементів масиву з нульовим завершенням. Цитування переконує, що елементи масиву передаються як є, а специфіка оболонки printfпризводить до повторного використання останньої частини рядка формату для кожного параметра, що залишився. Тобто, це рівнозначно чомусь подібному:

for e in "${array[@]}"; do
    printf "%s\0" "${e}"
done

Після цього перелік нульових елементів списку передається до sort. Цей -zпараметр змушує його зчитувати елементи, що закінчуються нулем, сортувати їх і виводити також з нульовим завершенням. Якщо вам потрібно було отримати лише унікальні елементи, ви можете пройти, -uоскільки він більш портативний, ніж uniq -z. LC_ALL=CЗабезпечує стабільний порядок сортування незалежно від локалізації - іноді корисно для сценаріїв. Якщо ви хочете, sortщоб поважали локаль, видаліть його.

<()Конструкція отримує дескриптор для читання з породив трубопроводу, і <перенаправляє стандартний ввід whileпетлі до нього. Якщо вам потрібно отримати доступ до стандартного вводу всередині труби, ви можете використовувати інший дескриптор - вправу для читача :).

Тепер повернемося до початку. readВбудований зчитує вихід з перенаправлені стандартного введення. Встановлення порожнього IFSвідключення розділення слів, яке тут непотрібне - як результат, readзчитується весь "рядок" введення до єдиної наданої змінної. -rопція вимикає обробку евакуації, яка тут також небажана. Нарешті, -d ''встановлює розмежувач рядків на NUL - тобто повідомляє readчитати рядки з нульовим завершенням.

В результаті цикл виконується один раз для кожного наступного елемента з масивом, що закінчується нулем, при цьому значення зберігається в e. У прикладі просто розміщуються елементи в іншому масиві, але ви можете віддати перевагу їх безпосередньо :).

Звичайно, це лише один із багатьох способів досягнення тієї ж мети. Як я бачу, це простіше, ніж реалізація повного алгоритму сортування в bash, а в деяких випадках це буде швидше. Він обробляє всі спеціальні символи, включаючи нові рядки, і повинен працювати над більшістю загальних систем. Найголовніше, що це може навчити вас чомусь новому і дивовижному про баш :).


Чудове рішення та дуже корисне пояснення, дякую. Одне розширення: без встановлення IFS порожнім, провідна пробіл також буде усунена - навіть якщо в іншому випадку не було зроблено жодного розділення слів.
Дірк Геррман

Замість введення локальної змінної eта встановлення порожнього IFS використовуйте змінну REPLY.
Робін А. Мід

2

спробуйте це:

echo ${array[@]} | awk 'BEGIN{RS=" ";} {print $1}' | sort

Вихід буде:

3
5
а
б
c
f

Проблема вирішена.


3
Потрібно відредагувати це, щоб поставити вихід у новий масив, щоб повністю відповісти на його питання.
Пітер Орам

2

Якщо ви можете обчислити унікальне ціле число для кожного елемента в масиві, наприклад:

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 -> $?"

тоді ви можете використовувати ці цілі числа в якості індексів масиву, оскільки Bash завжди використовує розріджений масив, тому не потрібно турбуватися про невикористані індекси:

array=(a c b f 3 5)
for el in "${array[@]}"; do
    sexy_int "$el"
    sorted[$?]="$el"
done

echo "${sorted[@]}"
  • Плюси Швидкий.
  • Мінуси. Дублюються елементи об’єднуються, і неможливо зіставити вміст у 32-бітні унікальні цілі числа.

Цікава техніка, я використовував варіант для знаходження максимальних / хв значень без явного порівняння / сортування. Але незважене додавання без урахування довжини не буде працювати: "z" сортує перед "aaaa", тому ви не можете використовувати це для слів, як показано вище.
mr.spuratic

2

хв сортування:

#!/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

1
array=(a c b f 3 5)
new_array=($(echo "${array[@]}" | sed 's/ /\n/g' | sort))    
echo ${new_array[@]}

Зміст відлуння new_array буде:

3 5 a b c f

1

Існує рішення для звичайної проблеми пробілів та нових рядків:

Використовуйте символ , який не в вихідному масиві (наприклад , $'\1'чи $'\4'або аналогічний).

Ця функція робить роботу:

# 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
       }

Це буде сортувати масив:

$ array=( a b 'c d' $'e\nf' $'g\1h')
$ sortarray "${array[@]}"
$ printf '<%s>\n' "${sorted[@]}"
<a>
<b>
<c d>
<e
f>
<gh>

Це поскаржиться, що вихідний масив містить обхідний символ:

$ array=( a b 'c d' $'e\nf' $'g\4h')
$ sortarray "${array[@]}"
./script: error: array contains the workaround char

опис

  • Встановлюємо дві локальні змінні wa(обхідна діаграма) та нульовий IFS
  • Тоді (з ifs null) ми перевіряємо цілий масив $*.
  • Не містить жодних ознак природи [[ $* =~ [$wa] ]].
  • Якщо це так, підніміть повідомлення та подайте сигнал про помилку: exit 1
  • Уникайте розширень імен файлів: set -f
  • Встановіть нове значення IFS ( IFS=$'\n') змінної циклу xта нового рядка var ( nl=$'\n').
  • Друкуємо всі значення отриманих аргументів (вхідний масив $@).
  • але будь-який новий рядок замінюємо обхідною схемою "${@//$nl/$wa}".
  • надіслати ці значення для сортування sort -n.
  • і повернути всі відсортовані значення в позиційні аргументи set --.
  • Потім присвоюємо кожен аргумент по одному (щоб зберегти нові рядки).
  • в петлі for x
  • до нового масиву: sorted+=(…)
  • всередині лапок, щоб зберегти будь-який існуючий новий рядок.
  • відновлення рішення до нового рядка "${x//$wa/$nl}".
  • зроблено

1

Це питання виглядає тісно пов’язаним. І BTW, ось об'єднання в Bash (без зовнішніх процесів):

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[@]}"

0

Я не переконаний, що вам потрібна програма зовнішнього сортування в Bash.

Ось моя реалізація для простого алгоритму сортування бульбашок.

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[@]})"

Це друкує:

 input: a c b f 3 5
output: 3 5 a b c f

Сорт бульбашки є O(n^2). Здається, я пригадую, що більшість алгоритмів сортування використовують O(n lg(n))до останнього десятка елементів або близько того. Для кінцевих елементів використовується сортування вибору.
jww


-1

sorted=($(echo ${array[@]} | tr " " "\n" | sort))

У дусі bash / linux я б передав найкращий інструмент командного рядка для кожного кроку. sortвиконує основну роботу, але потребує введення, розділеного новою лінією замість місця, тому дуже простий конвеєр вище просто робить:

Вміст масиву Echo -> замініть простір на новий рядок -> сортувати

$() це перегукувати результат

($()) полягає в тому, щоб помістити "відлунений результат" у масив

Примітка . Як згадував @sorontar у коментарі до іншого питання:

У відсортованій = ($ (...)) частині використовується оператор "спліт і глоб". Вам слід вимкнути глобус: встановити -f або set -o noglob або shopt -op noglob або елемент масиву типу * буде розширено до списку файлів.


У дусі bash / linux : я думаю, ви зовсім не зрозуміли дух. Ваш код повністю порушений (розширення назви шляху та розбиття слів). Це було б краще (Bash≥4):, mapfile -t sorted < <(printf '%s\n' "${array[@]}" | sort)інакше sorted=(); while IFS= read -r line; do sorted+=( "$line" ); done < <(printf '%s\n' | sort).
gniourf_gniourf

Використовувані анти малюнки echo ${array[@]} | tr " " "\n":: це порушиться, якщо поля масиву містять пробіли та символи глобуса. Крім того, він породжує підзарядку і використовує марну зовнішню команду. І через echoте, що він німий, він зламається, якщо ваш масив починається з -e, -Eабо -n. Замість того, щоб використовувати: printf '%s\n' "${array[@]}". Інший антипатерн: ($())це помістити "відлунений результат" у масив . Звичайно, ні! це жахливий антипатерн, який ламається через розширення назви шляху (глобулювання) та розбиття слів. Ніколи не використовуйте цей жах.
gniourf_gniourf

Верхня відповідь має "жахливий антипатерн". І спосіб перейти до спростування чужої відповіді на питання, на яке ви відповіли самі.
Майкл
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.