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

418

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.

David Wolever
la source

Réponses:

572

Solution de réécriture de Pascal Pilz en fonction en 100% pur Bash (pas de commandes externes):

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 prendre en charge les délimiteurs à plusieurs 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
Nicholas Sushkin
la source
9
Utilisez ceci pour les séparateurs multicaractères: function join {perl -e '$ s = shift @ARGV; print join ($ s, @ARGV); ' "$ @"; } rejoindre ',' abc # a, b, c
Daniel Patru
4
@dpatru de toute façon pour faire cette pure bash?
CMCDragonkai
4
@puchu Ce qui ne fonctionne pas, ce sont les séparateurs multi-caractères. Dire «l'espace ne fonctionne pas» donne l'impression que se joindre à un espace ne fonctionne pas. Cela fait.
Eric
6
Cela favorise la génération de sous-coquilles si vous stockez la sortie dans une variable. Utilisez le konsoleboxstyle :) function join { local IFS=$1; __="${*:2}"; }ou function join { IFS=$1 eval '__="${*:2}"'; }. Ensuite, utilisez __après. Oui, je suis celui qui promeut l'utilisation de __comme variable de résultat;) (et une variable d'itération commune ou variable temporaire). Si le concept arrive sur un site wiki Bash populaire, ils m'ont copié :)
konsolebox
6
Ne placez pas l'extension $ddans le spécificateur de format de printf. Vous pensez que vous êtes en sécurité depuis que vous vous êtes «échappé», %mais il y a d'autres mises en garde: lorsque le délimiteur contient une barre oblique inverse (par exemple, \n) ou lorsque le délimiteur commence par un trait d'union (et peut-être d'autres auxquels je ne peux pas penser maintenant). Vous pouvez bien sûr les corriger (remplacer les contre-obliques par des contre-obliques doubles et les utiliser printf -- "$d%s"), mais à un moment donné, vous sentirez que vous vous battez contre le shell au lieu de travailler avec. C'est pourquoi, dans ma réponse ci-dessous, j'ai ajouté le délimiteur aux conditions à joindre.
gniourf_gniourf
206

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 à 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
n'a pas d'importance
la source
7
+1. Qu'en est-il printf -v bar ",%s" "${foo[@]}". C'est un de forkmoins (en fait clone). Il est même bifurquer la lecture d' un fichier: printf -v bar ",%s" $(<infile).
TrueY
14
Au lieu de prier $separatorne contient pas %sou tel, vous pouvez rendre votre printfrobuste: printf "%s%s" "$separator" "${foo[@]}".
musiphil
5
@musiphil Wrong. De bash man: "Le format est réutilisé autant que nécessaire pour consommer tous les arguments. L'utilisation de deux espaces réservés de format comme dans printf "%s%s"utiliserait le séparateur dans la première instance UNIQUEMENT ensemble de sortie, puis concaténerait simplement le reste des arguments.
AnyDev
3
@AndrDevEK: Merci d'avoir rattrapé l'erreur. Au lieu de cela, je suggérerais quelque chose comme printf "%s" "${foo[@]/#/$separator}".
musiphil
2
@musiphil, merci. Oui! Puis printf devient redondant et cette ligne peut être réduite à IFS=; regex="${foo[*]/#/$separator}". À ce stade, cela devient essentiellement la réponse de gniourf_gniourf qui IMO est plus propre depuis le début, c'est-à-dire, en utilisant la fonction pour limiter la portée des changements IFS et des variables temporaires.
AnyDev
145
$ foo=(a "b c" d)
$ bar=$(IFS=, ; echo "${foo[*]}")
$ echo "$bar"
a,b c,d
Pascal Pilz
la source
3
Les guillemets externes et les guillemets doubles autour du deux-points ne sont pas nécessaires. Seuls les guillemets intérieurs sont nécessaires:bar=$( IFS=, ; echo "${foo[*]}" )
ceving
8
+1 pour la solution la plus compacte qui n'a pas besoin de boucles, qui n'a pas besoin de commandes externes et qui n'impose pas de restrictions supplémentaires sur le jeu de caractères des arguments.
ceving
22
j'aime la solution, mais cela ne fonctionne que si IFS est un personnage
Jayen
8
Une idée pourquoi cela ne fonctionne pas si vous utilisez @au lieu de *, comme dans $(IFS=, ; echo "${foo[@]}")? Je peux voir que le *conserve déjà l'espace blanc dans les éléments, encore une fois je ne sais pas comment, car il @est généralement nécessaire à cet effet.
haridsv
10
J'ai trouvé la réponse à ma propre question ci-dessus. La réponse est que IFS est uniquement reconnu pour *. Dans la page de manuel de bash, recherchez «Paramètres spéciaux» et recherchez l'explication à côté de *:
haridsv
66

