簡體   English   中英

如何對 Bash 中的數組進行排序

[英]How to sort an array in Bash

我在 Bash 中有一個數組,例如:

array=(a c b f 3 5)

我需要對數組進行排序。 不僅僅是以排序的方式顯示內容,而是獲得一個包含排序元素的新數組。 新的排序數組可以是全新的,也可以是舊的。

你真的不需要那么多代碼:

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 -fset -o noglobshopt -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[*]}"部分

<<<這里稱為 strings ,采用"${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

請注意,此版本處理包含特殊字符或空格的值(換行符除外

注意bash 4+ 支持 readarray。


編輯根據@Dimitre 的建議,我已將其更新為:

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

它的好處是甚至可以理解正確嵌入換行符的排序元素。 不幸的是,正如@ruakh 正確表示的那樣,這並不意味着readarray的結果是正確的,因為readarray沒有選擇使用NUL而不是常規換行符作為行分隔符。

這是一個純 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
      # This sorts strings lexicographically.
      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! 沒有外部實用程序,也沒有子shell! 對於您可能擁有的任何有趣符號(空格、換行符、全局字符等),它們都是安全的。

筆記2。 測試[[ $i < $pivot ]]是正確的。 它使用字典字符串比較。 如果您的數組僅包含整數並且您想按數字排序,請改用((i < pivot)) 請不要編輯這個答案來改變它。 它已經被編輯(並回滾)了幾次。 我這里給出的測試是正確的,並且與示例中給出的輸出相對應:示例同時使用了字符串和數字,目的是按字典順序對其進行排序。 在這種情況下使用((i < pivot))是錯誤的

如果您不需要處理數組元素中的特殊 shell 字符:

array=(a c b f 3 5)
sorted=($(printf '%s\n' "${array[@]}"|sort))

無論如何,使用bash您將需要一個外部排序程序。

使用zsh不需要外部程序,並且可以輕松處理特殊的 shell 字符:

% array=('a a' c b f 3 5); printf '%s\n' "${(o)array[@]}" 
3
5
a a
b
c
f

ksh已將set -s為按ASCII排序。

tl;博士

對數組a_in進行排序並將結果存儲在a_out中(元素不得嵌入換行符[1] ):

重擊 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)

antak 解決方案相比的優勢:

  • 您不必擔心意外的通配(將數組元素意外解釋為文件名模式),因此不需要額外的命令來禁用通配( set -fset +f稍后恢復它)。

  • 您不必擔心使用unset IFS IFS [2]


可選閱讀:解釋和示例代碼

上面將 Bash 代碼與外部實用程序sort相結合,以提供一種適用於任意單行元素以及詞法或數字排序(可選按字段)的解決方案:

  • 性能:對於大約 20 個或更多元素,這將比純 Bash 解決方案更快- 一旦超過大約 100 個元素,速度就會越來越快。
    (確切的閾值取決於您的具體輸入、機器和平台。)

    • 它快速的原因是它避免了 Bash 循環
  • printf '%s\n' "${a_in[@]}" | sort printf '%s\n' "${a_in[@]}" | sort執行排序(在詞匯上,默認情況下 - 請參閱sort的 POSIX 規范):

    • "${a_in[@]}"安全地擴展為數組a_in的元素作為單獨的參數,無論它們包含什么(包括空格)。

    • printf '%s\n'然后按原樣在其自己的行上打印每個參數 - 即每個數組元素。

  • 請注意使用進程替換( <(...)將排序輸出作為輸入提供給read / readarray (通過重定向到標准輸入, < ),因為read / readarray必須在當前shell 中運行(不能在一個subshel ​​l ),以便輸出變量a_out對當前 shell 可見(以便該變量在腳本的其余部分中保持定義)。

  • 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 )變量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在 Bash v3 變體設置,但更改的范圍僅限於 command
相比之下,在安塔克的答案中, IFS=$'\n'后面的內容是分配而不是命令,在這種情況下, IFS更改是全局的。

