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

Dav*_*ver 382 arrays bash

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

FOO=( a b c )
Run Code Online (Sandbox Code Playgroud)

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

Nic*_*kin 505

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

function join_by { local IFS="$1"; shift; echo "$*"; }
Run Code Online (Sandbox Code Playgroud)

例如,

join_by , a "b c" d #a,b c,d
join_by / var local tmp #var/local/tmp
join_by , "${FOO[@]}" #a,b,c
Run Code Online (Sandbox Code Playgroud)

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

function join_by { local d=$1; shift; echo -n "$1"; shift; printf "%s" "${@/#/$d}"; }
Run Code Online (Sandbox Code Playgroud)

例如,

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
Run Code Online (Sandbox Code Playgroud)

  • 用于多字符分隔符:function join {perl -e'$ s = shift @ARGV; print join($ s,@ ARGV);' "$ @"; } join','abc#a,b,c (9认同)
  • 不要将扩展`$ d`放在`printf`的格式说明符中.你认为你是"安全的",因为你"逃脱"了`%`但是还有其他注意事项:当分隔符包含反斜杠(例如,`\n`)或者分隔符以连字符开头时(也许其他我可以'现在想起来).你当然可以解决这些问题(用双反斜杠替换反斜杠并使用'printf - "$ d%s"`),但在某些时候你会觉得你正在与shell作战而不是使用它.这就是为什么,在我下面的答案中,我将分隔符添加到要加入的术语中. (6认同)
  • 如果将输出存储到变量,这会促进产生子壳.使用`konsolebox`风格:)`函数join {local IFS = $ 1; __ = "$ {*:2}"; }`或`function join {IFS = $ 1 eval'__ ="$ {*:2}"'; }`.然后使用`__`.是的,我是推广使用`__`作为结果变量;)(以及常见的迭代变量或临时变量).如果这个概念进入了一个受欢迎的Bash wiki网站,他们会复制我:) (5认同)
  • @puchu不起作用的是多字符分隔符.说"空间不起作用"让人觉得加入空间不起作用.确实如此. (4认同)
  • @dpatru反正使纯扑? (3认同)
  • `join_by() { 本地 d="$1" a="$2"; 班次 2;printf %s%s "$a" "${@/#/$d}"; }` 对于任意长度的分隔符来说稍微简洁一些。(就像这里的所有其他人一样)这也适用于 zsh。 (3认同)

小智 199

又一个解决方案:

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

echo $bar
Run Code Online (Sandbox Code Playgroud)

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

#!/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
Run Code Online (Sandbox Code Playgroud)

  • 而不是祈祷`$ separator`不包含`%s`等,你可以使你的'printf`健壮:`printf"%s%s""$ separator""$ {foo [@]}"`. (14认同)
  • +1.怎么样'printf -v bar',%s""$ {foo [@]}"`.它是一个`fork` less(实际上是`clone`).它甚至分叉读取文件:`printf -v bar",%s"$(<infile)`. (7认同)
  • @musiphil错了.来自bash man:"格式在必要时被重用以消耗所有参数.使用两个格式占位符,如`printf"%s%s"`将在第一个实例中使用separator只有输出集,然后简单地连接其余的论点. (5认同)
  • @AndrDevEK:谢谢你抓住了这个错误.相反,我会建议像'printf"%s""$ {foo [@] /#/ $ separator}"`. (3认同)
  • @musiphil,谢谢.是! 然后printf变得多余,并且该行可以减少到'IFS =; 正则表达式= "$ {foo的[*] /#/ $隔板}"`.在这一点上,这基本上变成了gniourf_gniourf的答案,IMO从一开始就更清洁,也就是说,使用函数来限制IFS变化和临时变量的范围. (2认同)
  • `bar = $(printf"%s%s","$ {foo [@]}"| cut -c2-)` (2认同)

小智 129

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

  • 我喜欢这个解决方案,但它只适用于IFS是一个字符的情况 (21认同)
  • 我找到了上面我自己问题的答案.答案是IFS只被认可为`*`.在bash手册页中,搜索"特殊参数"并查找`*`旁边的解释: (10认同)
  • +1是最紧凑的解决方案,不需要循环,不需要外部命令,也不会对参数的字符集施加额外的限制. (8认同)
  • 知道为什么如果使用`@`而不是`*`,这不起作用,如`$(IFS =,; echo"$ {foo [@]}")`?我可以看到`*`已经保留了元素中的空白,再次不确定如何,因为为了这个缘故,通常需要`@`. (8认同)
  • 不需要外部双引号和冒号周围的双引号.只需要内部双引号:`bar = $(IFS = ,; echo"$ {foo [*]}") (3认同)
  • 在`'$ {foo [@]}"`vs`"$ {foo [*]}"`另见["错误代码SC2145"](https://github.com/koalaman/shellcheck/wiki/SC2145 )[Shellcheck](https://www.shellcheck.net/). (3认同)
  • IMO,最好的解决方案(对于单字符分隔符的情况),因为它在概念上是最简单的。 (3认同)
  • 如果需要多个字符的分隔符,可以通过管道传输到 sed:`... | sed 's/,/, /g'` (2认同)

mar*_*ton 65

也许,例如,

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

echo "$FOOJOIN"
Run Code Online (Sandbox Code Playgroud)

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

kon*_*box 27

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

IFS=, eval 'joined="${foo[*]}"'
Run Code Online (Sandbox Code Playgroud)

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


gni*_*urf 23

这是一个完成工作的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}"
}
Run Code Online (Sandbox Code Playgroud)

