Câu trả lời:
Bạn không thực sự cần nhiều mã như vậy:
IFS=$'\n' sorted=($(sort <<<"${array[*]}"))
unset IFS
Hỗ trợ khoảng trắng trong các phần tử (miễn là nó không phải là dòng mới) và hoạt động trong Bash 3.x.
ví dụ:
$ array=("a c" b f "3 5")
$ IFS=$'\n' sorted=($(sort <<<"${array[*]}")); unset IFS
$ printf "[%s]\n" "${sorted[@]}"
[3 5]
[a c]
[b]
[f]
Lưu ý: @sorontar đã chỉ ra rằng cần phải cẩn thận nếu các phần tử có chứa ký tự đại diện như *
hoặc ?
:
Phần được sắp xếp = ($ (...)) đang sử dụng toán tử "split and global". Bạn nên tắt global:
set -f
hoặcset -o noglob
hoặcshopt -op noglob
một phần tử của mảng như*
sẽ được mở rộng thành một danh sách các tệp.
Kết quả là một đỉnh cao sáu điều xảy ra theo thứ tự này:
IFS=$'\n'
"${array[*]}"
<<<
sort
sorted=($(...))
unset IFS
IFS=$'\n'
Đây là một phần quan trọng trong hoạt động của chúng tôi có ảnh hưởng đến kết quả của 2 và 5 theo cách sau:
Được:
"${array[*]}"
mở rộng đến mọi phần tử được giới hạn bởi ký tự đầu tiên của IFS
sorted=()
tạo ra các yếu tố bằng cách phân chia trên mỗi ký tự của IFS
IFS=$'\n'
thiết lập mọi thứ để các phần tử được mở rộng bằng cách sử dụng một dòng mới làm dấu phân cách và sau đó được tạo theo cách mà mỗi dòng trở thành một phần tử. (tức là Chia tách trên một dòng mới.)
Phân định theo một dòng mới rất quan trọng vì đó là cách sort
vận hành (sắp xếp trên mỗi dòng). Việc phân tách chỉ bằng một dòng mới không quan trọng, nhưng cần bảo tồn các thành phần có chứa khoảng trắng hoặc tab.
Giá trị mặc định IFS
là một khoảng trắng , một tab , theo sau là một dòng mới và sẽ không phù hợp với hoạt động của chúng tôi.
sort <<<"${array[*]}"
phần<<<
, được gọi là các chuỗi ở đây , lấy sự mở rộng của "${array[*]}"
, như đã giải thích ở trên, và đưa nó vào đầu vào tiêu chuẩn của sort
.
Với ví dụ của chúng tôi, sort
được cung cấp chuỗi sau đây:
a c
b
f
3 5
Vì sort
các loại , nó tạo ra:
3 5
a c
b
f
sorted=($(...))
phầnPhần $(...)
, được gọi là thay thế lệnh , làm cho nội dung của nó ( sort <<<"${array[*]}
) chạy như một lệnh bình thường, trong khi lấy đầu ra tiêu chuẩn kết quả là nghĩa đen đi đến đâu $(...)
.
Trong ví dụ của chúng tôi, điều này tạo ra một cái gì đó tương tự như viết đơn giản:
sorted=(3 5
a c
b
f
)
sorted
sau đó trở thành một mảng được tạo bằng cách tách chữ này trên mỗi dòng mới.
unset IFS
Điều này đặt lại giá trị của giá trị IFS
mặc định, và chỉ là thực hành tốt.
Đó là để đảm bảo chúng tôi không gây rắc rối với bất cứ điều gì dựa vào IFS
kịch bản sau này. (Nếu không, chúng ta cần nhớ rằng chúng ta đã thay đổi mọi thứ xung quanh - điều gì đó có thể không thực tế đối với các tập lệnh phức tạp.)
IFS
, nó chia các phần tử của bạn thành các phần nhỏ nếu chúng chỉ có một loại khoảng trắng cụ thể trong đó. Tốt; không hoàn hảo :-)
unset IFS
cần thiết không? Tôi nghĩ rằng việc chuẩn bị trước IFS=
một lệnh chỉ giới hạn thay đổi cho lệnh đó, tự động trở về giá trị trước đó.
sorted=()
không phải là lệnh mà là gán biến thứ hai.
Phản hồi ban đầu:
array=(a c b "f f" 3 5)
readarray -t sorted < <(for a in "${array[@]}"; do echo "$a"; done | sort)
đầu ra:
$ for a in "${sorted[@]}"; do echo "$a"; done
3
5
a
b
c
f f
Lưu ý phiên bản này đối phó với các giá trị có chứa các ký tự hoặc khoảng trắng đặc biệt ( ngoại trừ dòng mới)
Lưu ý readarray được hỗ trợ trong bash 4+.
Chỉnh sửa Dựa trên đề xuất của @Dimitre, tôi đã cập nhật nó thành:
readarray -t sorted < <(printf '%s\0' "${array[@]}" | sort -z | xargs -0n1)
có lợi ích thậm chí hiểu các yếu tố sắp xếp với các ký tự dòng mới được nhúng chính xác. Thật không may, như được báo hiệu chính xác bởi @ruakh, điều này không có nghĩa là kết quả của readarray
nó sẽ đúng , bởi vì readarray
không có tùy chọn nào để sử dụng NUL
thay vì các dòng mới thông thường như các dấu tách dòng.
readarray -t sorted < <(printf '%s\n' "${array[@]}" | sort)
sort -z
là một cải tiến hữu ích, tôi cho rằng -z
tùy chọn này là một phần mở rộng sắp xếp GNU.
sorted=(); while read -d $'\0' elem; do sorted[${#sorted[@]}]=$elem; done < <(printf '%s\0' "${array[@]}" | sort -z)
. Điều này cũng hoạt động trong bạn đang sử dụng bash v3 thay vì bash v4, vì readarray không có sẵn trong bash v3.
<
) kết hợp với thay thế quá trình <(...)
. Hoặc để đặt nó bằng trực giác: bởi vì (printf "bla")
không phải là một tập tin.
Đây là một triển khai quicksort Bash thuần túy:
#!/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[@]}" )
}
Sử dụng như, ví dụ,
$ 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")'
Việc triển khai này là đệ quy, vì vậy đây là một quicksort lặp:
#!/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
}
Trong cả hai trường hợp, bạn có thể thay đổi thứ tự bạn sử dụng: Tôi đã sử dụng so sánh chuỗi, nhưng bạn có thể sử dụng so sánh số học, so sánh thời gian sửa đổi tệp wrt, v.v. chỉ cần sử dụng thử nghiệm thích hợp; bạn thậm chí có thể làm cho nó chung chung hơn và để nó sử dụng một đối số đầu tiên là sử dụng hàm kiểm tra, ví dụ:
#!/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
}
Sau đó, bạn có thể có chức năng so sánh này:
compare_mtime() { [[ $1 -nt $2 ]]; }
Và sử dụng:
$ qsort compare_mtime *
$ declare -p qsort_ret
để có các tệp trong thư mục hiện tại được sắp xếp theo thời gian sửa đổi (mới nhất trước tiên).
GHI CHÚ. Các hàm này là Bash thuần túy! không có tiện ích bên ngoài, và không có subshells! chúng an toàn khi viết bất kỳ biểu tượng ngộ nghĩnh nào bạn có thể có (dấu cách, ký tự dòng mới, ký tự toàn cầu, v.v.).
sort
cung cấp là đủ, giải pháp sort
+ read -a
sẽ nhanh hơn bắt đầu từ xung quanh, giả sử, 20 mục và ngày càng nhanh hơn và nhanh hơn đáng kể các yếu tố bạn đang xử lý. Ví dụ, trên iMac cuối năm 2012 của tôi chạy OSX 10.11.1 với Fusion Drive: mảng 100 phần tử: ca. 0,03 giây. ( qsort()
) so với ca. 0,005 giây. ( sort
+ read -a
); Mảng 1000 phần tử: ca. 0,375 giây. ( qsort()
) so với ca. 0,011 giây ( sort
+ read -a
).
if [ "$i" -lt "$pivot" ]; then
được yêu cầu nếu không thì "2" <"10" đã giải quyết trở lại đúng. Tôi tin rằng đây là POSIX so với Thuật ngữ; hoặc có lẽ Liên kết nội tuyến .
Nếu bạn không cần xử lý các ký tự shell đặc biệt trong các thành phần mảng:
array=(a c b f 3 5)
sorted=($(printf '%s\n' "${array[@]}"|sort))
Với bash bạn sẽ cần một chương trình sắp xếp bên ngoài nào.
Với zsh, không cần chương trình bên ngoài và các ký tự shell đặc biệt dễ dàng xử lý:
% array=('a a' c b f 3 5); printf '%s\n' "${(o)array[@]}"
3
5
a a
b
c
f
ksh có set -s
để sắp xếp ASCIIbetically .
set -A array x 'a a' d; set -s -- "${array[@]}"; set -A sorted "$@"
Và, tất nhiên, lệnh set sẽ đặt lại các tham số vị trí hiện tại, nếu có.
tl; dr :
Sắp xếp mảng a_in
và lưu trữ kết quả trong a_out
(các phần tử không được nhúng dòng mới [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)
Ưu điểm so với giải pháp của antak :
Bạn không cần phải lo lắng về việc tạo hình ngẫu nhiên (giải thích ngẫu nhiên các thành phần mảng dưới dạng mẫu tên tệp), do đó không cần thêm lệnh để vô hiệu hóa tính năng tạo khối ( set -f
và set +f
để khôi phục lại sau này).
Bạn không cần phải lo lắng về việc đặt lại IFS
với unset IFS
. [2]
Ở trên kết hợp mã Bash với tiện ích bên ngoài sort
cho một giải pháp hoạt động với các phần tử dòng đơn tùy ý và sắp xếp từ vựng hoặc số (tùy chọn theo trường) :
Hiệu suất : Đối với khoảng 20 yếu tố trở lên , điều này sẽ nhanh hơn một giải pháp Bash thuần túy - đáng kể và ngày càng cao hơn khi bạn vượt qua khoảng 100 yếu tố.
(Ngưỡng chính xác sẽ phụ thuộc vào đầu vào, máy và nền tảng cụ thể của bạn.)
printf '%s\n' "${a_in[@]}" | sort
thực hiện sắp xếp (theo từ vựng, theo mặc định - xem sort
thông số POSIX ):
"${a_in[@]}"
mở rộng một cách an toàn các phần tử của mảng a_in
dưới dạng các đối số riêng lẻ , bất cứ thứ gì chúng chứa (bao gồm cả khoảng trắng).
printf '%s\n'
sau đó in từng đối số - tức là từng phần tử mảng - trên dòng riêng của nó, như hiện trạng.
Lưu ý việc sử dụng thay thế quy trình ( <(...)
) để cung cấp đầu ra được sắp xếp làm đầu vào read
/ readarray
(thông qua chuyển hướng đến stdin, <
), bởi vì read
/ readarray
phải chạy trong trình bao hiện tại (không được chạy trong lớp con ) a_out
để hiển thị biến đầu ra đến trình bao hiện tại (để biến vẫn được xác định trong phần còn lại của tập lệnh).
Đọc sort
kết quả đầu ra thành một biến mảng :
Bash v4 +: readarray -t a_out
đọc đầu ra các dòng riêng lẻ bằng cách sort
vào các phần tử của biến mảng a_out
, mà không bao gồm dấu \n
ở mỗi phần tử ( -t
).
Bash v3: readarray
không tồn tại, do đó read
phải được sử dụng:
IFS=$'\n' read -d '' -r -a a_out
bảo read
đọc vào biến mảng ( -a
) a_out
, đọc toàn bộ đầu vào, trên các dòng ( -d ''
), nhưng tách nó thành các phần tử mảng bằng các dòng mới ( IFS=$'\n'
. $'\n'
, Tạo ra một dòng mới theo nghĩa đen (LF ), là một chuỗi được gọi là ANSI C-trích dẫn ).
( -r
, một tùy chọn hầu như luôn luôn được sử dụng read
, vô hiệu hóa việc xử lý các \
ký tự không mong muốn .)
Mã mẫu được chú thích:
#!/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[@]}"
Do sử dụng sort
không có tùy chọn, điều này mang lại sự sắp xếp từ vựng (chữ số sắp xếp trước chữ cái và chuỗi chữ số được xử lý theo từ vựng , không phải là số):
*
10
5
a c
b
f
Nếu bạn muốn sắp xếp số theo trường thứ 1, bạn sẽ sử dụng sort -k1,1n
thay vì chỉ sort
, sẽ mang lại (sắp xếp không phải là số trước số và sắp xếp số chính xác):
*
a c
b
f
5
10
[1] Để xử lý các phần tử có dòng mới được nhúng, hãy sử dụng biến thể sau (Bash v4 +, với GNU sort
) :
readarray -d '' -t a_out < <(printf '%s\0' "${a_in[@]}" | sort -z)
.
Câu trả lời hữu ích của Michał Górny có giải pháp Bash v3.
[2] Trong khi IFS
được đặt trong biến thể Bash v3, thay đổi nằm trong phạm vi lệnh .
Ngược lại, những gì tiếp theo IFS=$'\n'
trong câu trả lời của antak là một bài tập chứ không phải là một lệnh, trong trường hợp đó sự IFS
thay đổi là toàn cầu .
Trong chuyến đi kéo dài 3 giờ từ Munich đến Frankfurt (mà tôi gặp khó khăn để tiếp cận vì Lễ hội tháng mười bắt đầu vào ngày mai) tôi đã suy nghĩ về bài đăng đầu tiên của mình. Sử dụng một mảng toàn cầu là một ý tưởng tốt hơn nhiều cho một hàm sắp xếp chung. Hàm sau xử lý các chuỗi tùy ý (dòng mới, khoảng trắng, v.v.):
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[@]}
Bản in này:
3 5 a b c z y
Đầu ra tương tự được tạo ra từ
BSORT=(a c b 'z y' 3 5)
bubble_sort
echo ${BSORT[@]}
Lưu ý rằng có lẽ Bash trong nội bộ sử dụng các con trỏ thông minh, do đó, hoạt động hoán đổi có thể rẻ (mặc dù tôi nghi ngờ điều đó). Tuy nhiên, bubble_sort
chứng minh rằng các chức năng nâng cao hơn như merge_sort
cũng nằm trong phạm vi của ngôn ngữ shell.
local -n BSORT="$1"
khi bắt đầu hàm. Sau đó, bạn có thể chạy bubble_sort myarray
để sắp xếp myarray .
Một giải pháp khác sử dụng bên ngoài sort
và đối phó với bất kỳ ký tự đặc biệt nào (ngoại trừ NULs :)). Nên hoạt động với bash-3.2 và GNU hoặc BSD sort
(đáng buồn thay, POSIX không bao gồm -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)
Đầu tiên nhìn vào chuyển hướng đầu vào ở cuối. Chúng tôi đang sử dụng tích printf
hợp để viết ra các phần tử mảng, không kết thúc. Việc trích dẫn đảm bảo các phần tử mảng được truyền printf
nguyên trạng và các chi tiết cụ thể của shell khiến nó sử dụng lại phần cuối của chuỗi định dạng cho mỗi tham số còn lại. Đó là, nó tương đương với một cái gì đó như:
for e in "${array[@]}"; do
printf "%s\0" "${e}"
done
Danh sách phần tử kết thúc null sau đó được chuyển đến sort
. Các -z
tùy chọn gây ra nó để đọc các yếu tố null-chấm dứt, sắp xếp chúng và đầu ra null-chấm dứt là tốt. Nếu bạn chỉ cần lấy các yếu tố duy nhất, bạn có thể vượt qua -u
vì nó dễ mang theo hơn uniq -z
. Thứ LC_ALL=C
tự sắp xếp ổn định độc lập với miền địa phương - đôi khi hữu ích cho các tập lệnh. Nếu bạn muốn sort
tôn trọng miền địa phương, hãy loại bỏ điều đó.
Cấu <()
trúc có được bộ mô tả để đọc từ đường ống được sinh ra và <
chuyển hướng đầu vào tiêu chuẩn của while
vòng lặp đến nó. Nếu bạn cần truy cập vào đầu vào tiêu chuẩn bên trong đường ống, bạn có thể sử dụng một mô tả khác - bài tập cho người đọc :).
Bây giờ, trở lại từ đầu. Đầu ra tích read
hợp đọc từ stdin chuyển hướng. Đặt trống sẽ IFS
vô hiệu hóa việc tách từ không cần thiết ở đây - kết quả là, read
đọc toàn bộ 'dòng' đầu vào cho biến được cung cấp duy nhất. -r
tùy chọn cũng vô hiệu hóa xử lý thoát không mong muốn ở đây. Cuối cùng, -d ''
đặt dấu phân cách dòng thành NUL - nghĩa là bảo read
đọc các chuỗi kết thúc bằng không.
Kết quả là, vòng lặp được thực thi một lần cho mỗi phần tử mảng kết thúc bằng 0 liên tiếp, với giá trị được lưu trữ trong e
. Ví dụ chỉ đặt các mục trong một mảng khác nhưng bạn có thể thích xử lý chúng trực tiếp :).
Tất nhiên, đó chỉ là một trong nhiều cách để đạt được cùng một mục tiêu. Như tôi thấy, nó đơn giản hơn việc thực hiện thuật toán sắp xếp hoàn chỉnh trong bash và trong một số trường hợp, nó sẽ nhanh hơn. Nó xử lý tất cả các ký tự đặc biệt bao gồm các dòng mới và sẽ hoạt động trên hầu hết các hệ thống phổ biến. Quan trọng nhất, nó có thể dạy cho bạn một cái gì đó mới và tuyệt vời về bash :).
e
và đặt IFS trống, hãy sử dụng biến REPLY.
thử cái này:
echo ${array[@]} | awk 'BEGIN{RS=" ";} {print $1}' | sort
Đầu ra sẽ là:
3 5 một b c f
Vấn đề được giải quyết.
Nếu bạn có thể tính một số nguyên duy nhất cho mỗi phần tử trong mảng, như sau:
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 -> $?"
sau đó, bạn có thể sử dụng các số nguyên này làm chỉ mục mảng, vì Bash luôn sử dụng mảng thưa thớt, do đó không cần phải lo lắng về các chỉ mục không được sử dụng:
array=(a c b f 3 5)
for el in "${array[@]}"; do
sexy_int "$el"
sorted[$?]="$el"
done
echo "${sorted[@]}"
sắp xếp tối thiểu:
#!/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
Có một cách giải quyết cho vấn đề thông thường về không gian và dòng mới:
Sử dụng một nhân vật đó không phải là trong mảng ban đầu (như $'\1'
hoặc $'\4'
hoặc tương tự).
Hàm này hoàn thành công việc:
# 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
}
Điều này sẽ sắp xếp mảng:
$ array=( a b 'c d' $'e\nf' $'g\1h')
$ sortarray "${array[@]}"
$ printf '<%s>\n' "${sorted[@]}"
<a>
<b>
<c d>
<e
f>
<gh>
Điều này sẽ phàn nàn rằng mảng nguồn chứa ký tự giải pháp:
$ array=( a b 'c d' $'e\nf' $'g\4h')
$ sortarray "${array[@]}"
./script: error: array contains the workaround char
wa
(workaround char) và IFS null$*
.[[ $* =~ [$wa] ]]
.exit 1
set -f
IFS=$'\n'
) một biến vòng lặp x
và một dòng mới var ( nl=$'\n'
).$@
)."${@//$nl/$wa}"
.sort -n
.set --
.for x
sorted+=(…)
"${x//$wa/$nl}"
.Câu hỏi này có vẻ liên quan chặt chẽ. Và BTW, đây là một sự hợp nhất trong Bash (không có các quy trình bên ngoài):
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[@]}"
Tôi không tin rằng bạn sẽ cần một chương trình sắp xếp bên ngoài ở Bash.
Đây là triển khai của tôi cho thuật toán sắp xếp bong bóng đơn giản.
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[@]})"
Điều này sẽ in:
input: a c b f 3 5
output: 3 5 a b c f
O(n^2)
. Tôi dường như nhớ lại hầu hết các thuật toán sắp xếp sử dụng O(n lg(n))
cho đến hàng tá phần tử cuối cùng hoặc lâu hơn. Đối với các yếu tố cuối cùng, sắp xếp lựa chọn được sử dụng.
a=(e b 'c d')
shuf -e "${a[@]}" | sort >/tmp/f
mapfile -t g </tmp/f
sorted=($(echo ${array[@]} | tr " " "\n" | sort))
Theo tinh thần của bash / linux, tôi sẽ sử dụng công cụ dòng lệnh tốt nhất cho mỗi bước. sort
thực hiện công việc chính nhưng cần đầu vào được phân tách bằng dòng mới thay vì không gian, do đó, đường ống rất đơn giản ở trên chỉ đơn giản là:
Echo nội dung mảng -> thay thế không gian bằng dòng mới -> sắp xếp
$()
là để lặp lại kết quả
($())
là đặt "kết quả lặp lại" trong một mảng
Lưu ý : như @sorontar đã đề cập trong một nhận xét cho một câu hỏi khác:
Phần được sắp xếp = ($ (...)) đang sử dụng toán tử "split and global". Bạn nên tắt global: set -f hoặc set -o noglob hoặc shopt -op noglob hoặc một phần tử của mảng như * sẽ được mở rộng thành một danh sách các tệp.
mapfile -t sorted < <(printf '%s\n' "${array[@]}" | sort)
, nếu không sorted=(); while IFS= read -r line; do sorted+=( "$line" ); done < <(printf '%s\n' | sort)
.
echo ${array[@]} | tr " " "\n"
điều này sẽ bị phá vỡ nếu các trường của mảng chứa khoảng trắng và ký tự toàn cầu. Bên cạnh đó, nó sinh ra một lớp con và sử dụng một lệnh bên ngoài vô dụng. Và do echo
bị câm, nó sẽ bị hỏng nếu mảng của bạn bắt đầu bằng -e
, -E
hoặc -n
. Thay vào đó sử dụng : printf '%s\n' "${array[@]}"
. Các antipotype khác là: ($())
đặt "kết quả lặp lại" trong một mảng . Chắc chắn không! đây là một antipotype khủng khiếp bị phá vỡ vì sự mở rộng tên đường dẫn (globalbing) và chia tách từ. Không bao giờ sử dụng kinh dị này.
IFS
, nó sẽ chia các phần tử của bạn thành các phần nhỏ nếu chúng có khoảng trắng trong đó. Hãy thử ví dụ vớiIFS=$'\n'
bỏ qua và xem!