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
.
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
.
Réponses:
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
konsolebox
style :) 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é :)
$d
dans 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.
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
printf -v bar ",%s" "${foo[@]}"
. C'est un de fork
moins (en fait clone
). Il est même bifurquer la lecture d' un fichier: printf -v bar ",%s" $(<infile)
.
$separator
ne contient pas %s
ou tel, vous pouvez rendre votre printf
robuste: printf "%s%s" "$separator" "${foo[@]}"
.
printf "%s%s"
utiliserait le séparateur dans la première instance UNIQUEMENT ensemble de sortie, puis concaténerait simplement le reste des arguments.
printf "%s" "${foo[@]/#/$separator}"
.
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.
$ foo=(a "b c" d)
$ bar=$(IFS=, ; echo "${foo[*]}")
$ echo "$bar"
a,b c,d
bar=$( IFS=, ; echo "${foo[*]}" )
@
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.
*
. Dans la page de manuel de bash, recherchez «Paramètres spéciaux» et recherchez l'explication à côté de *
:
Peut-être, par exemple,
SAVE_IFS="$IFS"
IFS=","
FOOJOIN="${FOO[*]}"
IFS="$SAVE_IFS"
echo "$FOOJOIN"
echo "-${IFS}-"
(les accolades séparent les tirets du nom de la variable).
echo $IFS
fait la même chose.
É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.
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}"
}
join_ret
une 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.
$(...)
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).
Ce n'est pas trop différent des solutions existantes, mais cela évite d'utiliser une fonction distincte, ne modifie pas IFS
dans 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.
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.
echo ${FOO[@]} | tr ' ' ','
Je voudrais faire écho au tableau sous forme de chaîne, puis transformer les espaces en sauts de ligne, puis utiliser paste
pour 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!
$FOO
n'est que le premier élément du tableau. En outre, cela se rompt pour les éléments de tableau contenant des espaces.
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.
s=$(IFS=, eval 'echo "${FOO[*]}"')
@Q
pourrait é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 '
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
printf
spécificateur de format (par exemple, %s
involontairement dans $sep
entraînera des problèmes.
sep
peut ê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 echo
elle.
function join_by { printf -v __ "${1//\%/%%}%s" "${@:2}"; __=${__:${#1}}; }
$ set a 'b c' d
$ history -p "$@" | paste -sd,
a,b c,d
HISTSIZE=0
?
paste -sd,
ne concerne pas l'utilisation de l'histoire.
HISTSIZE=0
- essayez-le.
Version plus courte de la première réponse:
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' "${@}"; }
Cela fonctionne avec l'utilisation: join_strings 'delim' "${array[@]}"
ou sans guillemets:join_strings 'delim' ${array[@]}
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
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
join_ws ,
(sans argument) sort à tort ,,
. 2. join_ws , -e
ne produit rien à tort (c'est parce que vous utilisez à tort echo
au lieu de printf
) En fait, je ne sais pas pourquoi vous avez annoncé l'utilisation de echo
au lieu de printf
: echo
est notoirement cassé et printf
est un robuste intégré.
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 )
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.
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
join
nom entre en conflit avec des conneries OS X
.. je l'appellerais conjoined
, ou peut jackie_joyner_kersee
- être ?
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
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 $
$
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.
printf -v bar ...
au lieu d'avoir à exécuter la boucle printf dans un sous-shell et capturer la sortie.
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[@]}"
}
local
) du tout.
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.
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}
, parameter
est 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.
Voir mon commentaire dans la réponse de Riccardo Galli .
__
comme nom de variable? Rend le code vraiment illisible.
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[*]}
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
bash: ${"${arr[*]}"// /,}: bad substitution
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 \| *.sh
qui, 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.
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