Как отсортировать массив в 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 указал, что необходимо соблюдать осторожность, если элементы содержат символы подстановки, такие как *или ?:

Часть sorted = ($ (...)) использует оператор «split and glob». Вы должны отключить glob: 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 поддерживается в bash 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: Я принял ваше предложение и исправил обработку пробелов для работы с чем угодно (используя внутренние разделители nullchar). Приветствия
sehe

1
Да, sort -zэто полезное улучшение, я полагаю, что -zопция является расширением сортировки GNU.
Димитр Радулов

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

1
@ user1527227 Это перенаправление ввода ( <) в сочетании с заменой процесса <(...) . Или, если выразиться интуитивно: потому что (printf "bla")это не файл.
Сехе

33

Вот чистая реализация быстрой сортировки 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[@]}" )
}

Используйте как, например,

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

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

#!/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
Престижность за впечатляющий Bashing, который предлагает большую гибкость в отношении элементов ввода и критериев сортировки. Если сортировки на основе строк с предлагаемыми опциями сортировки sortдостаточно, решение « sortread -aбудет быстрее, начиная, скажем, с 20 элементов, и все быстрее и быстрее, чем больше элементов, с которыми вы имеете дело. Например, на моем конце 2012 года iMac под управлением OSX 10.11.1 с Fusion Drive: массив из 100 элементов: ок. 0,03 с. ( qsort()) против ок. 0,005 с. ( sort+ read -a); Массив из 1000 элементов: ок. 0,375 с. ( qsort()) против ок. 0,014 с ( sort+ read -a).
mklement0

Ницца. Я помню быструю сортировку со времен колледжа, но также буду исследовать пузырьковую сортировку. Для моих потребностей сортировки у меня есть первый и второй элементы, формирующие ключ, сопровождаемый одним элементом данных (который я могу расширить позже). Ваш код может быть улучшен с помощью количества ключевых элементов (parm1) и количества элементов данных (parm2). Для OP параметры были бы 1 и 0. Для меня параметры были бы 2 и 1. Во всех отношениях ваш ответ имеет наибольшее обещание.
WinEunuuchs2Unix

1
Я обнаружил, что с набором данных из целых чисел в формате uncast 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 .


Очень хорошая справочная информация. Я бы почти попросил демо-версию о том, как ksh будет использовать флаг set -s ... но опять же, вопрос на bash, так что это было бы довольно не по теме
sehe

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

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

10

тл; др :

Сортируйте массив 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)

Преимущества перед решением Antak :

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

  • Вам не нужно беспокоиться о сбросе IFSс unset IFS. [2]


Дополнительное чтение: объяснение и пример кода

Вышеприведенное объединяет код Bash с внешней утилитой sortдля решения, которое работает с произвольными однострочными элементами и лексической или числовой сортировкой (необязательно по полю) :

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

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

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

    • printf '%s\n' затем печатает каждый аргумент, т. е. каждый элемент массива, в отдельной строке, как есть.

  • Обратите внимание на использование процесса substitution ( <(...)) для предоставления отсортированного вывода в качестве входных данных для 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

В 3-часовой поездке на поезде из Мюнхена во Франкфурт (до которой мне было трудно добраться, потому что завтра начинается Октоберфест), я думал о своем первом посте. Использование глобального массива - гораздо лучшая идея для общей функции сортировки. Следующая функция обрабатывает произвольные строки (переводы строк, пробелы и т. Д.):

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 внутренне использует smart-указатели, поэтому операция подкачки может быть дешевой (хотя я сомневаюсь в этом). Тем не менее, bubble_sortдемонстрирует, что более продвинутые функции, такие merge_sortкак также доступны в языке оболочки.


5
Пузырьковая сортировка? Ого ... Обама говорит, что "пузырьковая сортировка была бы неправильным путем" -> youtube.com/watch?v=k4RRi_ntQc8
Robottinosino

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

Эту функцию можно сделать более переносимой, сделав BSORT локальным массивом с именем по любому массиву, который нужно отсортировать. т.е. local -n BSORT="$1"в начале функции. Тогда вы можете запустить, bubble_sort myarrayчтобы отсортировать myarray .
Джонраф

7

Другое решение, которое использует внешние sortи справляется с любыми специальными символами (кроме NUL :)). Должен работать с 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опция отключает обработку escape, которая также нежелательна Наконец, -d ''устанавливает разделитель строки в NUL - то есть говорит readчитать строки с нулем в конце .

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

Конечно, это только один из многих способов достижения одной и той же цели. На мой взгляд, это проще, чем реализовать полный алгоритм сортировки в bash, а в некоторых случаях это будет быстрее. Он обрабатывает все специальные символы, включая символы новой строки, и должен работать в большинстве распространенных систем. Самое главное, он может научить вас чему-то новому и удивительному в bash :).


Отличное решение и очень полезное объяснение, спасибо. Одно расширение: без установки IFS на пустое место, первые пробельные символы также будут удалены, даже если в противном случае разбиение слов не было выполнено.
Дирк Херрманн

Вместо того, чтобы вводить локальную переменную eи устанавливать пустой IFS, используйте переменную REPLY.
Робин А. Мид

2

попробуй это:

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

Выход будет:

3
5

б
с
е

Задача решена.


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[@]}"
  • Плюсы. Быстро.
  • Cons. Дублированные элементы объединяются, и может быть невозможно отобразить содержимое в 32-битные уникальные целые числа.

Интересная техника, я использовал вариант для нахождения значений max / min без явного сравнения / сортировки. Но невзвешенное сложение без учета длины не сработает: «z» сортирует перед «aaaa», поэтому вы не можете использовать это для слов, как показано выше.
Мистер Спуратик

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) мы проверяем весь массив $*.
  • Не содержит никаких символов [[ $* =~ [$wa] ]].
  • Если это так, подайте сообщение и сообщите об ошибке: exit 1
  • Избегайте расширений имени файла: set -f
  • Установите новое значение IFS ( IFS=$'\n'), переменную цикла xи символ новой строки var ( nl=$'\n').
  • Мы печатаем все значения полученных аргументов (входной массив $@).
  • но мы заменяем любую новую строку на обходной символ "${@//$nl/$wa}".
  • отправить эти значения для сортировки sort -n.
  • и поместите обратно все отсортированные значения в позиционные аргументы set --.
  • Затем мы присваиваем каждый аргумент один за другим (чтобы сохранить переводы строки).
  • в петле for x
  • в новый массив: sorted+=(…)
  • внутри кавычек, чтобы сохранить любой существующий символ новой строки.
  • восстановление обходного пути до новой строки "${x//$wa/$nl}".
  • сделано

1

Этот вопрос выглядит тесно связанным. И кстати, вот слияние в 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выполняет основную работу, но требует ввода, разделенного новой строкой, а не пробелом, поэтому очень простой конвейер выше просто делает:

Содержимое массива эха -> заменить пробел новой строкой -> сортировать

$() это повторить результат

($()) это положить "отраженный результат" в массиве

Примечание : как @sorontar упоминается в комментарии к другому вопросу:

Часть sorted = ($ (...)) использует оператор «split and glob». Вы должны отключить glob: set -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.