How can I join elements of an array in Bash?
如果在bash中有这样的数组:
1 | FOO=( 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的元素的模式,仍然是不恰当的,但
这是一个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。如果你不喜欢
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}" } |
一个回声作为一个字符串数组,然后到饲料线的空间变换,然后使用
" <<<"$FOO" | paste -sd , -
结果:
这似乎对我的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中修改
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中可用。
使用这种形式的函数的好处是,您可以选择分隔符(默认为默认的
这个解决方案也不需要用户调用命令替换中的函数——命令替换调用子shell,以获取分配给另一个变量的字符串的联接版本。
至于缺点:您必须小心传递正确的参数名,传递
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.
数组和数组元素也是参数(存储值的实体),对数组的引用在技术上也是对参数的引用。与专用参数
改变或选择性的扩展形式(如子字符串扩展),偏离参数本身的引用不再有效。
以下是大多数与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[@]}" } |
这是最短的方法。
例子,
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的事,但它看起来像我一个你不需要使用
1 2 3 4 5 6 | join() { separator=$1 arr=$* arr=${arr:2} # throw away separator and following space arr=${arr// /$separator} } |
至少,它为我工作远没有这样的问题。
例如,
编辑:这是基本的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 |