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

How can I join elements of an array in Bash?

如果在bash中有这样的数组:

1
FOO=( a b c )

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


解决方案:作为一个重写的Pascal真菌功能100%纯巴什(NO的外部命令):

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

例如,

1
2
3
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来支持多字符分隔符,使用"red"gniourf _ gniourf

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

例如,

1
2
3
4
5
6
7
8
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 $'
'
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
2
3
4
5
6
#!/bin/bash
foo=('foo bar' 'foo baz' 'bar baz')
bar=$(printf",%s""${foo[@]}")
bar=${bar:1}

echo $bar

编辑:相同,但多字符可变长度分离器:

1
2
3
4
5
6
7
#!/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


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


也许,例如,

1
2
3
4
5
6
SAVE_IFS="$IFS"
IFS=","
FOOJOIN="${FOO[*]}"
IFS="$SAVE_IFS"

echo"$FOOJOIN"


然后我还没有解决方案是给定的:)这是我最简单的方式。它不需要一个函数:

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

注:本解决方案所观察到的工作中。非POSIX模式。在POSIX的元素的模式,仍然是不恰当的,但IFS=,变成永久性的。


这是一个100%的纯函数是如何工作的:bash

1
2
3
4
5
6
7
8
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}"
}

外观:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
$ 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
newlines
'
$'and trailing newlines

'
)
$ join joineda $'a sep with
newlines
'
"${a[@]}"
$ echo"$joineda"
 stuff with
newlines
a sep with
newlines
and trailing newlines


$

本preserves甚至newlines和拖尾,不需要一次把the result of the function。如果你不喜欢printf -v(你为什么不喜欢它?)A和传递变量的名字,你可以使用一个全局变量当然,返回的字符串:

1
2
3
4
5
6
7
8
9
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}"
}


一个回声作为一个字符串数组,然后到饲料线的空间变换,然后使用paste加入一行:很像

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

结果:

a,b,c

这似乎对我的quickest和附近的!


在重新使用"不重要的"解决方案,但一个由上一个语句}和{:1美元需要中介的替代变量。

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

printf格式字符串是安切洛蒂的"reused AS经常被作为必要的arguments."在它的手册页,所以,这是concatenations的字符串记录。诀窍是使用的话,到最后sperator列表长度印章,因为只想保留一切为长距离大学列表字段计数。


使用外部命令:NO

1
2
3
4
5
$ 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

警告,它assumes元素没有空白。


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


将迄今为止最好的世界与以下理念结合起来。

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

这个小杰作是

  • 100%纯bash(带ifs的参数扩展暂时取消设置、无外部调用、无printf…)
  • 紧凑、完整、完美(适用于单字符和多字符限制器,适用于包含空格、换行符和其他外壳特殊字符的限制器,适用于空分隔符)
  • 高效(无子shell,无数组复制)
  • 简单而愚蠢,在某种程度上,美丽而有教育意义。

实例:

1
2
3
4
5
6
7
8
9
10
11
$ join_ws , a b c
a,b,c
$ join_ws '' a b c
abc
$ join_ws $'
'
a b c
a
b
c
$ join_ws ' \/ ' A B C
A \/ B \/ C


解决方案是使用printf接受方的长度(基于"没有问题的答案)

1
2
3
4
5
6
7
8
#/!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


1
2
3
4
$ set a 'b c' d

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


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

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

导致

1
a,b,c

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


回答:最短的版本

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

用法:

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


在案件的元素你想加入是不只是一个空间分隔的字符串数组,你可以做这样的东西:

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

例如,我的用例是一些字符串是通过在我的壳,我需要使用这个脚本可以运行在SQL查询:

1
./my_script"aa bb cc dd"

在我的_脚本,我需要做的"SELECT * FROM WHERE名称表(AA、BB、CC、DD)。然后上面的命令会有用的。


现在我使用:

1
2
3
4
5
6
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 ' ' ','`"

它的作品,但在总想打破horribly案例)如果数组元素有一个空间。

(对于那些有兴趣,这是一个包装器脚本在pep8.py)


我的尝试。

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

多性状分离:使用Perl for

1
2
3
4
5
function join {
   perl -e '$s = shift @ARGV; print join($s, @ARGV);'"$@";
}

join ', ' a b c # a, b, c

或在一个线:

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


感谢@gniourf_ginourf对我迄今为止最好的组合的详细评论。抱歉,发布代码没有经过彻底的设计和测试。这是一个更好的尝试。

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

这种意境美是

  • (仍然)100%纯bash(感谢您明确指出printf也是内置的)。我以前不知道这件事…)
  • 使用多字符分隔符
  • 更紧凑、更完整,这一次仔细考虑并用shell脚本中的随机子字符串对其进行长期压力测试,包括使用shell特殊字符或控制字符,或在分隔符和/或参数中不使用字符,以及边缘情况,以及角落情况和其他类似于完全没有参数的诡辩。这不能保证没有更多的bug,但是要找到一个bug会有点困难。顺便说一句,即使是目前最受欢迎的答案和相关的问题也会受到这样的困扰-e bug…

其他示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$ 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' $'


'
 1.  2.  3.  $'



'

3.
2.
1.
$ join_ws $
$

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

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

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

至于缺点:您必须小心传递正确的参数名,传递__r会给您提供__r[@]。变量间接的行为,也扩展了其他形式的参数,也没有明确记录。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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-α。正如所观察到的,变量间接寻址不仅适用于变量,还适用于其他参数。

A parameter is an entity that stores values. It can be a name, a
number, or one of the special characters listed below under Special
Parameters. A variable is a parameter denoted by a name.

数组和数组元素也是参数(存储值的实体),对数组的引用在技术上也是对参数的引用。与专用参数@非常相似,array[@]也可以作为有效的参考。

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


以下是大多数与posix兼容的shell所支持的:

1
2
3
4
5
6
7
8
9
10
11
12
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[@]}"
}


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

这是最短的方法。

例子,

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


如果你建立在循环数组A,这里是一个简单的方式:

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


本方法需要空间和价值关怀,但需要A回路:

1
2
3
4
5
6
7
8
9
10
#!/bin/bash

FOO=( a b c )
BAR=""

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

也许我失踪的东西上,因为我是一个newb to the Whole bash / zsh的事,但它看起来像我一个你不需要使用printf在所有。不做不做得到真的很丑陋。

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

至少,它为我工作远没有这样的问题。

例如,join \| *.sh它吧,让我们说,我在我的~utilities.sh|play.sh|foobar.sh目录输出。对我来说很难。

编辑:这是基本的geisweiller尼罗河的一个答案,但通用的功能。


1
2
3
4
5
6
7
8
liststr=""
for item in list
do
    liststr=$item,$liststr
done
LEN=`expr length $liststr`
LEN=`expr $LEN - 1`
liststr=${liststr:0:$LEN}

这需要额外的关怀,所以最后的逗号。i在NO的bash的专家。只是因为我2C,这是什么,基本与更多


1
awk -v sep=. 'BEGIN{ORS=OFS="";for(i=1;i<ARGC;i++){print ARGV[i],ARGC-i-1?sep:""}}'"${arr[@]}"

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