Peut-être, par exemple,

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

echo "$FOOJOIN"
Martin Clayton
la source
3
Si vous faites cela, il pense que IFS- est la variable. Vous devez faire echo "-${IFS}-"(les accolades séparent les tirets du nom de la variable).
pause jusqu'à nouvel ordre.
1
Toujours le même résultat (je viens de mettre les tirets pour illustrer le point… echo $IFSfait la même chose.
David Wolever
41
Cela dit, cela semble toujours fonctionner… Donc, comme la plupart des choses avec Bash, je vais faire comme si je le comprenais et continuer ma vie.
David Wolever
2
Un "-" n'est pas un caractère valide pour un nom de variable, donc le shell fait la bonne chose lorsque vous utilisez $ IFS-, vous n'avez pas besoin de $ {IFS} - (bash, ksh, sh et zsh sous linux et solaris également d'accord).
Idelic
2
@ David la différence entre votre écho et celui de Dennis est qu'il a utilisé des guillemets doubles. Le contenu d'IFS est utilisé `` en entrée '' comme une déclaration de caractères de séparation de mots - vous obtiendrez donc toujours une ligne vide sans guillemets.
martin clayton
30

É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: Cette solution a été observée pour bien fonctionner en mode non-POSIX. En mode POSIX , les éléments sont toujours joints correctement, mais IFS=,deviennent permanents.

konsolebox
la source
ne fonctionne malheureusement que pour les délimiteurs à un seul caractère
maoizm
24

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 le printf -v(pourquoi ne l'aimeriez-vous pas?) Et en passant un nom de variable, vous pouvez bien sûr utiliser une variable globale pour la chaîne retourné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}"
}
gniourf_gniourf
la source
1
Votre dernière solution est très bonne, mais pourrait être rendue plus propre en faisant join_retune variable locale, puis en l'écho à la fin. Cela permet à join () d'être utilisé de la manière habituelle de script shell, par exemple $(join ":" one two three), et ne nécessite pas de variable globale.
James Sneeringer
1
@JamesSneeringer J'ai délibérément utilisé cette conception afin d'éviter les sous-coquilles. Dans les scripts shell, contrairement à de nombreux autres langages, les variables globales utilisées de cette façon ne sont pas nécessairement une mauvaise chose; surtout s'ils sont là pour éviter les sous-coquilles. De plus, les $(...)trims suivent les nouvelles lignes; donc si le dernier champ du tableau contient des sauts de ligne de fin, ceux-ci seraient coupés (voir la démo où ils ne sont pas coupés avec ma conception).
gniourf_gniourf
Cela fonctionne avec des séparateurs multi-caractères, ce qui me rend heureux ^ _ ^
spiffytech
Pour répondre au "pourquoi n'aimeriez-vous pas printf -v?": Dans Bash, les variables locales ne sont pas vraiment fonctionnelles locales, vous pouvez donc faire des choses comme ça. (Appelez la fonction f1 avec la variable locale x, qui à son tour appelle la fonction f2 qui modifie x - qui est déclarée locale dans la portée de f1) Mais ce n'est pas vraiment la façon dont les variables locales devraient fonctionner. Si les variables locales sont vraiment locales (ou supposées l'être, par exemple dans un script qui doit fonctionner à la fois sur bash et ksh), cela pose des problèmes avec cet ensemble "retourne une valeur en la stockant dans la variable avec ce nom".
tetsujin
15

Ce n'est pas trop différent des solutions existantes, mais cela évite d'utiliser une fonction distincte, ne modifie pas IFSdans le shell parent et est tout sur une seule ligne:

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

résultant en

a,b,c

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

Benjamin W.
la source
13

Sans 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.

Nil Geisweiller
la source
4
Si vous ne voulez pas utiliser une variable intermédiaire, cela peut être fait encore plus court:echo ${FOO[@]} | tr ' ' ','
jesjimher
2
Je ne comprends pas les votes négatifs. C'est une solution beaucoup plus compacte et lisible que d'autres publiées ici, et il est clairement averti que cela ne fonctionne pas lorsqu'il y a des espaces.
jesjimher
12

Je voudrais faire écho au tableau sous forme de chaîne, puis transformer les espaces en sauts de ligne, puis utiliser pastepour joindre tout en une seule ligne comme suit:

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

Résultats:

a,b,c

Cela me semble être le plus rapide et le plus propre!

Yanick Girouard
la source
$FOOn'est que le premier élément du tableau. En outre, cela se rompt pour les éléments de tableau contenant des espaces.
Benjamin
9

Avec la réutilisation de @, la solution n'a pas d'importance, mais avec une seule instruction en évitant la sous-station $ {: 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. Ensuite, l'astuce consiste à utiliser la longueur de la LISTE pour hacher le dernier spérateur, car la coupe ne conservera que la longueur de la LISTE lorsque les champs compteront.

Valise
la source
7
s=$(IFS=, eval 'echo "${FOO[*]}"')
anguille ghEEz
la source
8
Vous devriez étoffer votre réponse.
joce
Le meilleur. Merci!!
peter pan gz
4
Je voudrais pouvoir voter contre cette réponse car elle ouvre une faille de sécurité et parce qu'elle détruira les espaces dans les éléments.
anguille ghEEz
1
@bxm en effet, il semble conserver des espaces et ne permet pas de s'échapper du contexte des arguments d'écho. Je pensais que l'ajout @Qpourrait échapper aux valeurs jointes d'une interprétation erronée quand ils ont un menuisier en eux: foo=("a ," "b ' ' c" "' 'd e" "f " ";" "ls -latr"); s=$(IFS=, eval 'echo "${foo[*]@Q}"'); echo "${s}"sorties'a ,','b '\'' '\'' c',''\'' '\''d e','f ',';','ls -latr '
anguille ghEEz
1
Évitez les solutions qui utilisent des sous-coques, sauf si cela est nécessaire.
konsolebox
5

solution printf qui accepte des séparateurs de n'importe quelle longueur (basé sur @ n'a pas d'importance 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
Riccardo Galli
la source
Cela produit une sortie avec une virgule principale.
Mark Renouf
La dernière barre = $ {bar: $ {# sep}} supprime le séparateur. Je viens de copier et coller dans un shell bash et cela fonctionne. Quel shell utilisez-vous?
Riccardo Galli
2
Tout printf spécificateur de format (par exemple, %sinvolontairement dans $sepentraînera des problèmes.
Peter.O
seppeut être désinfecté avec ${sep//\%/%%}. J'aime mieux votre solution que ${bar#${sep}}ou ${bar%${sep}}(alternative). C'est bien s'il est converti en une fonction qui stocke le résultat dans une variable générique comme __, et non echoelle.
konsolebox
function join_by { printf -v __ "${1//\%/%%}%s" "${@:2}"; __=${__:${#1}}; }
konsolebox
4
$ set a 'b c' d

$ history -p "$@" | paste -sd,
a,b c,d
Steven Penny
la source
Cela devrait être au sommet.
Eric Walker
6
Cela ne devrait pas être au sommet: et si HISTSIZE=0?
har-wradim
@ har-wradim, l'astuce paste -sd,ne concerne pas l'utilisation de l'histoire.
Veda
@Veda Non, il s'agit de l'utilisation de la combinaison, et cela ne fonctionnerait pas si HISTSIZE=0- essayez-le.
har-wradim
4

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

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

Usage:

joinStrings "$myDelimiter" "${myArray[@]}"
Camilo Martin
la source
1
Une version plus longue, mais pas besoin de faire une copie d'une tranche des arguments d'une variable de tableau:join_strings () { local d="$1"; echo -n "$2"; shift 2 && printf '%s' "${@/#/$d}"; }
Rockallite
Pourtant, une autre version: join_strings () { local d="$1"; echo -n "$2"; shift 2 && printf '$d%s' "${@}"; } Cela fonctionne avec l'utilisation: join_strings 'delim' "${array[@]}"ou sans guillemets:join_strings 'delim' ${array[@]}
Cometsong
4

Combinez le meilleur de tous les 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'œuvre est

  • 100% pur bash (extension des paramètres avec IFS temporairement non défini, 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, 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
client
la source
1
Pas si gentil: au moins 2 problèmes: 1. join_ws ,(sans argument) sort à tort ,,. 2. join_ws , -ene produit rien à tort (c'est parce que vous utilisez à tort echoau lieu de printf) En fait, je ne sais pas pourquoi vous avez annoncé l'utilisation de echoau lieu de printf: echoest notoirement cassé et printfest un robuste intégré.
gniourf_gniourf
1

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 cassera horriblement si les éléments du tableau ont un espace en eux.

(Pour ceux qui sont intéressés, il s'agit d'un script wrapper autour de pep8.py )

David Wolever
la source
d'où obtenez-vous ces valeurs de tableau? si vous codez en dur comme ça, pourquoi ne pas simplement foo = "a, b, c".?
ghostdog74
Dans ce cas, je suis en train de coder en dur les valeurs, mais je veux les mettre dans un tableau afin de pouvoir les commenter individuellement. J'ai mis à jour la réponse pour vous montrer ce que je veux dire.
David Wolever
En supposant que vous utilisez réellement bash, cela pourrait fonctionner mieux: ARGS="--ignore $(echo "${TO_IGNORE[@]}" | tr ' ' ',')". L'opérateur $()est plus puissant que les backtics (permet l'imbrication de $()et ""). L'encapsulation ${TO_IGNORE[@]}avec des guillemets doubles devrait également aider.
kevinarpe
1

Ma tentative.

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

Utilisez perl pour les séparateurs multicaractères:

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

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

Ou en une seule ligne:

perl -le 'print join(shift, @ARGV);' ', ' 1 2 3
1, 2, 3
Daniel Patru
la source
fonctionne pour moi, bien que le joinnom entre en conflit avec des conneries OS X.. je l'appellerais conjoined, ou peut jackie_joyner_kersee- être ?
Alex Gray
1

Merci @gniourf_gniourf pour les commentaires détaillés sur ma combinaison des meilleurs mondes jusqu'à présent. Désolé pour le code de publication non conçu et testé à fond. 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

  • (toujours) 100% pure bash (merci d'avoir précisé explicitement que printf est également un module intégré. Je n'en étais pas conscient avant ...)
  • fonctionne avec des délimiteurs à plusieurs 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 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 dans le séparateur et / ou les paramètres, et les cas de bord , et les cas d'angle et autres problèmes comme aucun argument du tout. Cela ne garantit pas qu'il n'y a plus de bogue, mais ce 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 comme ce bogue -e ...

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 $ 
$
client
la source
1

Dans le cas où les éléments que vous souhaitez joindre ne sont pas un tableau juste 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`
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 je dois l'utiliser pour exécuter une requête SQL:

./my_script "aa bb cc dd"

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

Dexin Wang
la source
Vous pouvez utiliser printf -v bar ...au lieu d'avoir à exécuter la boucle printf dans un sous-shell et capturer la sortie.
codeforester
toutes les fantastiques solutions votées ci-dessus n'ont pas fonctionné mais votre solution brute l'a fait pour moi (Y)
ishandutta2007
1

En voici un que la plupart des shells compatibles POSIX prennent en charge:

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[@]}"
}
user541686
la source
C'est du bon code Bash, mais POSIX n'a pas de tableaux (ou local) du tout.
Anders Kaseorg
@Anders: Oui, j'ai appris cela très récemment :( Je vais le laisser pour l'instant, car la plupart des shells compatibles POSIX semblent prendre en charge les tableaux.
user541686
1

L'utilisation d'indirection variable pour se référer directement à 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 de l'utilisation de cette forme de fonction est que le séparateur peut être facultatif (par défaut, le premier caractère par défaut IFS, qui est un espace; peut-être en faire une chaîne vide si vous le souhaitez), et cela évite d'étendre les valeurs deux fois (d'abord lorsqu'il est passé en tant que paramètres, et en second lieu "$@"à l'intérieur de la fonction).

Cette solution n'exige pas non plus que l'utilisateur appelle la fonction à l'intérieur d'une substitution de commande - qui invoque un sous-shell, pour obtenir une version jointe d'une chaîne affectée à une autre 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.

N'hésitez pas à utiliser un nom plus confortable pour la fonction.

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

Un paramètre est une entité qui stocke des valeurs. Il peut s'agir d'un nom, d'un nombre 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 paramètres (entités qui stockent de la valeur), et les références aux tableaux sont également des références techniques aux paramètres. Et tout comme le paramètre spécial @, array[@]fait également une référence valide.

Les formes d'expansion modifié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.

Mise à jour

Dans la version finale de Bash 5.0, l' indirection variable est déjà appelée expansion indirecte et son comportement est déjà explicitement documenté dans le manuel:

Si le premier caractère du paramètre est un point d'exclamation (!) Et que le paramètre n'est pas un nom, il introduit un niveau d'indirection. Bash utilise la valeur formée en développant le reste du paramètre comme nouveau paramètre; celle-ci est ensuite développée et cette valeur est utilisée dans le reste de l'expansion, plutôt que dans l'expansion du paramètre d'origine. C'est ce qu'on appelle l'expansion indirecte.

Prenant note que dans la documentation de ${parameter}, parameterest appelé "un paramètre shell comme décrit (dans) PARAMETRES ou une référence de tableau ". Et dans la documentation des tableaux, il est mentionné que "Tout élément d'un tableau peut être référencé en utilisant ${name[subscript]}". Cela fait __r[@]une référence de tableau.

Join by arguments version

Voir mon commentaire dans la réponse de Riccardo Galli .

konsolebox
la source
2
Y a-t-il une raison spécifique à utiliser __comme nom de variable? Rend le code vraiment illisible.
PesaThe
@PesaThe C'est juste une préférence. Je préfère utiliser des noms génériques pour une variable de retour. D'autres noms non génériques s'attribuent à des fonctions spécifiques et nécessitent une mémorisation. L'appel de plusieurs fonctions qui renvoient des valeurs sur différentes variables peut rendre le code moins facile à suivre. L'utilisation d'un nom générique forcerait le scripteur à transférer la valeur de la variable de retour vers la variable appropriée pour éviter les conflits, et cela rend le code plus lisible car il devient explicite où vont les valeurs renvoyées. Je fais cependant quelques exceptions à cette règle.
konsolebox
0

Cette approche prend soin des 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}
dengel
la source
0

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[*]}
Ian Kelling
la source
0

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
user31986
la source
1
Cela ne fonctionne pas correctement pour la chaîne avec des espaces: `t = (a" b c "d); echo $ {t [2]} (affiche "b c"); echo $ {"$ {t [*]}" // /,} (imprime a, b, c, d)
kounoupis
7
bash: ${"${arr[*]}"// /,}: bad substitution
Cameron Hudson
0

Peut-être en retard pour la fête, mais cela fonctionne pour moi:

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

  echo "${output}"
}
TacB0sS
la source
-1

Peut-être que je manque quelque chose d'évident, car je suis un nouveau dans le bash / zsh, mais il me semble que vous n'avez pas besoin du tout d'utiliser printf. Il ne devient 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 \| *.shqui, disons que je suis dans mon ~répertoire, sort utilities.sh|play.sh|foobar.sh. Suffisant pour moi.

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

Jordan
la source
1
Je ne suis pas le downvoter, mais manipuler un global dans une fonction semble assez farfelu.
tripleee
-2
liststr=""
for item in list
do
    liststr=$item,$liststr
done
LEN=`expr length $liststr`
LEN=`expr $LEN - 1`
liststr=${liststr:0:$LEN}

Cela prend également en charge la virgule supplémentaire à la fin. Je ne suis pas un expert bash. Juste mon 2c, car c'est plus élémentaire et plus compréhensible

byte_array
la source
-2
awk -v sep=. 'BEGIN{ORS=OFS="";for(i=1;i<ARGC;i++){print ARGV[i],ARGC-i-1?sep:""}}' "${arr[@]}"

ou

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