В отличие от ksh или zsh, bash не имеет встроенной поддержки сортировки массивов или списков произвольных строк. Он может сортировать глобусы или выходные данные alias
или set
или typeset
(хотя последние 3 не в порядке сортировки локали пользователя), но это практически невозможно использовать здесь.
В инструментарии POSIX нет ничего, что могло бы легко сортировать произвольные списки строк¹ (sort
сортирует строки, поэтому только короткие (LINE_MAX часто короче PATH_MAX) последовательности символов, отличных от NUL и новой строки, в то время как пути к файлам являются непустыми последовательностями байтов, отличными от других. чем 0).
Таким образом, хотя вы можете реализовать свой собственный алгоритм сортировки в awk
(используя <
оператор сравнения строк) или дажеbash
(используя [[ < ]]
) для произвольных путей bash
, переносимо, проще всего прибегнуть к perl
:
С помощью bash4.4+
вы можете сделать:
readarray -td '' sorted_filearray < <(perl -MFile::Basename -l0 -e '
print for sort {basename($a) cmp basename($b)} @ARGV' -- "${filearray[@]}")
Это дает strcmp()
похожий порядок. Для порядка, основанного на правилах сортировки локали, таких как globs или выходные данные ls
, добавьте -Mlocale
аргумент в perl
. Для числовой сортировки (больше похожей на GNU, так sort -g
как она поддерживает числа, подобные +3
, 1.2e-5
а не разделители тысяч, хотя и не шестнадцатеричные), используйте <=>
вместо cmp
(и снова -Mlocale
для того, чтобы десятичная отметка пользователя учитывалась, как для sort
команды).
Вы будете ограничены максимальным размером аргументов команды. Чтобы избежать этого, вы можете передать список файлов perl
на его стандартный ввод вместо аргументов:
readarray -td '' sorted_filearray < <(
printf '%s\0' "${filearray[@]}" | perl -MFile::Basename -0le '
chomp(@files = <STDIN>);
print for sort {basename($a) cmp basename($b)} @files')
В более старых версиях bash
вы могли бы использовать while IFS= read -rd ''
цикл вместо readarray -d ''
или получить perl
для вывода список путей, правильно заключенных в кавычки, чтобы вы могли передать его eval "array=($(perl...))"
.
С помощью zsh
вы можете подделать расширение glob, для которого вы можете определить порядок сортировки:
sorted_filearray=(/(e{'reply=($filearray)'}oe{'REPLY=$REPLY:t'}))
С помощью reply=($filearray)
мы фактически форсируем глобальное расширение (которое изначально было просто/
) быть элементами массива. Затем мы определяем порядок сортировки на основе хвоста имени файла.
Для strcmp()
порядка, подобного языку, установите языковой стандарт на C. Для числовой сортировки (аналогично GNU sort -V
, sort -n
которая не имеет существенного значения при сравнении 1.4
и 1.23
(например, в языковых стандартах, где .
используется десятичная метка), добавьтеn
квалификатор glob.
Вместо этого oe{expression}
вы также можете использовать функцию для определения порядка сортировки, например:
by_tail() REPLY=$REPLY:t
или более продвинутые, такие как:
by_numbers_in_tail() REPLY=${(j:,:)${(s:,:)${REPLY:t}//[^0-9]/,}}
(так a/foo2bar3.pdf
(2,3 числа) сортирует после b/bar1foo3.pdf
(1,3), но до c/baz2zzz10.pdf
(2,10)) и использует как:
sorted_filearray=(/(e{'reply=($filearray)'}no+by_numbers_in_tail))
Конечно, они могут быть применены к реальным шарам, поскольку это то, для чего они в первую очередь предназначены. Например, для списка pdf
файлов в любом каталоге, отсортированного по базовому имени / хвосту:
pdfs=(**/*.pdf(N.oe+by_tail))
¹ Если strcmp()
приемлема сортировка на основе и для коротких строк, вы можете преобразовать строки в их шестнадцатеричное кодирование с помощью awk
до передачи в sort
и преобразовать обратно после сортировки.