たとえば、Bashに配列があります。
array=(a c b f 3 5)
配列を並べ替える必要があります。ソートされた方法でコンテンツを表示するだけでなく、ソートされた要素を持つ新しい配列を取得します。新しいソートされた配列は、まったく新しい配列でも古い配列でもかまいません。
それほど多くのコードは必要ありません。
IFS=$'\n' sorted=($(sort <<<"${array[*]}"))
unset IFS
要素内の空白をサポートします(改行でない限り)。andは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には 指摘 があり、要素に*
や?
などのワイルドカードが含まれている場合は注意が必要です。
Sort =($(...))部分は「split and glob」演算子を使用しています。 globをオフにする必要があります:
set -f
またはset -o noglob
またはshopt -op noglob
、または*
のような配列の要素は、ファイルのリストに展開されます。
その結果、次の6つのことがこの順序で行われます。
IFS=$'\n'
"${array[*]}"
<<<
sort
sorted=($(...))
unset IFS
IFS=$'\n'
これは、2と5の結果に次のように影響する操作の重要な部分です。
与えられた:
"${array[*]}"
は、IFS
の最初の文字で区切られたすべての要素に展開されますsorted=()
は、IFS
のすべての文字で分割して要素を作成しますIFS=$'\n'
設定 区切り文字としてa new lineを使用して要素が展開され、その後各行が要素になるように作成されます。 (つまり、新しい行で分割します。)
新しい行で区切ることが重要です。なぜなら、sort
がどのように動作するか(行ごとに並べ替える)からです。 onlyによる分割は重要ではありませんが、スペースまたはタブを含む要素を保持する必要があります。
IFS
のデフォルト値はa space、a tab、それに続くa new lineであり、この操作には不適当です。
sort <<<"${array[*]}"
部分here文字列 と呼ばれる<<<
は、上記で説明したように"${array[*]}"
を展開し、sort
の標準入力に送ります。
この例では、sort
に次の文字列が渡されます。
a c
b
f
3 5
sort
sortsであるため、以下を生成します。
3 5
a c
b
f
sorted=($(...))
部分コマンド置換 と呼ばれる$(...)
部分は、その内容(sort <<<"${array[*]}
)を通常のコマンドとして実行し、結果の標準出力を$(...)
があった場所に行くリテラル。
この例では、これは単に次のように書くことに似ています。
sorted=(3 5
a c
b
f
)
sorted
は、このリテラルを新しい行ごとに分割することで作成される配列になります。
unset IFS
これにより、IFS
の値がデフォルト値にリセットされます。これは適切な方法です。
スクリプトの後半でIFS
に依存するもので問題が発生しないようにするためです。 (そうでない場合は、物事をやり直したことを覚えておく必要があります。これは、複雑なスクリプトには実用的ではないかもしれません。)
元の応答:
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
注このバージョンは、特殊文字または空白を含む値を処理します(except改行)
注 readarrayはbash 4+でサポートされています。
編集 @Dimitreの提案に基づき、私はそれを次のように更新しました:
readarray -t sorted < <(printf '%s\0' "${array[@]}" | sort -z | xargs -0n1)
これには、改行文字が正しく埋め込まれたsorting要素を理解するという利点もあります。残念ながら、@ ruakhによって正しく通知されたように、これはreadarray
の代わりにreadarray
を使用するオプションがないため、NUL
の結果がcorrectになることを意味しませんでした通常のnewlines行区切りとして。
以下は、純粋な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
}
どちらの場合でも、使用する順序を変更できます。文字列比較を使用しましたが、算術比較を使用したり、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です!外部ユーティリティもサブシェルもありません!面白い記号(スペース、改行文字、グロブ文字など)に対して安全です。
配列要素で特殊なシェル文字を処理する必要がない場合:
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。
ミュンヘンからフランクフルトへの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は内部的にスマートポインターを使用しているため、スワップ操作couldは安価であることに注意してください(疑いはありますが)。ただし、bubble_sort
は、merge_sort
などのより高度な関数もシェル言語の範囲内にあることを示しています。
外部sort
を使用し、any特殊文字(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
組み込みを使用して、ゼロで終了する配列要素を書き出します。引用は、配列要素がそのまま渡されることを確認し、Shell printf
の詳細により、残りの各パラメーターに対してフォーマット文字列の最後の部分が再利用されます。つまり、次のようなものと同等です。
for e in "${array[@]}"; do
printf "%s\0" "${e}"
done
次に、ヌル終了要素リストがsort
に渡されます。 -z
オプションを使用すると、NULLで終了する要素が読み取られ、ソートされ、NULLで終了する要素も出力されます。一意の要素のみを取得する必要がある場合は、-u
よりも移植性が高いため、uniq -z
を渡すことができます。 LC_ALL=C
は、ロケールに関係なく安定したソート順を保証します—スクリプトに役立つ場合があります。 sort
にロケールを尊重させる場合は、それを削除します。
<()
構造は、生成されたパイプラインから読み取る記述子を取得し、<
はwhile
ループの標準入力をリダイレクトします。パイプ内の標準入力にアクセスする必要がある場合は、別の記述子を使用できます—読者のために練習してください:)。
さて、最初に戻りましょう。 read
ビルトインは、リダイレクトされたstdinから出力を読み取ります。空のIFS
を設定すると、ここで不要なWord分割が無効になります。その結果、read
は、指定された単一の変数への入力の「行」全体を読み取ります。 -r
オプションは、ここでも望ましくないエスケープ処理を無効にします。最後に、-d ''
は行区切り文字をNULに設定します。つまり、read
にゼロで終わる文字列を読み取るように指示します。
その結果、ループは連続するゼロで終わる配列要素ごとに1回実行され、値はe
に格納されます。この例では、アイテムを別の配列に配置するだけですが、それらを直接処理することもできます:)。
もちろん、それは同じ目標を達成する多くの方法の1つにすぎません。ご覧のとおり、bashで完全なソートアルゴリズムを実装するよりも簡単で、場合によっては高速になります。改行を含むすべての特殊文字を処理し、ほとんどの一般的なシステムで動作するはずです。最も重要なことは、bashについて新しくて素晴らしい何かを教えてくれるかもしれません:)。
これを試して:
echo ${array[@]} | awk 'BEGIN{RS=" ";} {print $1}' | sort
出力は次のようになります。
3 5 a b c f
問題が解決しました。
最小ソート:
#!/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
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
次のように、配列の各要素に対して一意の整数を計算できる場合:
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[@]}"
スペースと改行の通常の問題に対する回避策があります。
元の配列にない文字($'\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
(回避策文字)とnull IFSを設定します$*
をテストします。[[ $* =~ [$wa] ]]
は含まれていません。exit 1
set -f
IFS=$'\n'
)、ループ変数x
、および改行変数(nl=$'\n'
)を設定します。$@
)を出力します。"${@//$nl/$wa}"
に置き換えます。sort -n
でソートします。set --
に戻します。for x
sorted+=(…)
"${x//$wa/$nl}"
に復元します。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
a=(e b 'c d')
shuf -e "${a[@]}" | sort >/tmp/f
mapfile -t g </tmp/f