Bashで配列を並べ替える方法


139

たとえば、Bashに配列があります。

array=(a c b f 3 5)

配列をソートする必要があります。コンテンツを並べ替えて表示するだけでなく、並べ替えられた要素を含む新しい配列を取得します。新しいソートされた配列は、完全に新しいものでも古いものでもかまいません。

回答:


208

実際にはそれほど多くのコードは必要ありません。

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

サポート要素内の空白(それは改行ではありません長いほど)、およびバッシュ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は、要素にor などのワイルドカードが含まれている場合は注意が必要であることを指摘しました:*?

Sorted =($(...))の部分は、「split and glob」演算子を使用しています。グロブをオフにする必要があります:set -fまたはset -o noglobまたはshopt -op noglobまたは配列の要素*がファイルのリストに展開されます。

何が起こっていますか:

結果は、この順序で発生する6つの事柄の集大成です。

  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場合、空白が含まれていると、要素が細かく分割されます。省略してegを試してみてIFS=$'\n' ください!
antak

3
非常に素晴らしい。平均的なbashユーザーのために、このソリューションがどのように機能するかを説明できますか?
u32004、2015年

2
今、を使用するとIFS、特定の種類の空白のみが含まれている場合、要素が小さな断片に分割されます。良い; 完璧ではない:-)
限定的な贖罪

7
であるunset IFS必要?IFS=コマンドの先頭に追加すると、そのコマンドへの変更のみがスコープされ、その後、自動的に前の値に戻ると思いました。
Mark H

10
@MarkH sorted=()はコマンドではなく、2番目の変数割り当てなので必要です。
2016

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+ bashでサポートされています。


編集 @Dimitreの提案に基づいて、私はそれを次のように更新しました。

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

これには、改行文字が正しく埋め込まれたソート要素を理解するという利点もあります。残念ながら、@ ruakhによって正しく通知されたので、これはの結果readarray正しいことを意味しませんでした。通常の改行の代わりに行区切りとしてreadarray使用するオプションがないためです。NUL


5
いいですね、readarrayはbashのバージョン4以降で利用できることにも注意してください。それは少しを短縮することができますreadarray -t sorted < <(printf '%s\n' "${array[@]}" | sort)
Dimitre Radoulov

1
@Dimitre:私はあなたの提案を取り入れ、空白処理を修正して何でも動作するようにしました(内部的にnullchar-delimitersを使用)。乾杯
sehe

1
はい、これsort -zは有用な改善です-z。オプションはGNUソート拡張であると思います。
Dimitre Radoulov、2011

