How can I join elements of an array in Bash?

前端 未结 30 2131
爱一瞬间的悲伤
爱一瞬间的悲伤 2020-11-22 12:05

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.

相关标签:
30条回答
  • 2020-11-22 12:28
    $ set a 'b c' d
    
    $ history -p "$@" | paste -sd,
    a,b c,d
    
    0 讨论(0)
  • 2020-11-22 12:28

    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.

    0 讨论(0)
  • 2020-11-22 12:29

    Maybe, e.g.,

    SAVE_IFS="$IFS"
    IFS=","
    FOOJOIN="${FOO[*]}"
    IFS="$SAVE_IFS"
    
    echo "$FOOJOIN"
    
    0 讨论(0)
  • 2020-11-22 12:31

    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.

    0 讨论(0)
  • 2020-11-22 12:31

    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.

    0 讨论(0)
  • 2020-11-22 12:31

    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 $ 
    $
    
    0 讨论(0)
提交回复
热议问题