How can I join elements of an array in Bash?

ArraysBash

Arrays Problem Overview


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.

Arrays Solutions


Solution 1 - Arrays

A 100% pure Bash function that supports multi-character delimiters is:

function join_by {
  local d=${1-} f=${2-}
  if shift 2; then
    printf %s "$f" "${@/#/$d}"
  fi
}

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
join_by '-n' '-e' '-E' '-n' #-e-n-E-n-n
join_by , #
join_by , a #a

The code above is based on the ideas by @gniourf_gniourf, @AdamKatz, @MattCowell, and @x-yuri. It works with options errexit (set -e) and nounset (set -u).

Alternatively, a simpler function that supports only a single character delimiter, would be:

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

This solution is based on Pascal Pilz's original suggestion.

A detailed explanation of the solutions previously proposed here can be found in "How to join() array elements in a bash script", an article by meleu at dev.to.

Solution 2 - Arrays

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

Solution 3 - Arrays

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

Solution 4 - Arrays

Maybe, e.g.,

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

echo "$FOOJOIN"

Solution 5 - Arrays

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.

Solution 6 - Arrays

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.

Solution 7 - Arrays

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=,; printf '%s' "${arr[*]}")"

resulting in

a,b,c

Limitation: the separator can't be longer than one character.


This could be simplified to just

(IFS=,; printf '%s' "${arr[*]}")

at which point it's basically the same as Pascal's answer, but using printf instead of echo, and printing the result to stdout instead of assigning it to a variable.

Solution 8 - Arrays

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}"
}

Solution 9 - Arrays

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 !

Solution 10 - Arrays

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.

Solution 11 - Arrays

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

Solution 12 - Arrays

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

This is the shortest way to do it.

Example,

# 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

Solution 13 - Arrays

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

Solution 14 - Arrays

Shorter version of top answer:

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

Usage:

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

Solution 15 - Arrays

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

  • (still) 100% pure bash ( thanks for explicitly pointing out that printf is a builtin as well. I wasn't aware about this before ... )
  • works with multi-character delimiters
  • more compact and more complete and this time carefully thought over and long-term stress-tested with random substrings from shell scripts amongst others, covering use of shell special characters or control characters or no characters in both separator and / or parameters, and edge cases, and corner cases and other quibbles like no arguments at all. That doesn't guarantee there is no more bug, but it will be a little harder challenge to find one. BTW, even the currently top voted answers and related suffer from such things like that -e bug ...

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 $ 
$

Solution 16 - Arrays

$ set a 'b c' d

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

Solution 17 - Arrays

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

  • 100% pure bash ( parameter expansion with IFS temporarily unset, no external calls, no printf ... )
  • compact, complete and flawless ( works with single- and multi-character limiters, works with limiters containing white space, line breaks and other shell special characters, works with empty delimiter )
  • efficient ( no subshell, no array copy )
  • simple and stupid and, to a certain degree, beautiful and instructive as well

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

Solution 18 - Arrays

Here's a single liner that is a bit weird but works well for multi-character delimiters and supports any value (including containing spaces or anything):

ar=(abc "foo bar" 456)
delim=" | "
printf "%s\n$delim\n" "${ar[@]}" | head -n-1 | paste -sd ''

This would show in the console as

abc | foo bar | 456

Note: Notice how some solutions use printf with ${ar[*]} and some with ${ar[@]}?

The ones with @ use the printf feature that supports multiple arguments by repeating the format template.

The ones with * should not be used. They do not actually need printfand rely on manipulating the field separator and bash's word expansion. These would work just as well with echo, cat, etc. - these solutions likely use printf because the author doesn't really understand what they are doing...

Solution 19 - Arrays

Many, if not most, of these solutions rely on arcane syntax, brain-busting regex tricks, or calls to external executables. I would like to propose a simple, bash-only solution that is very easy to understand, and only slightly sub-optimal, performance-wise.

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
}

Example:

$ a=( 1 "2 2" 3 )
$ join_by ", " "${a[@]}"
1, 2 2, 3
$ 

I'd like to point out that any solution that uses /usr/bin/[ or /usr/bin/printf is inherently slower than my solution, since I use 100% pure bash. As an example of its performance, Here's a demo where I create an array with 1,000,000 random integers, then join them all with a comma, and time it.

$ 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
$ 

Solution 20 - Arrays

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)