2
埋め込まれた改行を処理したい場合は、独自のreadarrayをロールできます。例:sorted=(); while read -d $'\0' elem; do sorted[${#sorted[@]}]=$elem; done < <(printf '%s\0' "${array[@]}" | sort -z)。readarrayはbash v3では使用できないため、これはbash v4ではなくbash v3を使用している場合にも機能します。
Bob Bell

1
@ user1527227 プロセスの置換<と組み合わせた入力リダイレクト()です。または直感的に言えば、ファイルではないからです。 <(...)(printf "bla")
sehe 2014

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
入力要素とソート基準に関して優れた柔軟性を提供する印象的なバッシングの称賛。sort提供する並べ替えオプションを使用した行ベースの並べ替えで十分な場合、sort+ read -aソリューションは約20アイテムから開始し、処理する要素が増えるほど大幅に高速になります。たとえば、Fusion Driveを備えたOSX 10.11.1を実行している2012年後半のiMacでは、100エレメントのアレイ:ca。0.03秒 (qsort())対約 0.005秒 (sort+ read -a); 1000要素配列:約 0.375秒 (qsort())対約 0.014秒(sort+ read -a)。
mklement0 2015年

いいね。大学時代のクイックソートを覚えていますが、バブルソートについても研究します。私の並べ替えのニーズのために、最初の要素と2番目の要素がキーを形成し、その後に1つのデータ要素が続きます(後で拡張する場合があります)。キー要素の数(parm1)とデータ要素の数(parm2)でコードを改善できます。OPの場合、パラメーターは1と0になります。私にとっては、パラメーターは2と1になります。
WinEunuuchs2Unix 2017

1
キャストされていない文字列整数のデータセットを使用するとif [ "$i" -lt "$pivot" ]; then、解決された "2" <"10"がtrueを返します。私はこれがPOSIX対辞書的であると信じています。またはおそらくインラインリンク
Page2PagePro 2018

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

kshASCIIでset -sソートする必要があります。


とても素敵な背景情報。それはむしろオフトピックになるので、私はほとんどkshがセット-sフラグを使用する方法についてのデモのために求めるだろう...しかし、再度、質問はbashの上にある
sehe

これは、ほとんどのKornShell実装(たとえば、ksh88pdksh)で動作するはずset -A array x 'a a' d; set -s -- "${array[@]}"; set -A sorted "$@" です。もちろん、setコマンドは、現在の位置パラメータ(存在する場合)をリセットします。
Dimitre Radoulov、2011

あなたはシェル知識の真の泉です。この種の微妙な違いは人類の他のほとんどのメンバーを
回避する

10

tl; dr

配列a_inを並べ替えて結果を格納しますa_out(要素に改行を埋め込むことはできません[1] ):

Bash v4 +:

readarray -t a_out < <(printf '%s\n' "${a_in[@]}" | sort)

バッシュv3:

IFS=$'\n' read -d '' -r -a a_out < <(printf '%s\n' "${a_in[@]}" | sort)

アンタックのソリューションに対する利点:

  • あなたはそう余分なコマンドが無効グロブに必要とされている(、偶発グロブ(ファイル名パターンとして配列要素を誤って解釈)心配する必要はないset -f、とset +f後でそれを復元します)。

  • あなたは、再設定について心配する必要はありませんIFSunset IFS[2]


オプションの読み取り:説明とサンプルコード

上記は、Bashコードと外部ユーティリティsortを組み合わせて、任意の一行要素字句または数値の並べ替え(オプションでフィールドによる)機能するソリューションを実現します

  • パフォーマンス約20要素以上の場合、これは純粋なBashソリューションより高速になります。約100要素を超えると、大幅かつますます高速になります。
    (正確なしきい値は、特定の入力、マシン、およびプラットフォームによって異なります。)

    • 高速である理由は、Bashループを回避するためです。
  • 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)。

    • バッシュのV3:readarrayそう、存在しない場合readに使用する必要があります
      IFS=$'\n' read -d '' -r -a a_out指示read配列に読み取るために(-a)変数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

最初のフィールドによる数値の並べ替えが必要場合はsort -k1,1n、単にの代わりに使用しますsort。これにより、(数値の前に非数値が並べ替えられ、数値が正しく並べ替えられます):

*
a c
b
f
5
10

[1]改行が埋め込まれた要素を処理するには、次のバリアント(Bash v4 +、GNUを使用 sort)を使用します
readarray -d '' -t a_out < <(printf '%s\0' "${a_in[@]}" | sort -z)
MichałGórnyの役立つ回答には、Bash v3ソリューションがあります。

一方[2] IFS れるバッシュv3のバリアントに設定、変更がされているコマンドにスコープ
対照的に、IFS=$'\n' antakの回答の後に続くのは、コマンドではなく割り当てです。この場合、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は内部的にスマートポインターを使用しているので、スワップ操作安価である可能性があります(ただし、疑わしいです)。ただし、などのbubble_sortより高度な関数merge_sortもシェル言語の範囲内にあることを示しています。


5
バブルソート?うわー..オバマ氏は「バブルソートは間違った方法になるだろう」-> youtube.com/watch?v=k4RRi_ntQc8
Robottinosino 2013

1
さて、O-guyは賢くなりたいと思っていましたが、これは50/50の偶然の質問ではないと感じていませんでした。O-Guyの前任者、B-Guyに話をしましょう。以前ははるかに上手でした(2000年10月、オハイオ州レイノルズバーグ)。質問にはお答えできません。」したがって、このB男はブール論理について本当に知っています。O-guyはしません。
Andreas Spindler 2013

BSORTを、ソートする配列の名前参照を持つローカル配列にすることで、関数をより簡単に移植できます。つまりlocal -n BSORT="$1"、関数の開始時。次に、実行bubble_sort myarrayしてmyarrayをソートできます。
johnraff 2017年

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

次に、nullで終了する要素のリストがに渡されsortます。この-zオプションにより、nullで終了する要素が読み取られ、並べ替えられ、nullで終了する要素も出力されます。固有の要素のみを取得する必要がある場合-uは、よりも移植性が高いため、渡すことができますuniq -zLC_ALL=Cスクリプトのための便利な場合-独立したロケールの安定ソート順序を保証します。sortロケールを尊重したい場合は、それを削除してください。

<()構築物は、生成されたパイプラインから読み取るために、ディスクリプタを取得し、<標準入力をリダイレクトwhileそれにループ。パイプ内の標準入力にアクセスする必要がある場合は、別の記述子を使用することができます—読者のために練習してください:)。

