If I have an array like this in Bash:
FOO=( a b c )
How do I join the elements with commas? For example, producing a,b,c
.
If I have an array like this in Bash:
FOO=( a b c )
How do I join the elements with commas? For example, producing a,b,c
.
Rewriting solution by Pascal Pilz as a function in 100% pure Bash (no external commands):
function join_by { local IFS=\"$1\"; shift; echo \"$*\"; }
For example,
join_by , a \"b c\" d #a,b c,d
join_by / var local tmp #var/local/tmp
join_by , \"${FOO[@]}\" #a,b,c
Alternatively, we can use printf to support multi-character delimiters, using the idea by @gniourf_gniourf
function join_by { local d=$1; shift; echo -n \"$1\"; shift; printf \"%s\" \"${@/#/$d}\"; }
For example,
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
Yet another solution:
#!/bin/bash
foo=(\'foo bar\' \'foo baz\' \'bar baz\')
bar=$(printf \",%s\" \"${foo[@]}\")
bar=${bar:1}
echo $bar
Edit: same but for multi-character variable length separator:
#!/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
$ foo=(a \"b c\" d)
$ bar=$(IFS=, ; echo \"${foo[*]}\")
$ echo \"$bar\"
a,b c,d
Maybe, e.g.,
SAVE_IFS=\"$IFS\"
IFS=\",\"
FOOJOIN=\"${FOO[*]}\"
IFS=\"$SAVE_IFS\"
echo \"$FOOJOIN\"
Surprisingly my solution is not yet given :) This is the simplest way for me. It doesn\'t need a function:
IFS=, eval \'joined=\"${foo[*]}\"\'
Note: This solution was observed to work well in non-POSIX mode. In POSIX mode, the elements are still joined properly, but IFS=,
becomes permanent.
Here\'s a 100% pure Bash function that does the job:
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}\"
}
Look:
$ 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
$
This preserves even the trailing newlines, and doesn\'t need a subshell to get the result of the function. If you don\'t like the printf -v
(why wouldn\'t you like it?) and passing a variable name, you can of course use a global variable for the returned string:
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}\"
}
I would echo the array as a string, then transform the spaces into line feeds, and then use paste
to join everything in one line like so:
tr \" \" \"\\n\" <<< \"$FOO\" | paste -sd , -
Results:
a,b,c
This seems to be the quickest and cleanest to me !
With re-use of @doesn\'t matters\' solution, but with a one statement by avoiding the ${:1} substition and need of an intermediary variable.
echo $(printf \"%s,\" \"${LIST[@]}\" | cut -d \",\" -f 1-${#LIST[@]} )
printf has \'The format string is reused as often as necessary to satisfy the arguments.\' in its man pages, so that the concatenations of the strings is documented. Then the trick is to use the LIST length to chop the last sperator, since cut will retain only the lenght of LIST as fields count.
Using no external commands:
$ 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
Warning, it assumes elements don\'t have whitespaces.
s=$(IFS=, eval \'echo \"${FOO[*]}\"\')
printf solution that accept separators of any length (based on @doesn\'t matters answer)
#/!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
$ set a \'b c\' d
$ history -p \"$@\" | paste -sd,
a,b c,d
Shorter version of top answer:
joinStrings() { local a=(\"${@:3}\"); printf \"%s\" \"$2${a[@]/#/$1}\"; }
Usage:
joinStrings \"$myDelimiter\" \"${myArray[@]}\"
Combine best of all worlds so far with following idea.
# join with separator
join_ws() { local IFS=; local s=\"${*/#/$1}\"; echo \"${s#\"$1$1$1\"}\"; }
This little masterpiece is
Examples:
$ 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
Right now I\'m using:
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 \' \' \',\'`\"
Which works, but (in the general case) will break horribly if array elements have a space in them.
(For those interested, this is a wrapper script around pep8.py)
My attempt.
$ array=(one two \"three four\" five)
$ echo \"${array[0]}$(printf \" SEP %s\" \"${array[@]:1}\")\"
one SEP two SEP three four SEP five
Use perl for multicharacter separators:
function join {
perl -e \'$s = shift @ARGV; print join($s, @ARGV);\' \"$@\";
}
join \', \' a b c # a, b, c
Or in one line:
perl -le \'print join(shift, @ARGV);\' \', \' 1 2 3
1, 2, 3
Thanks @gniourf_gniourf for detailed comments on my combination of best worlds so far. Sorry for posting code not thoroughly designed and tested. Here is a better try.
# join with separator
join_ws() { local d=$1 s=$2; shift 2 && printf %s \"$s${@/#/$d}\"; }
This beauty by conception is
Additional examples:
$ 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 $
$
Using variable indirection to refer directly to an array also works. Named references can also be used, but they only became available in 4.3.
The advantage of using this form of a function is that you can have the separator optional (defaults to the first character of default IFS
, which is a space; perhaps make it an empty string if you like), and it avoids expanding values twice (first when passed as parameters, and second as \"$@\"
inside the function).
This solution also doesn\'t require the user to call the function inside a command substitution - which summons a subshell, to get a joined version of a string assigned to another variable.
As for the disadvantages: you would have to be careful at passing a correct parameter name, and passing __r
would give you __r[@]
. The behavior of variable indirection to also expand other forms of parameters is also not explicitly documented.
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.
This works from 3.1 to 5.0-alpha. As observed, variable indirection doesn\'t only work with variables, but other parameters as well.
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.
Arrays and array elements are also parameters (entities that store value), and references to arrays are technically references to parameters as well. And much like the special parameter @
, array[@]
also makes a valid reference.
Altered or selective forms of expansion (like substring expansion) that deviate reference from the parameter itself no longer work.
In case the elements you want to join is not an array just a space separated string, you can do something like this:
foo=\"aa bb cc dd\"
bar=`for i in $foo; do printf \",\'%s\'\" $i; done`
bar=${bar:1}
echo $bar
\'aa\',\'bb\',\'cc\',\'dd\'
for example, my use case is that some strings are passed in my shell script and I need to use this to run on a SQL query:
./my_script \"aa bb cc dd\"
In my_script, I need to do \"SELECT * FROM table WHERE name IN (\'aa\',\'bb\',\'cc\',\'dd\'). Then above command will be useful.
This approach takes care of spaces within the values, but requires a loop:
#!/bin/bash
FOO=( a b c )
BAR=\"\"
for index in ${!FOO[*]}
do
BAR=\"$BAR,${FOO[$index]}\"
done
echo ${BAR:1}
If you build the array in a loop, here is a simple way:
arr=()
for x in $(some_cmd); do
arr+=($x,)
done
arr[-1]=${arr[-1]%,}
echo ${arr[*]}
This isn\'t all too different from existing solutions, but it avoids using a separate function, doesn\'t modify IFS
in the parent shell and is all in a single line:
arr=(a b c)
printf \'%s\\n\' \"$(IFS=,; echo \"${arr[*]}\")\"
resulting in
a,b,c
Here\'s one that I think is POSIX-compatible:
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[@]}\"
}
Perhaps I\'m missing something obvious, since I\'m a newb to the whole bash/zsh thing, but it looks to me like you don\'t need to use printf
at all. Nor does it get really ugly to do without.
join() {
separator=$1
arr=$*
arr=${arr:2} # throw away separator and following space
arr=${arr// /$separator}
}
At least, it has worked for me thus far without issue.
For instance, join \\| *.sh
, which, let\'s say I\'m in my ~
directory, outputs utilities.sh|play.sh|foobar.sh
. Good enough for me.
EDIT: This is basically Nil Geisweiller\'s answer, but generalized into a function.
liststr=\"\"
for item in list
do
liststr=$item,$liststr
done
LEN=`expr length $liststr`
LEN=`expr $LEN - 1`
liststr=${liststr:0:$LEN}
This takes care of the extra comma at the end also. I am no bash expert. Just my 2c, since this is more elementary and understandable
awk -v sep=. \'BEGIN{ORS=OFS=\"\";for(i=1;i<ARGC;i++){print ARGV[i],ARGC-i-1?sep:\"\"}}\' \"${arr[@]}\"
or
$ a=(1 \"a b\" 3)
$ b=$(IFS=, ; echo \"${a[*]}\")
$ echo $b
1,a b,3