Arrays 如何在Bash中连接数组的元素?

Arrays 如何在Bash中连接数组的元素?,arrays,bash,Arrays,Bash,如果我在Bash中有这样一个数组: FOO=( a b c ) 如何用逗号连接元素?例如,可能产生a、b、c,例如 SAVE_IFS="$IFS" IFS="," FOOJOIN="${FOO[*]}" IFS="$SAVE_IFS" echo "$FOOJOIN" 现在我正在使用: TO_IGNORE=( E201 # Whitespace after '(' E301 # Expected N blank lines, found M E303 # Too ma

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

FOO=( a b c )
如何用逗号连接元素?例如,可能产生
a、b、c

,例如

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

echo "$FOOJOIN"
现在我正在使用:

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 ' ' ','`"
这是可行的,但是(在一般情况下)如果数组元素中有一个空格,它会严重损坏


(对于感兴趣的人来说,这是一个包装器脚本)

这种方法会考虑值中的空格,但需要一个循环:

#!/bin/bash

FOO=( a b c )
BAR=""

for index in ${!FOO[*]}
do
    BAR="$BAR,${FOO[$index]}"
done
echo ${BAR:1}
另一个解决方案:

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

这也会处理结尾处的额外逗号。我不是bash专家。就我的2c,因为这是一个更基本、更容易理解的

方法,可以重复使用@dos't not matters'解决方案,但是使用了一个语句,避免了${:1}替换和中间变量的需要

$ foo=(a "b c" d)
$ bar=$(IFS=, ; echo "${foo[*]}")
$ echo "$bar"
a,b c,d
echo $(printf "%s," "${LIST[@]}" | cut -d "," -f 1-${#LIST[@]} )
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.

printf的手册页中有“格式字符串根据需要重复使用,以满足参数要求。”这样就可以记录字符串的连接。然后,技巧是使用列表长度切掉最后一个sperator,因为cut将只保留列表长度作为字段计数。

不使用外部命令:

$ set a 'b c' d

$ history -p "$@" | paste -sd,
a,b c,d
$ 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

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

如果要加入的元素不是数组,而是空格分隔的字符串,则可以执行以下操作:

s=$(IFS=, eval 'echo "${FOO[*]}"')
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"

在我的_脚本中,我需要执行“从表中选择*,其中名称位于('aa'、'bb'、'cc'、'dd')。然后上述命令将非常有用。

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


支持多字符分隔符的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
join_by , a "b c" d #a,b c,d
join_by / var local tmp #var/local/tmp
join_by , "${FOO[@]}" #a,b,c
比如说,

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
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的原始建议

有关在此之前提出的解决方案的详细说明,请参见。

我的尝试

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

下面是一个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}"
}
看:

这甚至保留了尾随的换行符,并且不需要子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}"
}

将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

也许我遗漏了一些明显的东西,因为我是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
。对我来说已经足够好了


编辑:这基本上是,但概括为一个函数。

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

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

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

top answer的较短版本:

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

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

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


tr”““\n”如果在循环中构建数组,以下是一种简单的方法:

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

将迄今为止世界上最好的东西与以下想法结合起来

# 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

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

# join with separator
join_ws() { local d=$1 s=$2; shift 2 && printf %s "$s${@/#/$d}"; }
这种观念上的美是

  • (仍然)100%纯bash(感谢您明确指出printf也是一个内置的。我以前没有意识到这一点…)
  • 使用多字符分隔符
  • 更紧凑、更完整,这一次仔细考虑并使用shell脚本中的随机子字符串进行长期应力测试,包括在分隔符和/或参数中使用shell特殊字符或控制字符或不使用字符,以及边缘情况、角点情况和其他诡辩,例如在所有。这并不能保证没有更多的bug,但要找到一个会有点困难。顺便说一句,即使是目前最热门的答案和相关的问题也会遇到类似-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 $ 
$

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

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

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

echo $(printf "%s," "${LIST[@]}" | cut -d "," -f 1-${#LIST[@]} )
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。正如所观察到的,变量间接寻址不仅适用于变量,还适用于其他参数
arr=(a b c)
printf '%s\n' "$(IFS=,; printf '%s' "${arr[*]}")"
a,b,c
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[@]}"
}
arr=(1 2 3 4 5)
x=${"${arr[*]}"// /,}
echo $x  # output: 1,2,3,4,5
function joinArray() {
  local delimiter="${1}"
  local output="${2}"
  for param in ${@:3}; do
    output="${output}${delimiter}${param}"
  done

  echo "${output}"
}
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
$ 
$ 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
$ 
ar=(abc "foo bar" 456)
delim=" | "
printf "%s\n$delim\n" "${ar[@]}" | head -n-1 | paste -sd ''
abc | foo bar | 456