另一種使用外部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來寫出以零結尾的數組元素。 引用確保數組元素按原樣傳遞,並且 shell printf的細節導致它為每個剩余參數重用格式字符串的最后一部分。 也就是說,它相當於:

for e in "${array[@]}"; do
    printf "%s\0" "${e}"
done

然后將 null 終止的元素列表傳遞給sort -z選項使其讀取以空字符結尾的元素,對它們進行排序並輸出以空字符結尾的元素。 如果您只需要獲取唯一元素,則可以傳遞-u因為它比uniq -z更便攜。 LC_ALL=C確保獨立於語言環境的穩定排序順序——有時對腳本很有用。 如果您希望sort尊重語言環境,請將其刪除。

<()構造獲取要從生成的管道讀取的描述符,並且<while循環的標准輸入重定向到它。 如果你需要訪問管道內的標准輸入,你可以使用另一個描述符——讀者練習:)。

現在,回到開始。 read內置從重定向的標准輸入讀取輸出。 設置空IFS會禁用分詞,這在此處是不必要的——因此, read會將輸入的整個“行”讀取到單個提供的變量。 -r選項也禁用此處不需要的轉義處理。 最后, -d ''將行分隔符設置為 NUL——也就是說,告訴read以零結尾的字符串。

結果,對於每個連續的以零結尾的數組元素執行一次循環,值存儲在e中。 該示例只是將項目放在另一個數組中,但您可能更喜歡直接處理它們:)。

當然,這只是實現同一目標的眾多方法之一。 正如我所看到的,它比在 bash 中實現完整的排序算法更簡單,並且在某些情況下它會更快。 它處理所有特殊字符,包括換行符,並且應該適用於大多數常見系統。 最重要的是,它可能會教你一些關於 bash 的新奇事物 :)。

在從慕尼黑到法蘭克福的 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這樣的更高級的函數也在 shell 語言的范圍內。

如果您可以為數組中的每個元素計算一個唯一的整數,如下所示:

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 位唯一整數。

最小排序:

#!/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

嘗試這個:

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

輸出將是:

3
5
a
b
c
f

問題解決了。

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'$'\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 (workaround char)和一個空 IFS
  • 然后(使用 ifs null)我們測試整個數組$*
  • 不包含任何 woraround char [[ $* =~ [$wa] ]]
  • 如果是,請發出消息並發出錯誤信號: exit 1
  • 避免文件名擴展: set -f
  • 設置 IFS 的新值 ( IFS=$'\n' ) 循環變量x和換行符 ( nl=$'\n' )。
  • 我們打印接收到的參數的所有值(輸入數組$@ )。
  • 但是我們用解決方法 char "${@//$nl/$wa}"替換任何新行。
  • 發送這些值進行排序sort -n
  • 並將所有排序的值放回位置參數set --
  • 然后我們一個一個地分配每個參數(以保留換行符)。
  • for x
  • 到一個新數組: sorted+=(…)
  • 引號內以保留任何現有的換行符。
  • 將解決方法恢復到換行符"${x//$wa/$nl}"
  • 完畢

這個問題看起來密切相關。 順便說一句,這是 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[@]}"

把事情簡單化 ;)

在以下示例中,數組b是數組a的排序版本!

第二行echo s 數組a的每一項,然后通過pipe將它們傳遞給sort命令,輸出用於啟動數組b

a=(2 3 1)
b=( $( for x in ${a[@]}; do echo $x; done | sort ) )
echo ${b[@]} # output: 1 2 3
a=(e b 'c d')
shuf -e "${a[@]}" | sort >/tmp/f
mapfile -t g </tmp/f

非常感謝在我之前回答的人。 使用他們出色的輸入、bash 文檔和其他方面的想法,這對我來說是完美的,無需 IFS 更改

array=("a \n c" b f "3 5")

在 bash > v4.4 WITH EOL 字符中使用進程替換和讀取數組

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

