Réponse compatible
Il existe de nombreuses façons de le faire dans frapper.
Cependant, il est important de noter d'abord qu'il bash
a de nombreuses fonctionnalités spéciales (soi-disant bashismes ) qui ne fonctionneront dans aucune autrecoquille.
En particulier, les tableaux , les tableaux associatifs et la substitution de modèles , qui sont utilisés dans les solutions de cet article ainsi que d'autres dans le fil, sont des bashismes et peuvent ne pas fonctionner sous d'autres shells que beaucoup de gens utilisent.
Par exemple: sur mon Debian GNU / Linux , il y a un shell standard appelétiret; Je connais beaucoup de gens qui aiment utiliser un autre shell appeléksh; et il y a aussi un outil spécial appeléoccupé avec son propre interprète shell (cendre).
Chaîne demandée
La chaîne à séparer dans la question ci-dessus est:
IN="bla@some.com;john@home.com"
J'utiliserai une version modifiée de cette chaîne pour m'assurer que ma solution est robuste aux chaînes contenant des espaces blancs, ce qui pourrait casser d'autres solutions:
IN="bla@some.com;john@home.com;Full Name <fulnam@other.org>"
Fractionner la chaîne en fonction du délimiteur dans frapper (version> = 4.2)
En pur bash
, on peut créer un tableau avec des éléments divisés par une valeur temporaire pour IFS (le séparateur de champ d'entrée ). L'IFS, entre autres, indique bash
quel (s) caractère (s) il doit traiter comme un délimiteur entre les éléments lors de la définition d'un tableau:
IN="bla@some.com;john@home.com;Full Name <fulnam@other.org>"
# save original IFS value so we can restore it later
oIFS="$IFS"
IFS=";"
declare -a fields=($IN)
IFS="$oIFS"
unset oIFS
Dans les versions plus récentes de bash
, le fait de préfixer une commande avec une définition IFS modifie l'IFS pour cette commande uniquement et la réinitialise à la valeur précédente immédiatement après. Cela signifie que nous pouvons faire ce qui précède en une seule ligne:
IFS=\; read -a fields <<<"$IN"
# after this command, the IFS resets back to its previous value (here, the default):
set | grep ^IFS=
# IFS=$' \t\n'
Nous pouvons voir que la chaîne IN
a été stockée dans un tableau nommé fields
, divisé sur les points-virgules:
set | grep ^fields=\\\|^IN=
# fields=([0]="bla@some.com" [1]="john@home.com" [2]="Full Name <fulnam@other.org>")
# IN='bla@some.com;john@home.com;Full Name <fulnam@other.org>'
(Nous pouvons également afficher le contenu de ces variables en utilisant declare -p
:)
declare -p IN fields
# declare -- IN="bla@some.com;john@home.com;Full Name <fulnam@other.org>"
# declare -a fields=([0]="bla@some.com" [1]="john@home.com" [2]="Full Name <fulnam@other.org>")
Notez que read
c'est le moyen le plus rapide de faire le fractionnement car il n'y a pas de fourches ou de ressources externes appelées.
Une fois le tableau défini, vous pouvez utiliser une simple boucle pour traiter chaque champ (ou, plutôt, chaque élément du tableau que vous avez maintenant défini):
# `"${fields[@]}"` expands to return every element of `fields` array as a separate argument
for x in "${fields[@]}" ;do
echo "> [$x]"
done
# > [bla@some.com]
# > [john@home.com]
# > [Full Name <fulnam@other.org>]
Ou vous pouvez supprimer chaque champ du tableau après le traitement à l'aide d'un approche de décalage , que j'aime:
while [ "$fields" ] ;do
echo "> [$fields]"
# slice the array
fields=("${fields[@]:1}")
done
# > [bla@some.com]
# > [john@home.com]
# > [Full Name <fulnam@other.org>]
Et si vous voulez juste une simple impression du tableau, vous n'avez même pas besoin de le parcourir:
printf "> [%s]\n" "${fields[@]}"
# > [bla@some.com]
# > [john@home.com]
# > [Full Name <fulnam@other.org>]
Mise à jour: récente frapper > = 4,4
Dans les versions plus récentes de bash
, vous pouvez également jouer avec la commande mapfile
:
mapfile -td \; fields < <(printf "%s\0" "$IN")
Cette syntaxe préserve les caractères spéciaux, les nouvelles lignes et les champs vides!
Si vous ne souhaitez pas inclure de champs vides, vous pouvez procéder comme suit:
mapfile -td \; fields <<<"$IN"
fields=("${fields[@]%$'\n'}") # drop '\n' added by '<<<'
Avec mapfile
, vous pouvez également sauter la déclaration d'un tableau et implicitement "boucler" sur les éléments délimités, en appelant une fonction sur chacun:
myPubliMail() {
printf "Seq: %6d: Sending mail to '%s'..." $1 "$2"
# mail -s "This is not a spam..." "$2" </path/to/body
printf "\e[3D, done.\n"
}
mapfile < <(printf "%s\0" "$IN") -td \; -c 1 -C myPubliMail
(Remarque: la \0
fin de la chaîne de format est inutile si vous ne vous souciez pas des champs vides à la fin de la chaîne ou s'ils ne sont pas présents.)
mapfile < <(echo -n "$IN") -td \; -c 1 -C myPubliMail
# Seq: 0: Sending mail to 'bla@some.com', done.
# Seq: 1: Sending mail to 'john@home.com', done.
# Seq: 2: Sending mail to 'Full Name <fulnam@other.org>', done.
Ou vous pouvez utiliser <<<
, et dans le corps de la fonction, inclure un traitement pour supprimer la nouvelle ligne qu'il ajoute:
myPubliMail() {
local seq=$1 dest="${2%$'\n'}"
printf "Seq: %6d: Sending mail to '%s'..." $seq "$dest"
# mail -s "This is not a spam..." "$dest" </path/to/body
printf "\e[3D, done.\n"
}
mapfile <<<"$IN" -td \; -c 1 -C myPubliMail
# Renders the same output:
# Seq: 0: Sending mail to 'bla@some.com', done.
# Seq: 1: Sending mail to 'john@home.com', done.
# Seq: 2: Sending mail to 'Full Name <fulnam@other.org>', done.
Fractionner la chaîne en fonction du délimiteur dans coquille
Si vous ne pouvez pas utiliser bash
, ou si vous voulez écrire quelque chose qui peut être utilisé dans de nombreux shells différents, vous ne pouvez souvent pas utiliser de bashismes - et cela inclut les tableaux que nous avons utilisés dans les solutions ci-dessus.
Cependant, nous n'avons pas besoin d'utiliser des tableaux pour faire une boucle sur les "éléments" d'une chaîne. Il existe une syntaxe utilisée dans de nombreux shells pour supprimer les sous-chaînes d'une chaîne de la première ou de la dernière occurrence d'un modèle. Notez que*
s'agit d'un caractère générique qui représente zéro ou plusieurs caractères:
(L'absence de cette approche dans toute solution publiée jusqu'à présent est la principale raison pour laquelle j'écris cette réponse;)
${var#*SubStr} # drops substring from start of string up to first occurrence of `SubStr`
${var##*SubStr} # drops substring from start of string up to last occurrence of `SubStr`
${var%SubStr*} # drops substring from last occurrence of `SubStr` to end of string
${var%%SubStr*} # drops substring from first occurrence of `SubStr` to end of string
Comme expliqué par Score_Under :
#
et %
supprimer la sous-chaîne correspondante la plus courte possible respectivement au début et à la fin de la chaîne, et
##
et %%
supprimez la sous-chaîne correspondante la plus longue possible.
En utilisant la syntaxe ci-dessus, nous pouvons créer une approche où nous extrayons des "éléments" de sous-chaîne de la chaîne en supprimant les sous-chaînes jusqu'au délimiteur ou après.
Le bloc de code ci-dessous fonctionne bien dans frapper(y compris Mac OS bash
),tiret, ksh, et occupéc'est cendre:
IN="bla@some.com;john@home.com;Full Name <fulnam@other.org>"
while [ "$IN" ] ;do
# extract the substring from start of string up to delimiter.
# this is the first "element" of the string.
iter=${IN%%;*}
echo "> [$iter]"
# if there's only one element left, set `IN` to an empty string.
# this causes us to exit this `while` loop.
# else, we delete the first "element" of the string from IN, and move onto the next.
[ "$IN" = "$iter" ] && \
IN='' || \
IN="${IN#*;}"
done
# > [bla@some.com]
# > [john@home.com]
# > [Full Name <fulnam@other.org>]
S'amuser!