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.
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.
konsolebox style :) function join { local IFS=$1; __="${*:2}"; } or function join { IFS=$1 eval '__="${*:2}"'; }. Then use __ after. Yes, I'm the one promoting use of __ as a result variable ;) (and a common iteration variable or temporary variable). If the concept gets to a popular Bash wiki site, they copied me :)$d in the format specifier of printf. You think you're safe since you “escaped” the % but there are other caveats: when the delimiter contains a backslash (e.g., \n) or when the delimiter starts with a hyphen (and maybe others I can't think of now). You can of course fix these (replace backslashes by double backslashes and use printf -- "$d%s"), but at some point you'll feel that you're fighting against the shell instead of working with it. That's why, in my answer below, I prepended the delimiter to the terms to be joined.$ foo=(a "b c" d)
$ bar=$(IFS=, ; echo "${foo[*]}")
$ echo "$bar"
a,b c,d
@ instead of *, as in $(IFS=, ; echo "${foo[@]}") ? I can see that the * already preserves the whitespace in the elements, again not sure how, since @ is usually required for this sake.*. In bash man page, search for "Special Parameters" and look for the explanation next to *: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
Maybe, e.g.,
SAVE_IFS="$IFS"
IFS=","
FOOJOIN="${FOO[*]}"
IFS="$SAVE_IFS"
echo "$FOOJOIN"
echo "-${IFS}-" (the curly braces separate the dashes from the variable name).- as part of a variable name, whether you use brackets or not.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.
echo ${FOO[@]} | tr ' ' ','/, }This simple single-character delimiter solution requires non-POSIX mode. In POSIX mode, the elements are still joined properly, but the IFS=, assignment becomes permanent.
IFS=, eval 'joined="${foo[*]}"'
A script executed with the #!bash header respected executes in non-POSIX mode by default, but to help make sure a script runs in non-POSIX mode, add set +o posix or shopt -uo posix at the beginning of the script.
For multi-character delimiters, I recommend using a printf solution with escaping and indexing techniques.
function join {
local __sep=${2-} __temp
printf -v __temp "${__sep//%/%%}%s" "${@:3}"
printf -v "$1" %s "${__temp:${#__sep}}"
}
join joined ', ' "${foo[@]}"
Or
function join {
printf -v __ "${1//%/%%}%s" "${@:2}"
__=${__:${#1}}
}
join ', ' "${foo[@]}"
joined=$__
This is based on Riccardo Galli's answer with my suggestion applied.
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.
printf '%s\n' "$((IFS="⁋"; printf '%s' "${arr[*]}") | sed "s,⁋,LONG DELIMITER,g"))". The ⁋ is used as placeholder for replacement and can be any single character that can't occur in the array value (hence the uncommon unicode glyph).echo in the subshell, without having to invoke printf thereprintfs, but I wouldn't switch the inner one to echo to avoid the ambiguities that come with using echo – but I could probably simplify to (IFS=,; printf -- '%s\n' "${arr[*]}")printf vs. echo.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}"
}
join_ret a local variable, and then echoing it at the end. This allows join() to be used in the usual shell scripting way, e.g. $(join ":" one two three), and doesn't require a global variable.$(...) trims trailing newlines; so if the last field of the array contains trailing newlines, these would be trimmed (see demo where they are not trimmed with my design)./usr/bin/printf.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 !
$FOO is just the first element of the array, though. Also, this breaks for array elements containing spaces.printf '%s\0' "${FOO[@]}" | paste -zsd "," By that it supports array elements which contain spaces and new lines.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.
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
RESULT=$(echo "${INPUT// /,") This also works with multi-char delimiters.x I get 1[*], which is not desired. To fix the second line needs to be wrapped with braces, like a=${arr[*]}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 $
$
Shorter version of top answer:
joinStrings() { local a=("${@:3}"); printf "%s" "$2${a[@]/#/$1}"; }
Usage:
joinStrings "$myDelimiter" "${myArray[@]}"
join_strings () { local d="$1"; echo -n "$2"; shift 2 && printf '%s' "${@/#/$d}"; }join_strings () { local d="$1"; echo -n "$2"; shift 2 && printf '$d%s' "${@}"; } This works with usage: join_strings 'delim' "${array[@]}" or unquoted: join_strings 'delim' ${array[@]}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
printf format specifier (eg. %s unintentionally in $sep will cause problems.sep can be sanitized with ${sep//\%/%%}. I like your solution better than ${bar#${sep}} or ${bar%${sep}} (alternative). This is nice if converted to a function that stores result to a generic variable like __, and not echo it.function join_by { printf -v __ "${1//\%/%%}%s" "${@:2}"; __=${__:${#1}}; }s=$(IFS=, eval 'echo "${FOO[*]}"')
@Q could escape the joined values from misinterpreting when they have a joiner in them: foo=("a ," "b ' ' c" "' 'd e" "f " ";" "ls -latr"); s=$(IFS=, eval 'echo "${foo[*]@Q}"'); echo "${s}" outputs 'a ,','b '\'' '\'' c',''\'' '\''d e','f ',';','ls -latr '$ set a 'b c' d
$ history -p "$@" | paste -sd,
a,b c,d
HISTSIZE=0?paste -sd, not about the use of history.HISTSIZE=0 -- try it out.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...
printf because printf is better than echo. Also, head -n-1 is not portable (does not work on macOS, for example).printf - I use it myself. I'm complaining about the use of ${ar[*]} that doesn't make any sense. I would also contend that echo, while not better, is often sufficient for simple output, especially if you don't want to bother with specifying the new line character every output.head -n-1, yes - it isn't portable to Mac. It is also isn't portable to SunOS or AIX or any of the non-modern UN*X that don't support GNU. Unlike those though, on Mac you can brew install coreutils to get a compatible ghead, or do any of the other things mentioned here: superuser.com/q/543950/10942Combine the best of all worlds so far with the following idea.
# join with separator
join_ws() {
local IFS=
local s="${*/#/$1}"
echo "${s#"$1$1$1"}"
}
This little masterpiece is
printf ...)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
join_ws , (with no arguments) wrongly outputs ,,. 2. join_ws , -e wrongly outputs nothing (that's because you're wrongly using echo instead of printf). I actually don't know why you advertised the use of echo instead of printf: echo is notoriously broken, and printf is a robust builtin.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
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)
ARGS="--ignore $(echo "${TO_IGNORE[@]}" | tr ' ' ',')". Operator $() is more powerful than backtics (allows nesting of $() and ""). Wrapping ${TO_IGNORE[@]} with double quotes should also help.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
join name conflicts with some crap on OS X.. i'd call it conjoined, or maybe jackie_joyner_kersee?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.
printf -v bar ... instead of having to run the printf loop in a subshell and capture the output.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.
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.
See my comment in Riccardo Galli's answer.
__ as a variable name? Makes the code really unreadable.5.0.16(1)-release, and when I try to invoke the function, I get no output.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}"
}
"${@:3}" with double quotes.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.
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[@]}"
}
local) at all.