【问题标题】:How to sort an array in Bash如何在 Bash 中对数组进行排序
【发布时间】:2011-11-18 12:52:15
【问题描述】:

我在 Bash 中有一个数组,例如:

array=(a c b f 3 5)

我需要对数组进行排序。不仅以排序的方式显示内容,还可以获得一个包含排序元素的新数组。新排序的数组可以是全新的也可以是旧的。

【问题讨论】:

    标签: arrays bash shell sorting


    【解决方案1】:

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

    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 有 pointed out,如果元素包含诸如 *? 之类的通配符,则需要注意:

    sorted=($(...)) 部分使用“split and glob”运算符。您应该关闭 glob:set -fset -o noglobshopt -op noglob 或像 * 这样的数组元素将扩展为文件列表。

    发生了什么:

    结果是按此顺序发生的六件事的高潮:

    1. IFS=$'\n'
    2. "${array[*]}"
    3. &lt;&lt;&lt;
    4. sort
    5. sorted=($(...))
    6. unset IFS

    首先,IFS=$'\n'

    这是我们操作的一个重要部分,它通过以下方式影响 2 和 5 的结果:

    给定:

    • "${array[*]}" 扩展到由IFS 的第一个字符分隔的每个元素
    • sorted=() 通过拆分 IFS 的每个字符来创建元素

    IFS=$'\n' sets things up 以便使用 新行 作为分隔符扩展元素,然后以每行成为一个元素的方式创建。 (即在新行上拆分。)

    用新行分隔很重要,因为这就是sort 的操作方式(按行排序)。 分割一个新行并不重要,但需要保留包含空格或制表符的元素。

    IFS 的默认值是 一个空格一个制表符,后跟 一个新行,不适合我们的操作。

    接下来,sort &lt;&lt;&lt;"${array[*]}" 部分

    &lt;&lt;&lt;,称为here strings,采用上述"${array[*]}" 的扩展,并将其输入sort 的标准输入。

    在我们的示例中,sort 被输入以下字符串:

    a c
    b
    f
    3 5
    

    由于sort排序,它产生:

    3 5
    a c
    b
    f
    

    接下来,sorted=($(...)) 部分

    $(...) 部分,称为command substitution,导致其内容 (sort &lt;&lt;&lt;"${array[*]}) 作为正常命令运行,同时将生成的 标准输出 作为文字,随处可见 @ 987654358@是。

    在我们的示例中,这会产生类似于简单编写的内容:

    sorted=(3 5
    a c
    b
    f
    )
    

    sorted 然后变成一个数组,通过在每一新行上拆分这个文字来创建。

    最后,unset IFS

    这会将IFS 的值重置为默认值,这是一种很好的做法。

    这是为了确保我们不会在脚本后面依赖于 IFS 的任何内容造成麻烦。 (否则我们需要记住我们已经改变了一些东西——这对于复杂的脚本可能是不切实际的。)

    【讨论】:

    • @xx 或没有IFS,如果元素中有空格,它会将元素分成小块。尝试省略IFS=$'\n' 例如,看看!
    • 非常好。您能为普通的 bash 用户解释一下这个解决方案是如何工作的吗?
    • 现在,使用IFS,如果元素中只有一种特定类型的空白,它会将您的元素分成小块。好的;不完美:-)
    • unset IFS 有必要吗?我认为将IFS= 添加到命令的范围内仅对该命令的更改,之后自动返回到其先前的值。
    • @MarkH 这是必要的,因为sorted=() 不是命令,而是第二个变量赋值。
    【解决方案2】:

    原始回复:

    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 而不是常规的换行符 作为行分隔符。

    【讨论】:

    • 很好,还应该注意 readarray 从 bash 版本 4 开始可用。可以缩短一点:readarray -t sorted &lt; &lt;(printf '%s\n' "${array[@]}" | sort)
    • @Dimitre:我采纳了您的建议并修复了空白处理以适用于任何内容(在内部使用 nullchar 分隔符)。干杯
    • 是的,sort -z 是一个有用的改进,我想-z 选项是 GNU 排序扩展。
    • 如果你想处理嵌入的换行符,你可以滚动你自己的 readarray。例如:sorted=(); while read -d $'\0' elem; do sorted[${#sorted[@]}]=$elem; done &lt; &lt;(printf '%s\0' "${array[@]}" | sort -z)。这也适用于您使用 bash v3 而不是 bash v4,因为 readarray 在 bash v3 中不可用。
    • @user1527227 它是输入重定向 (&lt;) 结合 进程替换 &lt;(...)。或者直观地说:因为(printf "bla") 不是文件。
    【解决方案3】:

    这是一个纯 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 &lt; $pivot ]] 是正确的。它使用字典字符串比较。如果您的数组只包含整数并且您想按数字排序,请改用((i &lt; pivot))请不要编辑这个答案来改变它。它已经被编辑(并回滚)了几次。我这里给出的测试是正确的,并且与示例中给出的输出相对应:示例同时使用了字符串和数字,目的是按字典顺序对其进行排序。在这种情况下使用((i &lt; pivot))是错误的

    【讨论】:

    • 感谢令人印象深刻的 Bashing,它在输入元素和排序标准方面提供了极大的灵活性。如果使用sort 提供的排序选项进行基于行的排序就足够了,那么sort + read -a 解决方案将从大约20 个项目开始更快,并且随着您处理的元素越多,速度会越来越快和。例如,在我 2012 年末运行 OSX 10.11.1 和 Fusion Drive 的 iMac 上:100 元素数组:ca。 0.03s 秒。 (qsort()) 与 ca。 0.005 秒。 (sort + read -a); 1000 元素数组:大约0.375 秒。 (qsort()) 与 ca。 0.014 秒 (sort + read -a)。
    • 不错。我记得大学时代的快速排序,但也会研究冒泡排序。对于我的排序需要,我有第一个和第二个元素形成键,然后是一个数据元素(我稍后可能会扩展)。您的代码可以通过关键元素的数量 (parm1) 和数据元素的数量 (parm2) 来改进。对于 OP,参数是 1 和 0。对我来说,参数是 2 和 1。无论如何,你的答案最有希望。
    • 使用我发现的未转换字符串整数数据集if [ "$i" -lt "$pivot" ]; then 是必需的,否则解析的“2”Inline Link.
    【解决方案4】:

    如果不需要处理数组元素中的特殊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
    

    kshset -sASCII 顺序排序

    【讨论】:

    • 非常好的背景信息。我几乎会要求一个关于 ksh 如何使用 set -s 标志的演示......但话说回来,问题是关于 bash,所以这将是相当离题的
    • 这应该适用于大多数 KornShell 实现(例如 ksh88pdksh):set -A array x 'a a' d; set -s -- "${array[@]}"; set -A sorted "$@" 并且,当然,set 命令会重置当前的位置参数,如果有的话。
    • 你是贝壳知识的源泉。我敢肯定你一定有过照片的记忆或其他东西,因为这种细微的差异是人类物种的大多数其他成员所无法做到的 :),+1 以获得完整的信息包
    【解决方案5】:

    tl;dr

    对数组a_in进行排序并将结果存储在a_out中(元素不能嵌入换行符[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)
    

    优于antak's solution:

    • 您不必担心意外的通配(将数组元素意外解释为文件名模式),因此不需要额外的命令来禁用通配(set -fset +f 稍后恢复它)。

    • 您不必担心将IFS 重置为unset IFS[2]


    选读:解释和示例代码

    以上将 Bash 代码与外部实用程序 sort 结合在一起,以提供一种适用于任意行元素的解决方案>词汇或数字排序(可选按字段)

    • 性能:对于大约 20 个或更多元素,这将比纯 Bash 解决方案更快 -一旦超过 100 个元素,就会显着增加。
      (确切的阈值取决于您的具体输入、机器和平台。)

      • 它快速的原因是它避免了 Bash 循环
    • printf '%s\n' "${a_in[@]}" | sort 执行排序(在词汇上,默认情况下 - 请参阅sort's POSIX spec):

      • "${a_in[@]}" 安全地扩展为数组a_in 的元素作为单个参数,无论它们包含什么(包括空格)。

      • printf '%s\n' 然后按原样在其自己的行上打印每个参数 - 即每个数组元素。

    • 1234563 >read / readarray 必须在 current shell 中运行(不得在 subshel​​l 中运行),才能使输出变量 a_out 成为对当前 shell 可见(以便变量在脚本的其余部分中保持定义)。
    • sort的输出读入一个数组变量

      • Bash v4+:readarray -t a_outsort 输出的各个行读取到数组变量 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-quoted string)。
        -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
    

    如果您希望 numerical 按第一个字段排序,您可以使用 sort -k1,1n 而不是仅使用 sort,这会产生(非数字在数字之前排序,并且数字正确排序):

    *
    a c
    b
    f
    5
    10
    

    [1] 要处理带有嵌入换行符的元素,请使用以下变体(Bash v4+,带有 GNU sort):
    readarray -d '' -t a_out &lt; &lt;(printf '%s\0' "${a_in[@]}" | sort -z).
    @987654325 @ 有一个 Bash v3 解决方案。

    [2] 虽然在 Bash v3 变体中设置了 IFS,但更改仅限于命令
    相比之下,在 antak 的回答中,IFS=$'\n'  后面的内容是 assignment 而不是命令,在这种情况下,IFS 的更改是全局

    【讨论】:

      【解决方案6】:

      在从慕尼黑到法兰克福的 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 语言的范围内。

      【讨论】:

      • 冒泡排序?哇.. 奥巴马说“冒泡排序是错误的方法”-> youtube.com/watch?v=k4RRi_ntQc8
      • 好吧,看起来 O-guy 想要聪明一点,但他并没有感觉到这不是一个 50/50 的机会问题。作为 O-guy 的前任,让我们告诉他 B-guy,曾经做得更好(俄亥俄州雷诺兹堡,2000 年 10 月):“我认为,如果您知道自己的信仰,那么回答问题会容易得多. 我无法回答你的问题。”所以这个B-guy真的对布尔逻辑有所了解。 O-guy 没有。
      • 通过使 BSORT 成为一个带有 nameref 的本地数组到要排序的任何数组,可以使函数更容易移植。即local -n BSORT="$1" 在函数的开头。然后你可以运行bubble_sort myarraymyarray进行排序。
      【解决方案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 来写出以零结尾的数组元素。引用确保数组元素按原样传递,并且 shell printf 的细节导致它为每个剩余参数重用格式字符串的最后一部分。也就是说,它相当于:

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

      然后将以空结尾的元素列表传递给sort-z 选项使其读取以空字符结尾的元素,对它们进行排序并输出以空字符结尾的元素。如果您只需要获取唯一元素,则可以传递-u,因为它比uniq -z 更便携。 LC_ALL=C 确保独立于语言环境的稳定排序顺序——有时对脚本很有用。如果您希望 sort 尊重语言环境,请将其删除。

      &lt;() 构造获取要从生成的管道读取的描述符,&lt;while 循环的标准输入重定向到它。如果你需要访问管道内的标准输入,你可以使用另一个描述符——读者练习:)。

      现在,回到开头。 read 内置从重定向的标准输入读取输出。将IFS 设置为空会禁用分词,这在此处是不必要的——因此,read 会将输入的整个“行”读取到单个提供的变量。 -r 选项也会禁用此处不需要的转义处理。最后,-d '' 将行分隔符设置为 NUL——也就是说,告诉read 读取以零结尾的字符串。

      因此,对于每个连续的以零结尾的数组元素执行一次循环,值存储在e 中。该示例只是将项目放在另一个数组中,但您可能更喜欢直接处理它们:)。

      当然,这只是实现同一目标的众多方法之一。正如我所看到的,它比在 bash 中实现完整的排序算法更简单,并且在某些情况下它会更快。它处理所有特殊字符,包括换行符,并且应该适用于大多数常见系统。最重要的是,它可能会教你一些关于 bash 的新奇事物 :)。

      【讨论】:

      • 很好的解决方案和非常有用的解释,谢谢。一个扩展:如果不将 IFS 设置为空,则前导空格也将被消除 - 即使没有进行分词。
      • 不要引入局部变量 e 并设置空 IFS,而是使用 REPLY 变量。
      【解决方案8】:

      试试这个:

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

      输出将是:

      3 5 一种 b C F

      问题解决了。

      【讨论】:

      • 应该编辑这个以将输出放入一个新数组中以完全回答他的问题。
      【解决方案9】:

      如果你可以为数组中的每个元素计算一个唯一的整数,像这样:

      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”之前,所以你不能将它用于上面显示的单词。
      【解决方案10】:

      最小排序:

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

      【讨论】:

        【解决方案11】:
        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
        

        【讨论】:

          【解决方案12】:

          对于常见的空格和换行问题有一个解决方法:

          使用不在原始数组中的字符(如$'\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}"
          • 完成

          【讨论】:

            【解决方案13】:

            This question 看起来密切相关。顺便说一句,这是 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[@]}"
            

            【讨论】:

              【解决方案14】:

              我不相信您需要在 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)) 直到最后十几个元素。对于最终元素,使用选择排序。
              【解决方案15】:
              a=(e b 'c d')
              shuf -e "${a[@]}" | sort >/tmp/f
              mapfile -t g </tmp/f
              

              【讨论】:

                【解决方案16】:

                非常感谢在我之前回答的人。使用他们出色的输入、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 一起正常工作

                【讨论】:

                  【解决方案17】:
                  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 大师撕碎! :)

                  【讨论】:

                  • 你测试过代码吗?这是行不通的。 $ref 是运行mapfile 的子shell 的本地,它不会影响父shell。可能是,您在 zsh 或 fish 下测试了代码 - 问题是关于 bash。
                  【解决方案18】:

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

                  本着 bash / linux 的精神,我会为每个步骤提供最好的命令行工具。 sort 完成主要工作,但需要用换行符而不是空格分隔输入,所以上面非常简单的管道就可以了:

                  回显数组内容 --> 用换行符替换空格 --> 排序

                  $() 是回显结果

                  ($())是将“回显结果”放入数组中

                  注意:正如@sorontar 在comment 中提到的另一个问题:

                  sorted=($(...)) 部分使用“split and glob”运算符。您应该关闭 glob:set -f 或 set -o noglob 或 shopt -op noglob 或像 * 这样的数组元素将扩展为文件列表。

                  【讨论】:

                  • 本着 bash / linux 的精神:我猜你根本没有理解这种精神。您的代码已完全损坏(路径名扩展和分词)。这会更好(Bash≥4):mapfile -t sorted &lt; &lt;(printf '%s\n' "${array[@]}" | sort),否则sorted=(); while IFS= read -r line; do sorted+=( "$line" ); done &lt; &lt;(printf '%s\n' | sort)
                  • 您正在使用的反模式是:echo ${array[@]} | tr " " "\n":如果数组的字段包含空格和全局字符,这将中断。此外,它会生成一个子外壳并使用无用的外部命令。由于echo 很笨,如果您的数组以-e-E-n 开头,它将中断。改为使用:printf '%s\n' "${array[@]}"。另一个反模式是:($()) 是将“回显结果”放入数组中。当然不是!这是一个可怕的反模式,由于路径名扩展(通配符)和分词而中断。永远不要使用这种恐怖。
                  • 最佳答案是“可怕的反模式”。以及对别人对您自己回答的问题的回答投反对票的方式。
                  • 关于陷阱的精彩讨论!
                  猜你喜欢
                  • 2018-08-14
                  • 1970-01-01
                  • 1970-01-01
                  • 2017-03-04
                  • 2021-02-07
                  • 2014-02-23
                  • 2015-09-02
                  • 2010-11-11
                  • 2012-11-20
                  相关资源
                  最近更新 更多