web-dev-qa-db-fra.com

Comment puis-je joindre des éléments d'un tableau dans Bash?

Si j'ai un tableau comme celui-ci dans Bash:

FOO=( a b c )

Comment joindre les éléments avec des virgules? Par exemple, produire a,b,c.

335
David Wolever

Réécriture de la solution de Pascal Pilz en tant que fonction 100% pure Bash (pas de commande externe):

function join_by { local IFS="$1"; shift; echo "$*"; }

Par exemple,

join_by , a "b c" d #a,b c,d
join_by / var local tmp #var/local/tmp
join_by , "${FOO[@]}" #a,b,c

Alternativement, nous pouvons utiliser printf pour supporter les délimiteurs multi-caractères, en utilisant l’idée de @gniourf_gniourf

function join_by { local d=$1; shift; echo -n "$1"; shift; printf "%s" "${@/#/$d}"; }

Par exemple,

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
438
Nicholas Sushkin

Encore une autre solution:

#!/bin/bash
foo=('foo bar' 'foo baz' 'bar baz')
bar=$(printf ",%s" "${foo[@]}")
bar=${bar:1}

echo $bar

Edit: idem mais pour séparateur de longueur variable multi-caractères:

#!/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
192
doesn't matters
$ foo=(a "b c" d)
$ bar=$(IFS=, ; echo "${foo[*]}")
$ echo "$bar"
a,b c,d
112
Pascal Pilz

Peut-être, par exemple,

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

echo "$FOOJOIN"
64
martin clayton

Étonnamment, ma solution n’est pas encore donnée :) C’est le moyen le plus simple pour moi. Il n'a pas besoin d'une fonction:

IFS=, eval 'joined="${foo[*]}"'

Remarque: il a été observé que cette solution fonctionnait bien en mode non POSIX. En mode POSIX , les éléments sont toujours joints correctement, mais IFS=, devient permanent.

24
konsolebox

Voici une fonction 100% pure Bash qui fait le travail:

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

Regardez:

$ 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


$

Cela préserve même les retours à la ligne de fin et ne nécessite pas de sous-shell pour obtenir le résultat de la fonction. Si vous n'aimez pas le printf -v (pourquoi ne l'aimeriez-vous pas?) Et si vous passez un nom de variable, vous pouvez bien sûr utiliser une variable globale pour la chaîne renvoyée:

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

J'échangerais le tableau sous forme de chaîne, puis transformerais les espaces en sauts de ligne, puis utiliserais paste pour tout joindre en une ligne, comme ceci:

tr " " "\n" <<< "$FOO" | paste -sd , -

Résultats:

a,b,c

Cela semble être le plus rapide et le plus propre pour moi!

11
Yanick Girouard

Avec la réutilisation de la solution @, ça n’a pas d’importance, mais avec une instruction unique en évitant la sous-condition $ {: 1} et le besoin d’une variable intermédiaire.

