如何在Bash中加入数组元素?

时间:2009-10-06 17:46:16

标签: arrays bash

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

FOO=( a b c )

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

32 个答案:

答案 0 :(得分:458)

Pascal Pilz将解决方案重写为100%纯Bash中的函数(无外部命令):

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

或者,我们可以使用printf来支持多字符分隔符,使用@gniourf_gniourf的想法

function join_by { local d=$1; shift; echo -n "$1"; shift; printf "%s" "${@/#/$d}"; }

例如,

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

答案 1 :(得分:193)

又一个解决方案:

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

答案 2 :(得分:115)

$ foo=(a "b c" d)
$ bar=$(IFS=, ; echo "${foo[*]}")
$ echo "$bar"
a,b c,d

答案 3 :(得分:65)

也许,例如,

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

echo "$FOOJOIN"

答案 4 :(得分:24)

令人惊讶的是我的解决方案尚未给出:)这对我来说是最简单的方法。它不需要一个功能:

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

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

答案 5 :(得分:21)

这是一个完成工作的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}"
}

答案 6 :(得分:12)

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

tr " " "\n" <<< "$FOO" | paste -sd , -

结果:

a,b,c

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

答案 7 :(得分:8)

不使用外部命令:

$ 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

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

答案 8 :(得分:8)

s=$(IFS=, eval 'echo "${FOO[*]}"')

答案 9 :(得分:8)

重复使用@并不重要'解决方案,但通过避免$ {:1}替换和需要中间变量的一个声明。

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

printf has'格式字符串会根据需要经常重用以满足参数。在其手册页中,以便记录字符串的连接。然后诀窍就是使用LIST长度来切断最后一个sperator,因为cut只会保留LIST的长度作为字段数。

答案 10 :(得分:6)

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

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

导致

a,b,c

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

答案 11 :(得分:4)

$ set a 'b c' d

$ history -p "$@" | paste -sd,
a,b c,d

答案 12 :(得分:4)

printf解决方案,接受任何长度的分隔符(基于@并不重要)

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

答案 13 :(得分:4)

将目前为止最好的世界与以下想法相结合。

# 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

答案 14 :(得分:4)

最佳答案的缩短版本:

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

用法:

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

答案 15 :(得分:2)

这些解决方案中的许多(如果不是大多数的话)依赖于奥秘的语法,破坏头脑的正则表达式技巧或对外部可执行文件的调用。我想提出一个简单的,仅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。作为其性能的一个示例,这是一个演示,在该演示中,我创建一个具有1000000个随机整数的数组,然后将它们全部用逗号连接,然后对其计时。

$ 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
$ 

答案 16 :(得分:2)

这是一个有点奇怪的单行,但它适用于多字符定界符并支持任何值(包括空格或其他任何内容):

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

这将在控制台中显示为

abc | foo bar | 456

注意:请注意,有些解决方案如何将printf${ar[*]}一起使用,而另一些解决方案如何与${ar[@]}一起使用?后面的(带有@)使用printf功能,该功能通过重复格式模板来支持多个参数,而前一个实际上不需要printf并依靠操纵字段分隔符和bash的单词扩展,并且可以与echocat和其他语言一起使用-这些解决方案可能使用printf,因为作者并不真正了解他们在做什么,并且您不应该使用它们。

答案 17 :(得分:1)

以下是大多数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[@]}"
}

答案 18 :(得分:1)

使用变量间接指向直接引用数组也可以。也可以使用已命名的引用,但仅在4.3中可用。

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

此解决方案还不需要用户在命令替换内调用该函数-召唤一个子外壳,以获得分配给另一个变量的字符串的连接版本。

关于缺点:您必须小心传递正确的参数名称,而传递__r会给您__r[@]。变量间接寻址也可以扩展其他形式的参数的行为也没有明确记录。

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[@]也会提供有效的引用。

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

答案 19 :(得分:1)

感谢@gniourf_gniourf对我迄今为止最好的世界组合的详细评论。很抱歉发布未完全设计和测试的代码。这是一个更好的尝试。

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

这种美观的概念是

  • (仍然)100%纯粹的bash(感谢您明确指出printf也是内置的。我之前没有意识到这一点......)
  • 适用于多字符分隔符
  • 更紧凑,更完整,这次仔细考虑并使用shell脚本中的随机子串进行长期压力测试,包括使用shell特殊字符或控制字符或者在分隔符和/或参数中都没有字符,和边缘情况,角落案例和其他几乎没有参数的狡辩。这并不能保证不存在更多错误,但找到错误将是一个更难的挑战。顺便说一句,即使是目前最高投票的答案和相关的东西也受到诸如-e bug ......
  • 之类的影响

其他例子:

$ 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 $ 
$

答案 20 :(得分:1)

现在我正在使用:

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)的包装脚本

答案 21 :(得分:1)

将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

答案 22 :(得分:1)

我的尝试。

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

答案 23 :(得分:1)

如果您要连接的元素不是数组,只是一个空格分隔的字符串,您可以这样做:

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')。然后上面的命令将很有用。

答案 24 :(得分:0)

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

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

答案 25 :(得分:0)

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

这是最短的方法。

示例

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

答案 26 :(得分:0)

也许聚会晚了,但这对我有用:

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

  echo "${output}"
}

答案 27 :(得分:0)

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

#!/bin/bash

FOO=( a b c )
BAR=""

for index in ${!FOO[*]}
do
    BAR="$BAR,${FOO[$index]}"
done
echo ${BAR:1}

答案 28 :(得分:0)

我相信这是最短的解决方案,正如 Benamin W. 已经提到的:

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

想补充一点,如果你使用 zsh,你可以删除子shell:

IFS=, printf %s "${a[*]}"

测试:

a=(1 'a b' 3)
IFS=, printf %s "${a[*]}"
1,a b,3

答案 29 :(得分:-1)

也许我错过了一些显而易见的事情,因为我对整个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,但是被推广到一个函数中。

答案 30 :(得分:-2)

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

答案 31 :(得分:-2)

liststr=""
for item in list
do
    liststr=$item,$liststr
done
LEN=`expr length $liststr`
LEN=`expr $LEN - 1`
liststr=${liststr:0:$LEN}

这也会在最后处理额外的逗号。我不是bash专家。只是我的2c,因为这是更基本和可理解的