さて、最初に戻りましょう。readビルトインは、リダイレクト標準入力から出力を読み込みます。空IFSに設定すると、ここでは不要な単語分割が無効になります。その結果、read入力の「行」全体が単一の提供された変数に読み込まれます。-rオプションは、ここでも望ましくないエスケープ処理を無効にします。最後に-d ''、行区切り文字をNUL に設定します—つまり、readます。ゼロで終了する文字列を読み取るようします。

その結果、ゼロで終了する配列要素ごとにループが1回実行され、値がに格納されeます。例ではアイテムを別の配列に配置するだけですが、直接処理することもできます:)。

もちろん、これは同じ目標を達成するための多くの方法の1つにすぎません。私が見るように、bashで完全なソートアルゴリズムを実装するよりも簡単で、場合によってはより高速になります。改行を含むすべての特殊文字を処理し、ほとんどの一般的なシステムで動作するはずです。最も重要なのは、それはbashについて何か新しくて素晴らしいものを教えるかもしれません:)。


素晴らしい解決策と非常に役立つ説明、ありがとう。1つの拡張:IFSを空に設定しないと、先頭の空白も削除されます。
Dirk Herrmann

ローカル変数を導入してe空のIFSを設定する代わりに、REPLY変数を使用します。
ロビンA.ミード

2

これを試して:

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

出力は次のようになります。

3
5
a
b
c
f

問題が解決しました。


3
これを編集して出力を新しい配列に入れ、彼の質問に完全に答える必要があります。
Peter Oram、2012年

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

解説

  • 2つのローカル変数wa(回避文字)とnull IFS を設定します
  • 次に(ifs nullを使用して)配列全体をテストし$*ます。
  • woraround charは含まれていません[[ $* =~ [$wa] ]]
  • 存在する場合は、メッセージを表示してエラーを通知します。 exit 1
  • ファイル名の拡張は避けてください: set -f
  • IFSの新しい値(IFS=$'\n')、ループ変数x、および改行変数()を設定しますnl=$'\n'
  • 受け取った引数のすべての値を出力します(入力配列$@)。
  • ただし、新しい行は回避策のcharに置き換えます"${@//$nl/$wa}"
  • ソートする値を送信しますsort -n
  • そして、ソートされたすべての値を位置引数に戻しますset --
  • 次に、各引数を1つずつ割り当てます(改行を保持するため)。
  • ループで 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 2016年


-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 2016年

使用しているアンチパターンは次のecho ${array[@]} | tr " " "\n"とおりです。:これは、配列のフィールドに空白文字とグロブ文字が含まれていると壊れます。また、サブシェルを生成し、役に立たない外部コマンドを使用します。echo馬鹿げているため、配列が-e-Eまたはで始まる場合は壊れます-n。代わりに:を使用してくださいprintf '%s\n' "${array[@]}"。もう1つのアンチパターンは($())、「エコーされた結果」を配列に入れることです。確かにありません!これは、パス名の展開(グロビング)と単語の分割のために壊れる恐ろしいアンチパターンです。この恐怖を決して使用しないでください。
gniourf_gniourf 2016年

一番上の答えは「恐ろしいアンチパターン」です。そして、あなたが自分で答えた質問に対する他の誰かの答えに反対投票する方法。
マイケル2016年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.