echo $(printf "%s," "${LIST[@]}" | cut -d "," -f 1-${#LIST[@]} )

printf a 'La chaîne de formatage est réutilisée aussi souvent que nécessaire pour satisfaire les arguments.' dans ses pages de manuel, afin que les concaténations des chaînes soient documentées. Ensuite, l'astuce consiste à utiliser la longueur de la liste pour couper le dernier sperator, car couper ne conservera que la longueur de la liste, car les champs comptent.

8
Valise

En n'utilisant aucune commande externe:

$ 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

Attention, cela suppose que les éléments n'ont pas d'espaces.

7
Nil Geisweiller
s=$(IFS=, eval 'echo "${FOO[*]}"')
7
eel ghEEz
$ set a 'b c' d

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

solution printf qui accepte les séparateurs de toute longueur (basée sur la réponse @ Peu importe la réponse)

#/!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
4
Riccardo Galli

Ce n’est pas si différent des solutions existantes, mais cela évite d’utiliser une fonction distincte, ne modifie pas IFS dans le Shell parent et se trouve sur une seule ligne:

arr=(a b c)
printf '%s\n' "$(IFS=,; echo "${arr[*]}")"

résultant en

a,b,c

Limitation: le séparateur ne peut pas dépasser un caractère.

4
Benjamin W.

Version abrégée de la réponse principale:

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

Usage:

joinStrings "$myDelimiter" "${myArray[@]}"
4
Camilo Martin

Combinez le meilleur des mondes jusqu'à présent avec l'idée suivante.

# join with separator
join_ws()  { local IFS=; local s="${*/#/$1}"; echo "${s#"$1$1$1"}"; }

Ce petit chef d'oeuvre est

  • 100% pure bash (extension de paramètre avec IFS temporairement non défini, pas d'appels externes, pas d'impression ...)
  • compact, complet et sans faille (fonctionne avec des limiteurs à un ou plusieurs caractères, fonctionne avec des limiteurs contenant des espaces, des sauts de ligne et autres caractères spéciaux Shell, fonctionne avec un séparateur vide)
  • efficace (pas de sous-shell, pas de copie de tableau)
  • simple et stupide et, dans une certaine mesure, beau et instructif aussi

Exemples:

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

Ma tentative.

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

Merci @gniourf_gniourf pour ses commentaires détaillés sur ma combinaison de meilleurs mondes à ce jour. Désolé pour le code d'affichage pas entièrement conçu et testé. Voici un meilleur essai. 

# join with separator
join_ws() { local d=$1 s=$2; shift 2 && printf %s "$s${@/#/$d}"; }

Cette beauté de conception est

  • (toujours) 100% pur bash (merci d'avoir explicitement souligné que printf est également intégré. Je n'étais pas au courant de cela auparavant ...)
  • fonctionne avec des délimiteurs multi-caractères
  • plus compacte et plus complète et cette fois-ci soigneusement pensée et testée à long terme avec des sous-chaînes aléatoires de scripts Shell, couvrant l'utilisation de caractères spéciaux Shell ou de caractères de contrôle ou l'absence de caractères dans les séparateurs et/ou les paramètres, et les cas Edge , et les cas de coin et autres petites chicanes ne ressemblent à aucun argument. Cela ne garantit pas qu'il n'y a plus de bogue, mais ce sera un défi un peu plus difficile d'en trouver un. BTW, même les réponses actuellement les plus votées et les questions connexes souffrent de choses comme celle -le bogue ...

Exemples supplémentaires:

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

L'utilisation de l'indirection variable pour faire directement référence à un tableau fonctionne également. Les références nommées peuvent également être utilisées, mais elles ne sont devenues disponibles qu'en 4.3.

L'avantage d'utiliser cette forme de fonction est que vous pouvez avoir le séparateur facultatif (par défaut, le premier caractère de IFS par défaut, qui est un espace; peut-être en faire une chaîne vide si vous le souhaitez), et cela évite de développer deux fois les valeurs ( d'abord lorsqu'il est passé en tant que paramètres et ensuite en tant que "$@" dans la fonction).

Cette solution ne nécessite pas non plus que l'utilisateur appelle la fonction à l'intérieur d'une substitution de commande - qui appelle un sous-shell, pour obtenir une version jointe d'une chaîne affectée à une autre variable.

En ce qui concerne les inconvénients: vous devrez être prudent en transmettant un nom de paramètre correct, et transmettre __r vous donnerait __r[@]. Le comportement de l'indirection de variable pour développer également d'autres formes de paramètres n'est pas explicitement documenté.

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.

Cela fonctionne de 3.1 à 5.0-alpha. Comme observé, l'indirection variable ne fonctionne pas seulement avec des variables, mais également avec d'autres paramètres.

Un paramètre est une entité qui stocke des valeurs. Ce peut être un nom, un numéro, ou l’un des caractères spéciaux répertoriés ci-dessous sous Spécial Paramètres. Une variable est un paramètre désigné par un nom.

Les tableaux et les éléments de tableau sont également des paramètres (des entités qui stockent une valeur), et les références à des tableaux sont également des références techniques à des paramètres. Et, tout comme le paramètre spécial @, array[@] constitue également une référence valide.

Les formes d'expansion modifiées ou sélectives (telles que l'expansion de sous-chaîne) qui dévient de la référence du paramètre lui-même ne fonctionnent plus.

1
konsolebox

En ce moment j'utilise:

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 ' ' ','`"

Ce qui fonctionne, mais (dans le cas général) se brisera horriblement si les éléments du tableau ont un espace.

(Pour ceux que ça intéresse, c'est un script wrapper autour de pep8.py )

1
David Wolever

En voici un que la plupart des shells compatibles POSIX supportent:

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
Mehrdad

Dans le cas où les éléments que vous souhaitez rejoindre ne sont pas un tableau, mais simplement une chaîne séparée par des espaces, faites comme ceci:

foo="aa bb cc dd"
bar=`for i in $foo; do printf ",'%s'" $i; done`
bar=${bar:1}
echo $bar
    'aa','bb','cc','dd'

par exemple, mon cas d'utilisation est que certaines chaînes sont passées dans mon script Shell et que je dois l'utiliser pour s'exécuter sur une requête SQL:

./my_script "aa bb cc dd"

Dans my_script, je dois faire "SELECT * FROM table WHERE name IN (" aa "," bb "," cc "," dd "). La commande ci-dessus sera utile.

1
Dexin Wang

Utilisez Perl pour les séparateurs à caractères multiples:

function join {
   Perl -e '$s = shift @ARGV; print join($s, @ARGV);' "$@"; 
}

join ', ' a b c # a, b, c

Ou en une ligne:

Perl -le 'print join(shift, @ARGV);' ', ' 1 2 3
1, 2, 3
1
dpatru

Cette approche prend en charge les espaces dans les valeurs, mais nécessite une boucle:

#!/bin/bash

FOO=( a b c )
BAR=""

for index in ${!FOO[*]}
do
    BAR="$BAR,${FOO[$index]}"
done
echo ${BAR:1}
0
dengel

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

C'est le moyen le plus court de le faire.

Exemple,

arr=(1 2 3 4 5)
x=${"${arr[*]}"// /,}
echo $x  # output: 1,2,3,4,5
0
user31986

Si vous construisez le tableau dans une boucle, voici un moyen simple:

arr=()
for x in $(some_cmd); do
   arr+=($x,)
done
arr[-1]=${arr[-1]%,}
echo ${arr[*]}
0
Ian Kelling