Joindre des éléments d’un tableau?

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

FOO=( abc ) 

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

Solution de réécriture par Pascal Pilz en tant que fonction dans Bash 100% pure (pas de commandes externes):

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

Par exemple,

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

Alternativement, nous pouvons utiliser printf pour prendre en charge 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 , abc #a,b,c join_by ' , ' abc #a , b , c join_by ')|(' abc #a)|(b)|(c join_by ' %s ' abc #a %sb %sc join_by $'\n' abc #abc join_by - abc #abc join_by '\' abc #a\b\c 

Encore une autre solution:

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

Edit: idem pour le séparateur de longueur variable à plusieurs caractères:

 #!/bin/bash separator=")|(" # eg 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 "bc" d) $ bar=$(IFS=, ; echo "${foo[*]}") $ echo "$bar" a,bc,d 

Peut-être, par exemple,

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

Étonnamment ma solution n’est pas encore donnée 🙂 C’est le moyen le plus simple pour moi. Il n’a pas besoin de fonction:

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

Remarque: Cette solution a bien fonctionné en mode non-POSIX. En mode POSIX , les éléments sont toujours joints correctement, mais IFS=, devient permanent.

Voici une fonction Bash 100% pure 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 nouvelles lignes de fin, et n’a pas besoin d’un sous-shell pour obtenir le résultat de la fonction. Si vous n’aimez pas printf -v (pourquoi ne pas vous plaire?) Et passer 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}" } 

Avec la réutilisation de la solution @ important, mais avec un seul énoncé en évitant la sous-couche $ {: 1} et le besoin d’une variable intermédiaire.

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

printf a ‘La chaîne de format 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. L’astuce consiste alors à utiliser la longueur de la liste pour couper le dernier sperator, puisque la coupe ne conservera que la longueur de la liste au fur et à mesure que les champs comptent.

Je ferais écho au tableau sous forme de chaîne, puis transformerais les espaces en sauts de ligne, puis utiliser paste pour tout joindre dans 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!

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

solution printf qui accepte les séparateurs de n’importe quelle longueur (basée sur @ pas de 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 
 $ set a 'bc' d $ history -p "$@" | paste -sd, a,bc,d 

Combinez le meilleur de tous les mondes jusqu’ici avec l’idée suivante.

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

Ce petit chef-d’œuvre est

  • 100% pure bash (extension des parameters avec IFS temporairement désactivé, pas d’appels externes, pas d’impression …)
  • compact, complet et sans défaut (fonctionne avec des limiteurs à un ou plusieurs caractères, fonctionne avec des limiteurs contenant des espaces blancs, des sauts de ligne et d’autres caractères spéciaux du shell, fonctionne avec un délimiteur vide)
  • efficace (pas de sous-shell, pas de copie de tableau)
  • simple et stupide et, dans une certaine mesure, aussi beau et instructif

Exemples:

 $ join_ws , abc a,b,c $ join_ws '' abc abc $ join_ws $'\n' abc a b c $ join_ws ' \/ ' ABC A \/ B \/ C 

N’utilisant aucune commande externe:

 $ FOO=( abc ) # initialize the array $ BAR=${FOO[@]} # create a space delimited ssortingng 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 blancs.

Version plus courte de la première réponse:

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

Usage:

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

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 cela intéresse, il s’agit d’un script d’encapsulation autour de pep8.py )

Ma tentative

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

Utilisez perl pour les séparateurs de plusieurs caractères:

 function join { perl -e '$s = shift @ARGV; print join($s, @ARGV);' "$@"; } join ', ' abc # a, b, c 

Ou en une seule ligne:

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

L’utilisation de l’indirection de 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 que dans 4.3.

L’avantage d’utiliser cette forme de fonction est que vous pouvez avoir le séparateur optionnel (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 voulez), et cela évite d’étendre les valeurs deux fois ( d’abord lorsqu’il est passé en paramètre et ensuite en tant que "$@" dans la fonction).

Cette solution n’exige pas non plus que l’utilisateur appelle la fonction dans une substitution de commande – qui invoque 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 devez faire attention en passant un nom de paramètre correct, et le passage de __r vous donnerait __r[@] . Le comportement de l’indirection des variables pour développer d’autres formes de parameters n’est pas non plus 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 de variable ne fonctionne pas seulement avec les variables, mais également avec d’autres parameters.

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

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

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

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

 #!/bin/bash FOO=( abc ) BAR="" for index in ${!FOO[*]} do BAR="$BAR,${FOO[$index]}" done echo ${BAR:1} 

Si les éléments que vous voulez joindre ne sont pas un tableau simplement une chaîne séparée par des espaces, vous pouvez faire quelque chose comme ceci:

foo = “aa bb cc dd” bar = for i in $foo; do printf ",'%s'" $i; done for i in $foo; do printf ",'%s'" $i; done 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 exécuter une requête SQL:

./my_script “aa bb cc dd”

Dans my_script, je dois faire “SELECT * FROM table WHERE nom IN (‘aa’, ‘bb’, ‘cc’, ‘dd’). Ensuite, la commande ci-dessus sera utile.

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

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

Merci @gniourf_gniourf pour les commentaires détaillés sur ma combinaison des meilleurs mondes jusqu’à présent. Désolé pour l’affichage du code non conçu et testé de manière approfondie. Voici un meilleur essai.

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

Cette beauté par conception est

  • (encore) bash 100% pur (merci de souligner explicitement que printf est également une version intégrée. Je n’étais pas au courant avant…)
  • fonctionne avec des délimiteurs multi-caractères
  • plus compact et plus complet et cette fois soigneusement réfléchi et testé à long terme avec des sous-chaînes aléatoires à partir de scripts shell, entre autres, couvrant l’utilisation de caractères spéciaux de shell ou de caractères de contrôle ou aucun caractère séparateur et / ou parameters , et les cas en coin et autres querelles comme aucun argument. Cela ne garantit pas qu’il n’y a plus de bogue, mais il sera un peu plus difficile d’en trouver un. BTW, même les réponses actuellement les mieux votées et liées souffrent de choses telles que -e bug …

Exemples supplémentaires:

 $ join_ws '' abc 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 $ $ 

Peut-être que je manque quelque chose d’évident, puisque je suis un newb à la chose bash / zsh, mais il me semble que vous n’avez pas besoin d’utiliser printf . Ce n’est pas vraiment moche de s’en passer.

 join() { separator=$1 arr=$* arr=${arr:2} # throw away separator and following space arr=${arr// /$separator} } 

Au moins, cela a fonctionné pour moi jusqu’à présent sans problème.

Par exemple, join \| *.sh join \| *.sh , qui, disons que je suis dans mon répertoire ~ , utilities.sh|play.sh|foobar.sh . Suffisant pour moi.

EDIT: Ceci est essentiellement la réponse de Nil Geisweiller , mais généralisée dans une fonction.

 liststr="" for item in list do liststr=$item,$liststr done LEN=`expr length $liststr` LEN=`expr $LEN - 1` liststr=${liststr:0:$LEN} 

Cela prend soin de la virgule supplémentaire à la fin aussi. Je ne suis pas un expert bash. Juste mon 2c, puisque c’est plus élémentaire et compréhensible

 awk -v sep=. 'BEGIN{ORS=OFS="";for(i=1;i 

ou

 $ a=(1 "ab" 3) $ b=$(IFS=, ; echo "${a[*]}") $ echo $b 1,ab,3