看:

$ 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


$
Run Code Online (Sandbox Code Playgroud)

这甚至保留了尾随换行符,并且不需要子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}"
}
Run Code Online (Sandbox Code Playgroud)

  • 您的最后一个解决方案非常好,但可以通过将“join_ret”设置为局部变量,然后在最后回显它来使之更清晰。这允许 join() 以通常的 shell 脚本方式使用,例如 `$(join ":" one 二三)`,并且不需要全局变量。 (2认同)

Ben*_* W. 13

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

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

导致

a,b,c
Run Code Online (Sandbox Code Playgroud)

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


Yan*_*ard 12

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

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

结果:

a,b,c

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


use*_*986 10

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
Run Code Online (Sandbox Code Playgroud)

  • 对于带有空格的字符串,这不能正常工作:`t=(a "b c" d); echo ${t[2]} (打印“b c”);echo ${"${t[*]}"// /,} (打印 a,b,c,d) (4认同)

Nil*_*ler 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
Run Code Online (Sandbox Code Playgroud)

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

  • 如果你不想使用中间变量,它可以做得更短:`echo ${FOO[@]} | tr ' ' ','` (11认同)
  • 我不理解反对票。与此处发布的其他解决方案相比,它是一种紧凑且易于阅读的解决方案,并且明显警告说,有空间时,该解决方案将不起作用。 (2认同)

小智 8

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

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

printf has'格式字符串经常被重用,以满足参数.' 在其手册页中,以便记录字符串的连接.然后诀窍是使用LIST长度来切割最后一个sperator,因为cut将仅保留LIST的长度作为字段计数.


eel*_*EEz 8

s=$(IFS=, eval 'echo "${FOO[*]}"')
Run Code Online (Sandbox Code Playgroud)

  • 你应该充实你的答案. (8认同)
  • 我希望我能够回答这个问题,因为它会打开一个安全漏洞,因为它会破坏元素中的空格. (3认同)

Cam*_*tin 6

最佳答案的简短版本:

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

用法:

joinStrings "$myDelimiter" "${myArray[@]}"
Run Code Online (Sandbox Code Playgroud)


Ric*_*lli 5

可以接受任意长度分隔符的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
Run Code Online (Sandbox Code Playgroud)

  • 任何`printf`格式说明符(例如$ sep中无意中的`%s`都会引起问题。 (2认同)
  • `sep` 可以用 `${sep//\%/%%}` 进行清理。我比“${bar#${sep}}”或“${bar%${sep}}”(替代方案)更喜欢你的解决方案。如果转换为将结果存储到通用变量(如“__”)而不是“echo”的函数,那就太好了。 (2认同)

小智 5

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

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

这种受孕之美是

  • (仍然)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 $ 
$
Run Code Online (Sandbox Code Playgroud)