Solution 21 - Arrays

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.

Solution 22 - Arrays

My attempt.

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



Solution 23 - Arrays

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

Solution 24 - Arrays

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[*]}

Solution 25 - Arrays

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.

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.

Feel free to use a more comfortable name for the function.

This works from 3.1 to 5.0-alpha. As observed, variable indirection doesn't only work with variables but with 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.

Update

In the release version of Bash 5.0, variable indirection is already called indirect expansion and its behavior is already explicitly documented in the manual:

> If the first character of parameter is an exclamation point (!), and > parameter is not a nameref, it introduces a level of indirection. > Bash uses the value formed by expanding the rest of parameter as the > new parameter; this is then expanded and that value is used in the > rest of the expansion, rather than the expansion of the original > parameter. This is known as indirect expansion.

Taking note that in the documentation of ${parameter}, parameter is referred to as "a shell parameter as described (in) PARAMETERS or an array reference". And in the documentation of arrays, it is mentioned that "Any element of an array may be referenced using ${name[subscript]}". This makes __r[@] an array reference.

Join by arguments version

See my comment in Riccardo Galli's answer.

Solution 26 - Arrays

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}

Solution 27 - Arrays

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.

Solution 28 - Arrays

Here's one that most POSIX compatible shells support:

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[@]}"
}

Solution 29 - Arrays

Perhaps late for the party, but this works for me:

function joinArray() {
  local delimiter="${1}"
  local output="${2}"
  for param in ${@:3}; do
    output="${output}${delimiter}${param}"
  done

  echo "${output}"
}

Solution 30 - Arrays

I believe this is the shortest solution, as Benamin W. already mentioned:

(IFS=,; printf %s "${a[*]}")

Wanted to add that if you use zsh, you can drop the subshell:

IFS=, printf %s "${a[*]}"

Test:

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

Solution 31 - Arrays

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

Solution 32 - Arrays

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

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionDavid WoleverView Question on Stackoverflow
Solution 1 - ArraysNicholas SushkinView Answer on Stackoverflow
Solution 2 - Arraysdoesn't mattersView Answer on Stackoverflow
Solution 3 - ArraysPascal PilzView Answer on Stackoverflow
Solution 4 - Arraysmartin claytonView Answer on Stackoverflow
Solution 5 - ArrayskonsoleboxView Answer on Stackoverflow
Solution 6 - ArraysNil GeisweillerView Answer on Stackoverflow
Solution 7 - ArraysBenjamin W.View Answer on Stackoverflow
Solution 8 - Arraysgniourf_gniourfView Answer on Stackoverflow
Solution 9 - ArraysYanick GirouardView Answer on Stackoverflow
Solution 10 - ArraysValiseView Answer on Stackoverflow
Solution 11 - Arrayseel ghEEzView Answer on Stackoverflow
Solution 12 - Arraysuser31986View Answer on Stackoverflow
Solution 13 - ArraysRiccardo GalliView Answer on Stackoverflow
Solution 14 - ArraysCamilo MartinView Answer on Stackoverflow
Solution 15 - ArraysguestView Answer on Stackoverflow
Solution 16 - ArraysZomboView Answer on Stackoverflow
Solution 17 - ArraysguestView Answer on Stackoverflow
Solution 18 - ArraysGussView Answer on Stackoverflow
Solution 19 - ArraysMark PettitView Answer on Stackoverflow
Solution 20 - ArraysDavid WoleverView Answer on Stackoverflow
Solution 21 - ArraysDexin WangView Answer on Stackoverflow
Solution 22 - ArraysBen DavisView Answer on Stackoverflow
Solution 23 - ArraysDaniel PatruView Answer on Stackoverflow
Solution 24 - ArraysIan KellingView Answer on Stackoverflow
Solution 25 - ArrayskonsoleboxView Answer on Stackoverflow
Solution 26 - ArraysdengelView Answer on Stackoverflow
Solution 27 - ArraysJordanView Answer on Stackoverflow
Solution 28 - Arraysuser541686View Answer on Stackoverflow
Solution 29 - ArraysTacB0sSView Answer on Stackoverflow
Solution 30 - ArraysmattalxndrView Answer on Stackoverflow
Solution 31 - Arraysbyte_arrayView Answer on Stackoverflow
Solution 32 - ArraysMeowView Answer on Stackoverflow