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

FOO=( a b c )

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


当前回答

还有另一个解决方案:

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

其他回答

结合所有最好的世界到目前为止与以下的想法。

# 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

使用变量间接直接引用数组也可以。也可以使用命名引用,但它们在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。正如所观察到的,变量间接性不仅适用于变量,也适用于其他参数。

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

数组和数组元素也是参数(存储值的实体),对数组的引用在技术上也是对参数的引用。和特殊的参数@很像,数组[@]也有一个有效的引用。

偏离参数本身引用的修改或选择形式的展开(如子字符串展开)不再工作。

更新

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

如果参数的第一个字符是感叹号(!),并且 Parameter不是一个nameref,它引入了一个间接级别。 Bash使用通过展开参数的其余部分所形成的值作为 新的参数;然后展开该值,并在 其余的扩充,而不是原来的扩充 参数。这就是所谓的间接扩张。

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

通过参数加入

在Riccardo Galli的回答中可以看到我的评论。

这里有一个单行,有点奇怪,但适用于多字符分隔符,并支持任何值(包括包含空格或任何东西):

ar=(abc "foo bar" 456)
delim=" | "
printf "%s\n$delim\n" "${ar[@]}" | head -n-1 | paste -sd ''

这将在控制台显示为

abc | foo bar | 456

注意:注意一些解决方案是如何使用${ar[*]}和${ar[@]}的printf ?

带@的使用printf特性,通过重复格式模板来支持多个参数。

带*的不应该使用。它们实际上不需要打印,而是依赖于操作字段分隔符和bash的单词展开。这些方法同样适用于echo、cat等——这些解决方案可能使用printf,因为作者并不真正理解他们在做什么……

这些解决方案中的许多(如果不是大多数的话)依赖于晦涩难懂的语法、耗费脑力的正则表达式技巧或对外部可执行文件的调用。我想提出一个简单的、只使用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
$ 

还有另一个解决方案:

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