【问题标题】:How can I join elements of an array in Bash?如何在 Bash 中加入数组的元素?
【发布时间】:2010-12-04 08:45:58
【问题描述】:

如果我在 Bash 中有这样的数组:

FOO=( a b c )

如何用逗号连接元素?例如,产生a,b,c

【问题讨论】:

    标签: arrays bash


    【解决方案1】:

    也许,例如,

    SAVE_IFS="$IFS"
    IFS=","
    FOOJOIN="${FOO[*]}"
    IFS="$SAVE_IFS"
    
    echo "$FOOJOIN"
    

    【讨论】:

    • 如果你这样做,它认为 IFS- 是变量。你必须这样做echo "-${IFS}-"(花括号将破折号与变量名分开)。
    • 也就是说,这似乎仍然有效......所以,就像 Bash 的大多数事情一样,我会假装我理解它并继续我的生活。
    • “-”不是变量名的有效字符,因此当您使用 $IFS- 时,shell 会做正确的事情,您不需要 ${IFS}- (bash, ksh , sh 和 zsh 在 linux 和 solaris 中也同意)。
    • @David 你的回声和丹尼斯的不同之处在于他使用了双引号。 IFS 的内容在“输入时”用作单词分隔符的声明 - 所以你总是会得到一个没有引号的空行。
    • @DennisWilliamson:bash 不会将 - 视为变量名的一部分,无论您是否使用方括号。
    【解决方案2】:

    我现在正在使用:

    TO_IGNORE=(
        E201 # Whitespace after '('
        E301 # Expected N blank lines, found M
        E303 # Too many blank lines (pep8 gets confused by comments)
    )
    ARGS="--ignore `echo ${TO_IGNORE[@]} | tr ' ' ','`"
    

    这可行,但(在一般情况下)如果数组元素中有空格,则会严重损坏。

    (对于那些感兴趣的人,这是一个围绕pep8.py 的包装脚本)

    【讨论】:

    • 你从哪里得到这些数组值?如果你是这样硬编码的,为什么不只是 foo="a,b,c".?
    • 在这种情况下,我实际上 am 对这些值进行了硬编码,但我想将它们放在一个数组中,以便我可以单独评论每个值。我已经更新了答案以向您展示我的意思。
    • 假设您实际使用的是 bash,这可能会更好:ARGS="--ignore $(echo "${TO_IGNORE[@]}" | tr ' ' ',')"。运算符$() 比反引号更强大(允许嵌套$()"")。用双引号包裹${TO_IGNORE[@]} 也应该有帮助。
    【解决方案3】:

    这种方法处理值中的空格,但需要循环:

    #!/bin/bash
    
    FOO=( a b c )
    BAR=""
    
    for index in ${!FOO[*]}
    do
        BAR="$BAR,${FOO[$index]}"
    done
    echo ${BAR:1}
    

    【讨论】:

      【解决方案4】:

      另一种解决方案:

      #!/bin/bash
      foo=('foo bar' 'foo baz' 'bar baz')
      bar=$(printf ",%s" "${foo[@]}")
      bar=${bar:1}
      
      echo $bar
      

      编辑:相同,但用于多字符可变长度分隔符:

      #!/bin/bash
      separator=")|(" # e.g. constructing regex, pray it does not contain %s
      foo=('foo bar' 'foo baz' 'bar baz')
      regex="$( printf "${separator}%s" "${foo[@]}" )"
      regex="${regex:${#separator}}" # remove leading separator
      echo "${regex}"
      # Prints: foo bar)|(foo baz)|(bar baz
      

      【讨论】:

        【解决方案5】:
        liststr=""
        for item in list
        do
            liststr=$item,$liststr
        done
        LEN=`expr length $liststr`
        LEN=`expr $LEN - 1`
        liststr=${liststr:0:$LEN}
        

        这也会处理末尾的额外逗号。我不是 bash 专家。只是我的 2c,因为这更基本且易于理解

        【讨论】:

          【解决方案6】:
          $ foo=(a "b c" d)
          $ bar=$(IFS=, ; echo "${foo[*]}")
          $ echo "$bar"
          a,b c,d
          

          【讨论】:

          • +1 用于最紧凑的解决方案,它不需要循环、不需要外部命令并且不对参数的字符集施加额外的限制。
          • 我喜欢这个解决方案,但它只有在 IFS 是一个字符时才有效
          • 知道为什么如果使用@ 而不是*,这不起作用,如$(IFS=, ; echo "${foo[@]}") ?我可以看到* 已经在元素中保留了空格,但又不确定如何,因为为此通常需要@
          • 我在上面找到了我自己的问题的答案。答案是 IFS 仅被 * 识别。在 bash 手册页中,搜索“特殊参数”并查找 * 旁边的说明:
          • "${foo[@]}""${foo[*]}" 上,另请参阅 "Error code SC2145"Shellcheck
          【解决方案7】:

          通过重复使用@doesn't matter' 的解决方案,但通过避免 ${:1} 替换和中间变量的需要使用一个语句。

          echo $(printf "%s," "${LIST[@]}" | cut -d "," -f 1-${#LIST[@]} )
          

          printf 有'格式字符串会根据需要重复使用以满足参数。'在其手册页中,以便记录字符串的连接。然后诀窍是使用 LIST 长度来切割最后一个分隔符,因为 cut 将仅保留 LIST 的长度作为字段计数。

          【讨论】:

            【解决方案8】:
            $ set a 'b c' d
            
            $ history -p "$@" | paste -sd,
            a,b c,d
            

            【讨论】:

            • 这应该在顶部。
            • 这不应该在顶部:如果HISTSIZE=0
            • @har-wradim,诀窍是关于paste -sd,,而不是关于历史的使用。
            • @Veda 不,是关于组合的使用,如果HISTSIZE=0就不行了——试试看。
            【解决方案9】:

            不使用外部命令:

            $ FOO=( a b c )     # initialize the array
            $ BAR=${FOO[@]}     # create a space delimited string from array
            $ BAZ=${BAR// /,}   # use parameter expansion to substitute spaces with comma
            $ echo $BAZ
            a,b,c
            

            警告,它假定元素没有空格。

            【讨论】:

            • 如果不想使用中间变量,可以做得更短:echo ${FOO[@]} | tr ' ' ','
            • 我不明白反对票。与此处发布的其他解决方案相比,它是一个更加紧凑和可读的解决方案,并且清楚地警告它在有空格时不起作用。
            【解决方案10】:
            s=$(IFS=, eval 'echo "${FOO[*]}"')
            

            【讨论】:

            • 你应该充实你的答案。
            • 最好的。谢谢!!
            • 我希望我可以否决这个答案,因为它会打开一个安全漏洞,并且会破坏元素中的空格。
            • @bxm 确实,它似乎保留了空格,并且不允许从回显参数上下文中转义。我认为添加 @Q 可以避免连接的值在它们有连接器时被误解:foo=("a ," "b ' ' c" "' 'd e" "f " ";" "ls -latr"); s=$(IFS=, eval 'echo "${foo[*]@Q}"'); echo "${s}" 输出 'a ,','b '\'' '\'' c',''\'' '\''d e','f ',';','ls -latr '
            • 除非必要,否则避免使用子shell的解决方案。
            【解决方案11】:

            如果你想加入的元素不是数组,只是一个空格分隔的字符串,你可以这样做:

            foo="aa bb cc dd"
            bar=`for i in $foo; do printf ",'%s'" $i; done`
            bar=${bar:1}
            echo $bar
                'aa','bb','cc','dd'
            

            例如,我的用例是在我的 shell 脚本中传递了一些字符串,我需要使用它来运行 SQL 查询:

            ./my_script "aa bb cc dd"
            

            在 my_script 中,我需要执行“SELECT * FROM table WHERE name IN ('aa','bb','cc','dd')。然后上面的命令会很有用。

            【讨论】:

            • 您可以使用printf -v bar ... 而不必在子shell 中运行 printf 循环并捕获输出。
            • 上面所有花哨的赞成解决方案都不起作用,但您的粗略解决方案对我有用(Y)
            【解决方案12】:

            接受任意长度分隔符的 printf 解决方案(基于@doesn't Matters 答案)

            #/!bin/bash
            foo=('foo bar' 'foo baz' 'bar baz')
            
            sep=',' # can be of any length
            bar=$(printf "${sep}%s" "${foo[@]}")
            bar=${bar:${#sep}}
            
            echo $bar
            

            【讨论】:

            • 这会产生带有前导逗号的输出。
            • 最后一个 bar=${bar:${#sep}} 删除分隔符。我只是复制并粘贴到 bash shell 中,它确实有效。你用的是什么外壳?
            • 任何printf 格式说明符(例如%s无意中在$sep中都会导致问题。
            • sep 可以使用${sep//\%/%%} 进行清理。我比${bar#${sep}}${bar%${sep}}(替代方案)更喜欢您的解决方案。如果转换为将结果存储到像__ 这样的通用变量而不是echo 它的函数,那就太好了。
            • function join_by { printf -v __ "${1//\%/%%}%s" "${@:2}"; __=${__:${#1}}; }
            【解决方案13】:

            支持多字符分隔符的 100% 纯 Bash 函数是:

            function join_by {
              local d=${1-} f=${2-}
              if shift 2; then
                printf %s "$f" "${@/#/$d}"
              fi
            }
            

            例如,

            join_by , a b c #a,b,c
            join_by ' , ' a b c #a , b , c
            join_by ')|(' a b c #a)|(b)|(c
            join_by ' %s ' a b c #a %s b %s c
            join_by $'\n' a b c #a<newline>b<newline>c
            join_by - a b c #a-b-c
            join_by '\' a b c #a\b\c
            join_by '-n' '-e' '-E' '-n' #-e-n-E-n-n
            join_by , #
            join_by , a #a
            

            上面的代码基于@gniourf_gniourf、@AdamKatz、@MattCowell 和@x-yuri 的想法。它适用于选项errexit (set -e) 和nounset (set -u)。

            或者,一个只支持单个字符分隔符的更简单的函数是:

            function join_by { local IFS="$1"; shift; echo "$*"; }
            

            例如,

            join_by , a "b c" d #a,b c,d
            join_by / var local tmp #var/local/tmp
            join_by , "${FOO[@]}" #a,b,c
            

            此解决方案基于 Pascal Pilz 的原始建议。

            之前在这里提出的解决方案的详细解释可以在"How to join() array elements in a bash script", an article by meleu at dev.to找到。

            【讨论】:

            • 将此用于多字符分隔符:function join { perl -e '$s = shift @ARGV;打印加入($s,@ARGV);' "$@"; } join ', ' a b c # a, b, c
            • @dpatru 无论如何都要制作纯粹的 bash?
            • @puchu 不起作用的是多字符分隔符。说“空间不起作用”听起来就像加入一个空间不起作用。确实如此。
            • 如果将输出存储到变量,这会促进生成子shell。使用konsolebox 风格:) function join { local IFS=$1; __="${*:2}"; }function join { IFS=$1 eval '__="${*:2}"'; }。然后在之后使用__。是的,我是提倡使用__ 作为结果变量的人;)(以及常见的迭代变量或临时变量)。如果这个概念出现在一个流行的 Bash wiki 网站上,他们就会抄袭我 :)
            • 不要将扩展$d 放在printf 的格式说明符中。你认为你是安全的,因为你“转义”了%,但还有其他警告:当分隔符包含反斜杠时(例如,\n)或当分隔符以连字符开头时(也许其他我不能现在想想)。您当然可以修复这些问题(用双反斜杠替换反斜杠并使用printf -- "$d%s"),但在某些时候,您会觉得您是在与外壳作斗争而不是使用它。这就是为什么在下面的回答中,我将分隔符添加到要加入的条款之前。
            【解决方案14】:
            awk -v sep=. 'BEGIN{ORS=OFS="";for(i=1;i<ARGC;i++){print ARGV[i],ARGC-i-1?sep:""}}' "${arr[@]}"
            

            $ a=(1 "a b" 3)
            $ b=$(IFS=, ; echo "${a[*]}")
            $ echo $b
            1,a b,3
            

            【讨论】:

              【解决方案15】:

              我的尝试。

              $ array=(one two "three four" five)
              $ echo "${array[0]}$(printf " SEP %s" "${array[@]:1}")"
              one SEP two SEP three four SEP five
              

              【讨论】:

                【解决方案16】:

                这是一个 100% 纯 Bash 函数来完成这项工作:

                join() {
                    # $1 is return variable name
                    # $2 is sep
                    # $3... are the elements to join
                    local retname=$1 sep=$2 ret=$3
                    shift 3 || shift $(($#))
                    printf -v "$retname" "%s" "$ret${@/#/$sep}"
                }
                

                看:

                $ a=( one two "three three" four five )
                $ join joineda " and " "${a[@]}"
                $ echo "$joineda"
                one and two and three three and four and five
                $ join joinedb randomsep "only one element"
                $ echo "$joinedb"
                only one element
                $ join joinedc randomsep
                $ echo "$joinedc"
                
                $ a=( $' stuff with\nnewlines\n' $'and trailing newlines\n\n' )
                $ join joineda $'a sep with\nnewlines\n' "${a[@]}"
                $ echo "$joineda"
                 stuff with
                newlines
                a sep with
                newlines
                and trailing newlines
                
                
                $
                

                这甚至保留了尾随的换行符,并且不需要子shell来获取函数的结果。如果你不喜欢printf -v(你为什么不喜欢它?)并传递一个变量名,你当然可以为返回的字符串使用一个全局变量:

                join() {
                    # $1 is sep
                    # $2... are the elements to join
                    # return is in global variable join_ret
                    local sep=$1 IFS=
                    join_ret=$2
                    shift 2 || shift $(($#))
                    join_ret+="${*/#/$sep}"
                }
                

                【讨论】:

                • 您的最后一个解决方案非常好,但可以通过将join_ret 设为局部变量,然后在最后回显它来变得更简洁。这允许 join() 以通常的 shell 脚本方式使用,例如$(join ":" one two three),并且不需要全局变量。
                • @JamesSneeringer 我故意使用这种设计以避免子shell。在 shell 脚本中,与许多其他语言不同,以这种方式使用全局变量不一定是坏事;特别是如果他们在这里帮助避免子shell。此外,$(...) 修剪尾随换行符;因此,如果数组的最后一个字段包含尾随换行符,这些将被修剪(请参阅我的设计未修剪它们的演示)。
                • 这适用于多字符分隔符,这让我很高兴^_^
                • 解决“你为什么不喜欢 printf -v?”:在 Bash 中,局部变量并不是真正的函数局部变量,所以你可以这样做。 (使用局部变量 x 调用函数 f1,该函数又调用修改 x 的函数 f2 - 在 f1 的范围内声明为局部变量)但这并不是局部变量应该如何工作的真正方式。如果局部变量确实是局部变量(或者假设是局部变量,例如在必须同时在 bash 和 ksh 上工作的脚本中),那么它会导致整个“通过将值存储在具有此名称的变量中来返回值”方案的问题。
                • 这不是 100% 纯 bash;你打电话给/usr/bin/printf
                【解决方案17】:

                使用 perl 作为多字符分隔符:

                function join {
                   perl -e '$s = shift @ARGV; print join($s, @ARGV);' "$@"; 
                }
                
                join ', ' a b c # a, b, c
                

                或者在一行中:

                perl -le 'print join(shift, @ARGV);' ', ' 1 2 3
                1, 2, 3
                

                【讨论】:

                • 对我有用,虽然 join 名称与 OS X 上的一些废话有冲突......我会称之为 conjoined,或者可能是 jackie_joyner_kersee
                【解决方案18】:

                也许我遗漏了一些明显的东西,因为我是整个 bash/zsh 的新手,但在我看来,您根本不需要使用 printf。没有它也不会变得很丑。

                join() {
                  separator=$1
                  arr=$*
                  arr=${arr:2} # throw away separator and following space
                  arr=${arr// /$separator}
                }
                

                至少,到目前为止,它对我来说没有问题。

                例如,join \| *.sh,假设我在我的~ 目录中,输出utilities.sh|play.sh|foobar.sh。对我来说已经足够了。

                编辑:这基本上是Nil Geisweiller's answer,但被概括为一个函数。

                【讨论】:

                • 我不是反对者,但在函数中操作全局变量似乎很古怪。
                【解决方案19】:

                令人惊讶的是我的解决方案还没有给出:) 这对我来说是最简单的方法。它不需要函数:

                IFS=, eval 'joined="${foo[*]}"'
                

                注意:观察到此解决方案在非 POSIX 模式下运行良好。在POSIX mode 中,元素仍然正确连接,但IFS=, 变为永久。

                【讨论】:

                • 不幸的是仅适用于单字符分隔符
                【解决方案20】:

                最佳答案的简短版本:

                joinStrings() { local a=("${@:3}"); printf "%s" "$2${a[@]/#/$1}"; }
                

                用法:

                joinStrings "$myDelimiter" "${myArray[@]}"
                

                【讨论】:

                • 更长的版本,但不需要将参数切片复制到数组变量:join_strings () { local d="$1"; echo -n "$2"; shift 2 &amp;&amp; printf '%s' "${@/#/$d}"; }
                • 又一个版本:join_strings () { local d="$1"; echo -n "$2"; shift 2 &amp;&amp; printf '$d%s' "${@}"; } 这适用于用法:join_strings 'delim' "${array[@]}" 或未引用:join_strings 'delim' ${array[@]}
                【解决方案21】:

                我会将数组作为字符串回显,然后将空格转换为换行符,然后使用paste 将所有内容加入一行,如下所示:

                tr " " "\n" &lt;&lt;&lt; "$FOO" | paste -sd , -

                结果:

                a,b,c

                这对我来说似乎是最快最干净的!

                【讨论】:

                • $FOO 只是数组的第一个元素。此外,这会破坏包含空格的数组元素。
                【解决方案22】:

                如果你在循环中构建数组,这里有一个简单的方法:

                arr=()
                for x in $(some_cmd); do
                   arr+=($x,)
                done
                arr[-1]=${arr[-1]%,}
                echo ${arr[*]}
                

                【讨论】:

                  【解决方案23】:

                  将迄今为止所有世界中最好的与以下想法结合起来。

                  # join with separator
                  join_ws()  { local IFS=; local s="${*/#/$1}"; echo "${s#"$1$1$1"}"; }
                  

                  这个小杰作是

                  • 100% 纯 bash(临时取消设置 IFS 的参数扩展,没有外部调用,没有 printf ...)
                  • 紧凑、完整且完美无缺(适用于单字符和多字符限制器,适用于包含空格、换行符和其他 shell 特殊字符的限制器,适用于空分隔符)
                  • 高效(无子shell,无数组复制)
                  • 简单而愚蠢,在某种程度上,也很漂亮和有启发性

                  例子:

                  $ join_ws , a b c
                  a,b,c
                  $ join_ws '' a b c
                  abc
                  $ join_ws $'\n' a b c
                  a
                  b
                  c
                  $ join_ws ' \/ ' A B C
                  A \/ B \/ C
                  

                  【讨论】:

                  • 不太好:至少有 2 个问题:1. join_ws ,(没有参数)错误地输出 ,,。 2. join_ws , -e 错误地什么也不输出(那是因为你错误地使用了echo 而不是printf)。我真的不知道你为什么宣传使用echo 而不是printfecho 是出了名的坏,而printf 是一个健壮的内置函数。
                  【解决方案24】:

                  感谢@gniourf_gniourf 详细介绍了我迄今为止的最佳世界组合。很抱歉发布没有经过彻底设计和测试的代码。这是一个更好的尝试。

                  # join with separator
                  join_ws() { local d=$1 s=$2; shift 2 && printf %s "$s${@/#/$d}"; }
                  

                  这种意念之美是

                  • (仍然)100% 纯 bash(感谢您明确指出 printf 也是内置的。我之前不知道这一点...)
                  • 适用于多字符分隔符
                  • 更紧凑、更完整,这次仔细考虑并使用来自 shell 脚本的随机子字符串进行了长期压力测试,包括使用 shell 特殊字符或控制字符或在分隔符和/或参数中不使用字符,和边缘情况,极端情况和其他小问题,就像根本没有争论一样。这并不能保证没有更多的错误,但要找到一个更难的挑战。顺便说一句,即使是目前投票率最高的答案和相关问题也会受到 -e 错误之类的影响......

                  其他示例:

                  $ join_ws '' a b c
                  abc
                  $ join_ws ':' {1,7}{A..C}
                  1A:1B:1C:7A:7B:7C
                  $ join_ws -e -e
                  -e
                  $ join_ws $'\033[F' $'\n\n\n'  1.  2.  3.  $'\n\n\n\n'
                  3.
                  2.
                  1.
                  $ join_ws $ 
                  $
                  

                  【讨论】:

                    【解决方案25】:

                    使用变量间接引用直接引用数组也可以。也可以使用命名引用,但它们仅在 4.3 中可用。

                    使用这种形式的函数的好处是你可以有可选的分隔符(默认为默认IFS的第一个字符,这是一个空格;如果你愿意,可以将它设为一个空字符串),并且它避免两次扩展值(第一次作为参数传递,第二次作为函数内部的"$@")。

                    此解决方案也不需要用户在命令替换中调用该函数 - 这会调用一个子 shell,以获取分配给另一个变量的字符串的连接版本。

                    function join_by_ref {
                        __=
                        local __r=$1[@] __s=${2-' '}
                        printf -v __ "${__s//\%/%%}%s" "${!__r}"
                        __=${__:${#__s}}
                    }
                    
                    array=(1 2 3 4)
                    
                    join_by_ref array
                    echo "$__" # Prints '1 2 3 4'.
                    
                    join_by_ref array '%s'
                    echo "$__" # Prints '1%s2%s3%s4'.
                    
                    join_by_ref 'invalid*' '%s' # Bash 4.4 shows "invalid*[@]: bad substitution".
                    echo "$__" # Prints nothing but newline.
                    

                    请随意为函数使用更舒适的名称。

                    这适用于 3.1 到 5.0-alpha。正如所观察到的,变量间接不仅适用于变量,还适用于其他参数。

                    参数是存储值的实体。它可以是一个名字,一个 数字,或下面列出的特殊字符之一 参数。变量是由名称表示的参数。

                    数组和数组元素也是参数(存储值的实体),对数组的引用在技术上也是对参数的引用。就像特殊参数@ 一样,array[@] 也可以作为有效参考。

                    偏离参数本身的引用的更改或选择性扩展形式(如子字符串扩展)不再起作用。

                    更新

                    在 Bash 5.0 的发布版本中,变量间接已被称为间接扩展,其行为已在手册中明确记录:

                    如果参数的第一个字符是感叹号(!),并且 参数不是nameref,它引入了一个间接级别。 Bash 使用扩展其余参数形成的值作为 新参数;然后扩展 this 并将该值用于 其余的扩展,而不是原来的扩展 范围。这称为间接扩展。

                    请注意,在${parameter} 的文档中,parameter 被称为“在 PARAMETERS 中描述的外壳参数或数组引用” .并且在数组的文档中,提到“数组的任何元素都可以使用${name[subscript]} 引用”。这使得__r[@] 成为数组引用。

                    通过参数加入版本

                    Riccardo Galli's answer 中查看我的comment

                    【讨论】:

                    • 使用__作为变量名有什么特别的原因吗?让代码真的不可读。
                    • @PesaThe 这只是一个偏好。我更喜欢使用通用名称作为返回变量。其他非通用名称属于特定功能,需要记忆。调用多个返回不同变量值的函数会使代码更难理解。使用通用名称将迫使脚本编写者将值从返回变量转移到适当的变量以避免冲突,并且它使代码最终更具可读性,因为它变得明确返回值的去向。不过,我对该规则几乎没有例外。
                    • 我无法让代码工作。我正在使用5.0.16(1)-release,当我尝试调用该函数时,我没有得到任何输出。
                    • @MarkPettit 该函数并不意味着产生输出。它将值存储到__。但是,前四个命令可以。在 5.1.0(1)-alpha 上测试。
                    【解决方案26】:

                    这与现有的解决方案并没有太大的不同,但它避免使用单独的函数,不会修改父 shell 中的 IFS 并且都在一行中:

                    arr=(a b c)
                    printf '%s\n' "$(IFS=,; printf '%s' "${arr[*]}")"
                    

                    导致

                    a,b,c
                    

                    限制:分隔符不能超过一个字符。


                    这可以简化为

                    (IFS=,; printf '%s' "${arr[*]}")
                    

                    此时它与Pascal's answer基本相同,但使用printf而不是echo,并将结果打印到stdout而不是将其分配给变量。

                    【讨论】:

                    • 我用它来连接一个长分隔符,像这样:printf '%s\n' "$((IFS="⁋"; printf '%s' "${arr[*]}") | sed "s,⁋,LONG DELIMITER,g"))" 用作替换的占位符,可以是数组值中不能出现的任何单个字符(因此是不常见的 unicode 字形)。
                    • 你可以在子shell中使用echo,而不必在那里调用printf
                    • @Treviño 我实际上不记得为什么我使用嵌套的printfs,但我不会将内部的一个切换到echo 以避免使用echo 带来的歧义– 但我可能可以简化为 (IFS=,; printf -- '%s\n' "${arr[*]}")
                    • 我实际上将保持原样 - 如果我进行更改,它将基本上成为 Pascal's answer 的副本,唯一的区别是 printfecho
                    • 或者我只是添加简化并注意相似之处。
                    【解决方案27】:

                    这是大多数 POSIX 兼容的 shell 支持的一个:

                    join_by() {
                        # Usage:  join_by "||" a b c d
                        local arg arr=() sep="$1"
                        shift
                        for arg in "$@"; do
                            if [ 0 -lt "${#arr[@]}" ]; then
                                arr+=("${sep}")
                            fi
                            arr+=("${arg}") || break
                        done
                        printf "%s" "${arr[@]}"
                    }
                    

                    【讨论】:

                    • 这是很好的 Bash 代码,但完全是 POSIX doesn’t have 数组(或 local)。
                    • @Anders:是的,我最近很难学到这一点 :( 不过我暂时不提了,因为大多数 POSIX 兼容的 shell 似乎都支持数组。
                    【解决方案28】:

                    x=${arr[*]// /,}

                    这是最短的方法。

                    例子,

                    # ZSH:
                    arr=(1 "2 3" 4 5)
                    x=${"${arr[*]}"// /,}
                    echo $x  # output: 1,2,3,4,5
                    
                    # ZSH/BASH:
                    arr=(1 "2 3" 4 5)
                    a=${arr[*]}
                    x=${a// /,}
                    echo $x  # output: 1,2,3,4,5
                    

                    【讨论】:

                    • 这不适用于带空格的字符串:`t=(a "b c" d); echo ${t[2]}(打印“b c”); echo ${"${t[*]}"// /,} (打印 a,b,c,d)
                    • 非数组应用程序(用于空格分隔的字符串):RESULT=$(echo "${INPUT// /,") 这也适用于多字符分隔符。
                    • 请注意,在 bash 版本中存在语法错误(至少在我在 Mac 上测试时)。当我打印x 时,我得到1[*],这是不希望的。修复第二行需要用大括号括起来,比如a=${arr[*]}
                    【解决方案29】:

                    也许派对迟到了,但这对我有用:

                    function joinArray() {
                      local delimiter="${1}"
                      local output="${2}"
                      for param in ${@:3}; do
                        output="${output}${delimiter}${param}"
                      done
                    
                      echo "${output}"
                    }
                    

                    【讨论】:

                      【解决方案30】:

                      这些解决方案中的许多(如果不是大多数)依赖于晦涩难懂的语法、令人费解的正则表达式技巧或对外部可执行文件的调用。我想提出一个简单的、仅限 bash 的解决方案,该解决方案非常容易理解,并且在性能方面只是略微次优。

                      join_by () {
                          # Argument #1 is the separator. It can be multi-character.
                          # Argument #2, 3, and so on, are the elements to be joined.
                          # Usage: join_by ", " "${array[@]}"
                          local SEPARATOR="$1"
                          shift
                      
                          local F=0
                          for x in "$@"
                          do
                              if [[ F -eq 1 ]]
                              then
                                  echo -n "$SEPARATOR"
                              else
                                  F=1
                              fi
                              echo -n "$x"
                          done
                          echo
                      }
                      

                      例子:

                      $ a=( 1 "2 2" 3 )
                      $ join_by ", " "${a[@]}"
                      1, 2 2, 3
                      $ 
                      

                      我想指出,任何使用 /usr/bin/[/usr/bin/printf 的解决方案本质上都比我的解决方案慢,因为我使用 100% 纯 bash。作为其性能的一个示例,这里有一个演示,我创建了一个包含 1,000,000 个随机整数的数组,然后用逗号将它们全部连接起来,并计时。

                      $ eval $(echo -n "a=("; x=0 ; while [[ x -lt 1000000 ]]; do echo -n " $RANDOM" ; x=$((x+1)); done; echo " )")
                      $ time join_by , ${a[@]} >/dev/null
                      real    0m8.590s
                      user    0m8.591s
                      sys     0m0.000s
                      $ 
                      

                      【讨论】:

                      • 另外一个好处是,这不仅适用于数组。它也适用于文件:
                      • 您的“固有更快”解决方案比 printf 方法慢 2.9 倍。
                      • @MarkPettit printf 是内置的 bash。见gnu.org/software/bash/manual/html_node/…
                      猜你喜欢
                      • 2015-08-06
                      • 2017-03-17
                      • 1970-01-01
                      • 2020-05-16
                      • 2012-11-18
                      • 2021-02-20
                      • 2014-05-06
                      • 2012-10-12
                      • 1970-01-01
                      相关资源
                      最近更新 更多