在 bash > v4.4 WITH NULL 字符中使用進程替換和讀取數組

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

最后我們驗證

printf "[%s]\n" "${sorted[@]}"

輸出是

[3 5]
[a \n c]
[b]
[f]

請讓我知道這是否是對嵌入式 /n 的正確測試,因為兩種解決方案都會產生相同的結果,但第一個解決方案不應該與嵌入式 /n 一起正常工作

我不相信您需要在 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

這里有很好的答案。 學到了很多。 看完所有內容后,我想我會把我的帽子扔進戒指。 我認為這是最短的方法(並且可能更快,因為它沒有做太多 shell 腳本解析,盡管存在printfsort的生成問題,但它們每個只調用一次)並處理數據中的空白:

a=(3 "2 a" 1)                                                # Setup!
IFS=$'\n' b=( $(printf "%s\n" "${a[@]}" | sort) ); unset IFS # Sort!
printf "'%s' " "${b[@]}";                                    # Success!

輸出:

'1' '2 a' '3'

請注意, IFS更改僅限於 scope 所在的行。 如果您知道數組中沒有空格,則不需要IFS修改。

靈感來自@yas 的回答和@Alcamtar 的評論。

編輯

哦,我不知何故錯過了實際接受的答案,它比我的還要短。 呸!

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

事實證明, unset是必需的,因為這是一個沒有命令的變量賦值。

我建議去回答那個答案,因為它有一些關於 globbing 的有趣的東西,如果數組中有通配符,這些東西可能是相關的。 它還詳細描述了正在發生的事情。

編輯 2

GNU 有一個擴展,其中排序使用\0分隔記錄,如果您的數據中有 LF,這很好。 但是,當它返回到 shell 以分配給數組時,我看不到轉換它以便 shell 將在\0上定界的好方法,因為即使設置IFS=$'\0' ,shell 也不會不喜歡也沒有好好拆散。

array=(z 'b c'); { set "${array[@]}"; printf '%s\n' "$@"; } \
    | sort \
    | mapfile -t array; declare -p array
declare -a array=([0]="b c" [1]="z")
  • 打開一個內聯函數{...}以獲取一組新的位置參數(例如$1$2等)。
  • 將數組復制到位置參數。 (例如set "${array[@]}"會將第 n 個數組參數復制到第 n 個位置參數。注意引號保留了可能包含在數組元素中的空格)。
  • 打印每個位置參數(例如printf '%s\n' "$@"將在其自己的行上打印每個位置參數。同樣,請注意引號保留每個位置參數中可能包含的空白)。
  • 然后sort做它的事情。
  • 使用 mapfile 將流讀入數組(例如mapfile -t array將每一行讀入變量array ,而-t忽略每行中的\n )。
  • 轉儲數組以顯示其已排序。

作為一個函數:

set +m
shopt -s lastpipe

sort_array() { 
    declare -n ref=$1
    set "${ref[@]}"
    printf '%s\n' "$@"
    | sort \
    | mapfile -t $ref
}

然后

array=(z y x); sort_array array; declare -p array
declare -a array=([0]="x" [1]="y" [2]="z")

我期待着被所有的 UNIX 大師撕碎! :)

sorted=($(echo ${array[@]} | tr " " "\n" | sort))

本着 bash / linux 的精神,我會為每個步驟提供最好的命令行工具。 sort完成主要工作,但需要用換行符而不是空格分隔輸入,因此上面非常簡單的管道只需執行以下操作:

echo 數組內容 --> 用換行符替換空格 --> 排序

$()是回顯結果

($())是將“回顯結果”放入數組中

注意:正如@sorontar 在對另一個問題的評論中提到的那樣:

sorted=($(...)) 部分使用“split and glob”運算符。 您應該關閉 glob: set -f 或 set -o noglob 或 shopt -op noglob 或像 * 這樣的數組元素將擴展為文件列表。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM