J'ai cette chaîne stockée dans une variable:
IN="[email protected];[email protected]"
Maintenant, je voudrais diviser les chaînes par ;
délimiteur de sorte que j'ai:
ADDR1="[email protected]"
ADDR2="[email protected]"
Je n'ai pas nécessairement besoin des variables ADDR1
et ADDR2
. S'ils sont des éléments d'un tableau, c'est encore mieux.
Après les suggestions des réponses ci-dessous, je me suis retrouvé avec ce qui suit:
#!/usr/bin/env bash
IN="[email protected];[email protected]"
mails=$(echo $IN | tr ";" "\n")
for addr in $mails
do
echo "> [$addr]"
done
Sortie:
> [[email protected]]
> [[email protected]]
Il y avait une solution impliquant le réglage Internal_field_separator (IFS) à ;
. Je ne suis pas sûr de ce qui s'est passé avec cette réponse. Comment réinitialisez-vous IFS
par défaut?
RE: IFS
solution, j'ai essayé ceci et cela fonctionne, je garde l'ancien IFS
puis je le restaure:
IN="[email protected];[email protected]"
OIFS=$IFS
IFS=';'
mails2=$IN
for x in $mails2
do
echo "> [$x]"
done
IFS=$OIFS
BTW, quand j'ai essayé
mails2=($IN)
Je n’ai reçu que la première chaîne lors de l’impression en boucle, sans crochets $IN
cela fonctionne.
Vous pouvez définir la variable séparateur de champ interne (IFS), puis le laisser analyser dans un tableau. Lorsque cela se produit dans une commande, l'affectation à IFS
n'a lieu que dans l'environnement de cette commande unique (à read
). Il analyse ensuite l'entrée en fonction de la valeur de la variable IFS
dans un tableau, sur lequel on peut ensuite effectuer une itération.
IFS=';' read -ra ADDR <<< "$IN"
for i in "${ADDR[@]}"; do
# process "$i"
done
Il analysera une ligne d'éléments séparés par ;
, en les insérant dans un tableau. Stuff pour le traitement complet de $IN
, à chaque fois une ligne d'entrée séparée par ;
:
while IFS=';' read -ra ADDR; do
for i in "${ADDR[@]}"; do
# process "$i"
done
done <<< "$IN"
Tiré de tableau divisé par script de Bash Shell:
IN="[email protected];[email protected]"
arrIN=(${IN//;/ })
Explication:
Cette construction remplace toutes les occurrences de ';'
(l'initiale //
signifie un remplacement global) dans la chaîne IN
avec ' '
(un seul espace), puis interprète la chaîne délimitée par des espaces. comme un tableau (c'est ce que font les parenthèses qui l'entourent).
La syntaxe utilisée à l'intérieur des accolades pour remplacer chaque caractère ';'
par un caractère ' '
est appelée Expansion des paramètres .
Il y a quelques pièges communs:
Si cela ne vous dérange pas de les traiter immédiatement, j'aime bien faire ceci:
for i in $(echo $IN | tr ";" "\n")
do
# process
done
Vous pouvez utiliser ce type de boucle pour initialiser un tableau, mais il existe probablement un moyen plus simple de le faire. J'espère que cela aide, cependant.
Pour cette SO question, il y a déjà beaucoup de façons différentes de le faire dans bash . Mais bash a beaucoup de caractéristiques spéciales, appelées bashisme qui fonctionnent bien, mais cela ne fonctionnera pas dans les autres Shell .
En particulier, tableaux, tableau associatif, et substitution de modèle sont purs bashismes et ne fonctionneront peut-être pas sous un autre - coquilles.
Sur mon Debian GNU/Linux, il existe un standard Shell appelé tiret , mais je connais beaucoup de gens qui aiment utiliser ksh .
Enfin, dans de très petites situations, il existe un outil spécial appelé busybox avec son propre interpréteur Shell ( ash ).
L'échantillon de chaîne dans la question SO est:
IN="[email protected];[email protected]"
Comme cela pourrait être utile avec espaces blancs et comme espaces blancs pourrait modifier le résultat de la routine, je préfère utiliser cet exemple de chaîne:
IN="[email protected];[email protected];Full Name <[email protected]>"
Sous pure bash, nous pouvons utiliser tableaux et IFS:
var="[email protected];[email protected];Full Name <[email protected]>"
oIFS="$IFS" IFS=";" declare -a fields=($var) IFS="$oIFS" unset oIFS
IFS=\; read -a fields <<<"$IN"
L'utilisation de cette syntaxe sous bash récent ne change pas $IFS
pour la session en cours, mais uniquement pour la commande en cours:
set | grep ^IFS=
IFS=$' \t\n'
Maintenant, la chaîne var
est divisée et stockée dans un tableau (nommé fields
):
set | grep ^fields=\\\|^var=
fields=([0]="[email protected]" [1]="[email protected]" [2]="Full Name <[email protected]>")
var='[email protected];[email protected];Full Name <[email protected]>'
Nous pourrions demander du contenu variable avec declare -p
:
declare -p IN fields
declare -- IN="[email protected];[email protected];Full Name <[email protected]>"
declare -a fields=([0]="[email protected]" [1]="[email protected]" [2]="Full Name <[email protected]>")
read
est le moyen le plus rapide de scinder, car il n'y a pas de forks et aucune ressource externe n'est appelée.
À partir de là, vous pouvez utiliser la syntaxe que vous connaissez déjà pour traiter chaque champ:
for x in "${fields[@]}";do
echo "> [$x]"
done
> [[email protected]]
> [[email protected]]
> [Full Name <[email protected]>]
ou supprimer chaque champ après traitement (j'aime cette approche shifting):
while [ "$fields" ] ;do
echo "> [$fields]"
fields=("${fields[@]:1}")
done
> [[email protected]]
> [[email protected]]
> [Full Name <[email protected]>]
ou même pour une impression simple (syntaxe plus courte):
printf "> [%s]\n" "${fields[@]}"
> [[email protected]]
> [[email protected]]
> [Full Name <[email protected]>]
Vous pouvez jouer avec 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 vous souciez pas des champs vides, vous pouvez:
mapfile -td \; fields <<<"$IN"
fields=("${fields[@]%$'\n'}") # drop '\n' added by '<<<'
Mais vous pouvez utiliser les champs via la fonction:
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
(Nota: \0
à la fin de la chaîne de format sont inutilisables tant que vous ne vous souciez pas des champs vides à la fin de la chaîne)
mapfile < <(echo -n "$IN") -td \; -c 1 -C myPubliMail
Rendra quelque chose comme:
Seq: 0: Sending mail to '[email protected]', done.
Seq: 1: Sending mail to '[email protected]', done.
Seq: 2: Sending mail to 'Full Name <[email protected]>', done.
Ou Supprimer la nouvelle ligne ajoutée par <<<
syntaxe bash dans la fonction:
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
Rendra la même sortie:
Seq: 0: Sending mail to '[email protected]', done.
Seq: 1: Sending mail to '[email protected]', done.
Seq: 2: Sending mail to 'Full Name <[email protected]>', done.
Mais si vous voulez écrire quelque chose d’utilisable sous plusieurs coques, vous devez ne pas utiliser bashismes.
Il existe une syntaxe, utilisée dans de nombreux shells, pour scinder une chaîne entre first ou last occurrence d'une sous-chaîne:
${var#*SubStr} # will drop begin of string up to first occur of `SubStr`
${var##*SubStr} # will drop begin of string up to last occur of `SubStr`
${var%SubStr*} # will drop part of string from last occur of `SubStr` to the end
${var%%SubStr*} # will drop part of string from first occur of `SubStr` to the end
(Le manque de ceci est la raison principale de la publication de ma réponse;)
Comme indiqué par Score_Under :
#
et%
suppriment la chaîne de correspondance la plus courte possible, et
##
et%%
suppriment le plus longtemps possible.où
#
et##
signifient à partir de la gauche (début) de la chaîne, et
%
et%%
meand à partir de la droite (fin) de la chaîne.
Ce petit exemple de script fonctionne bien sous bash , tiret , ksh , busybox et a été testé sous le bash de Mac-OS. aussi:
var="[email protected];[email protected];Full Name <[email protected]>"
while [ "$var" ] ;do
iter=${var%%;*}
echo "> [$iter]"
[ "$var" = "$iter" ] && \
var='' || \
var="${var#*;}"
done
> [[email protected]]
> [[email protected]]
> [Full Name <[email protected]>]
S'amuser!
J'ai vu quelques réponses référençant la commande cut
, mais elles ont toutes été supprimées. Il est un peu étrange que personne n’en ait parlé plus en détail, car j’estime que c’est l’une des commandes les plus utiles pour effectuer ce type de tâche, en particulier pour l’analyse de fichiers journaux délimités.
Dans le cas de la scission de cet exemple spécifique dans un tableau de script bash, tr
est probablement plus efficace, mais cut
peut être utilisé et est plus efficace si vous souhaitez extraire des champs spécifiques du milieu.
Exemple:
$ echo "[email protected];[email protected]" | cut -d ";" -f 1
[email protected]
$ echo "[email protected];[email protected]" | cut -d ";" -f 2
[email protected]
Vous pouvez évidemment mettre cela dans une boucle et itérer le paramètre -f pour extraire chaque champ indépendamment.
Cela devient plus utile lorsque vous avez un fichier journal délimité avec des lignes comme celle-ci:
2015-04-27|12345|some action|an attribute|meta data
cut
est très pratique pour pouvoir cat
ce fichier et sélectionner un champ particulier pour un traitement ultérieur.
Cela a fonctionné pour moi:
string="1;2"
echo $string | cut -d';' -f1 # output is 1
echo $string | cut -d';' -f2 # output is 2
Que diriez-vous de cette approche:
IN="[email protected];[email protected]"
set -- "$IN"
IFS=";"; declare -a Array=($*)
echo "${Array[@]}"
echo "${Array[0]}"
echo "${Array[1]}"
echo "[email protected];[email protected]" | sed -e 's/;/\n/g'
[email protected]
[email protected]
Cela fonctionne aussi:
IN="[email protected];[email protected]"
echo ADD1=`echo $IN | cut -d \; -f 1`
echo ADD2=`echo $IN | cut -d \; -f 2`
Attention, cette solution n'est pas toujours correcte. Si vous ne transmettez que "[email protected]", il sera attribué à ADD1 et à ADD2.
Je pense que AWK est la meilleure et efficace commande pour résoudre votre problème. AWK est inclus par défaut dans presque toutes les distributions Linux.
echo "[email protected];[email protected]" | awk -F';' '{print $1,$2}'
va donner
[email protected] [email protected]
Bien sûr, vous pouvez stocker chaque adresse électronique en redéfinissant le champ d'impression awk.
Un autre regard sur réponse de Darron , voici comment je le fais:
IN="[email protected];[email protected]"
read ADDR1 ADDR2 <<<$(IFS=";"; echo $IN)
Dans Bash, une méthode infaillible, qui fonctionnera même si votre variable contient des nouvelles lignes:
IFS=';' read -d '' -ra array < <(printf '%s;\0' "$in")
Regardez:
$ in=$'one;two three;*;there is\na newline\nin this field'
$ IFS=';' read -d '' -ra array < <(printf '%s;\0' "$in")
$ declare -p array
declare -a array='([0]="one" [1]="two three" [2]="*" [3]="there is
a newline
in this field")'
L'astuce pour que cela fonctionne est d'utiliser l'option -d
de read
(délimiteur) avec un délimiteur vide, de sorte que read
soit obligé de lire tout ce qui lui est transmis. Et nous alimentons read
avec exactement le contenu de la variable in
, sans fin de nouvelle ligne grâce à printf
. Notez que nous plaçons également le délimiteur dans printf
pour nous assurer que la chaîne transmise à read
comporte un délimiteur de fin. Sans elle, read
réduirait les champs vides en fin de chaîne:
$ in='one;two;three;' # there's an empty field
$ IFS=';' read -d '' -ra array < <(printf '%s;\0' "$in")
$ declare -p array
declare -a array='([0]="one" [1]="two" [2]="three" [3]="")'
le dernier champ vide est conservé.
Depuis Bash 4.4, le mapfile
intégré (aussi appelé readarray
) prend en charge l’option -d
pour spécifier un délimiteur. Une autre manière canonique est donc:
mapfile -d ';' -t array < <(printf '%s;' "$in")
Que diriez-vous de cette doublure, si vous n'utilisez pas de tableaux:
IFS=';' read ADDR1 ADDR2 <<<$IN
Voici un 3-liner propre:
in="foo@bar;bizz@buzz;fizz@buzz;buzz@woof"
IFS=';' list=($in)
for item in "${list[@]}"; do echo $item; done
où IFS
délimitent des mots basés sur le séparateur et ()
sert à créer un tablea . Ensuite, [@]
est utilisé pour renvoyer chaque élément en tant que mot séparé.
Si vous avez du code après cela, vous devez également restaurer $IFS
, par exemple. unset IFS
.
Sans régler l'IFS
Si vous n'avez qu'un colon, vous pouvez le faire:
a="foo:bar"
b=${a%:*}
c=${a##*:}
tu auras:
b = foo
c = bar
La fonction Bash/zsh suivante divise son premier argument sur le délimiteur donné par le second argument:
split() {
local string="$1"
local delimiter="$2"
if [ -n "$string" ]; then
local part
while read -d "$delimiter" part; do
echo $part
done <<< "$string"
echo $part
fi
}
Par exemple, la commande
$ split 'a;b;c' ';'
les rendements
a
b
c
Cette sortie peut, par exemple, être acheminée vers d'autres commandes. Exemple:
$ split 'a;b;c' ';' | cat -n
1 a
2 b
3 c
Par rapport aux autres solutions proposées, celle-ci présente les avantages suivants:
IFS
n'est pas annulé: en raison de la portée dynamique de variables même locales, le remplacement de IFS
par une boucle entraîne la fuite de la nouvelle valeur dans les appels de fonction effectués à l'intérieur de la boucle.
Les tableaux ne sont pas utilisés: la lecture d'une chaîne dans un tableau à l'aide de read
nécessite l'indicateur -a
dans Bash et -A
dans zsh.
Si vous le souhaitez, la fonction peut être insérée dans un script comme suit:
#!/usr/bin/env bash
split() {
# ...
}
split "$@"
Il existe un moyen simple et intelligent comme celui-ci:
echo "add:sfff" | xargs -d: -i echo {}
Mais vous devez utiliser gnu xargs, BSD xargs ne peut pas supporter -d delim. Si vous utilisez Apple mac comme moi. Vous pouvez installer gnu xargs:
brew install findutils
ensuite
echo "add:sfff" | gxargs -d: -i echo {}
vous pouvez appliquer awk à de nombreuses situations
echo "[email protected];[email protected]"|awk -F';' '{printf "%s\n%s\n", $1, $2}'
aussi vous pouvez utiliser cette
echo "[email protected];[email protected]"|awk -F';' '{print $1,$2}' OFS="\n"
C'est le moyen le plus simple de le faire.
spo='one;two;three'
OIFS=$IFS
IFS=';'
spo_array=($spo)
IFS=$OIFS
echo ${spo_array[*]}
Il y a quelques bonnes réponses ici (errator esp.), Mais pour que quelque chose d'analogue se scinde en d'autres langues - c'est ce que j'ai compris comme question initiale - je me suis décidé sur ceci:
IN="[email protected];[email protected]"
declare -a a="(${IN/;/ })";
Maintenant, ${a[0]}
, ${a[1]}
, etc., sont comme vous le souhaitiez. Utilisez ${#a[*]}
pour le nombre de termes. Ou pour itérer, bien sûr:
for i in ${a[*]}; do echo $i; done
NOTE IMPORTANTE:
Cela fonctionne dans les cas où il n'y a pas d'espaces à craindre, ce qui résout mon problème mais ne résout peut-être pas le vôtre. Allez avec la solution $IFS
dans ce cas.
IN="[email protected];[email protected]"
IFS=';'
read -a IN_arr <<< "${IN}"
for entry in "${IN_arr[@]}"
do
echo $entry
done
Sortie
[email protected]
[email protected]
Système: Ubuntu 12.04.1
Deux alternatives bourne-ish, où ni l'un ni l'autre n'exige de tableaux bash:
Cas 1: Keep it Nice and simple: Utilisez une NewLine comme séparateur d’enregistrements ... par ex.
IN="[email protected]
[email protected]"
while read i; do
# process "$i" ... eg.
echo "[email:$i]"
done <<< "$IN"
Remarque: dans ce premier cas, aucun sous-processus n'est créé pour faciliter la manipulation de la liste.
Idée: peut-être vaut-il la peine d’utiliser NL de manière intensive en interne et de ne convertir que sur un autre RS lors de la génération du résultat final à l'extérieur .
Cas 2: Utiliser un ";" comme séparateur d’enregistrements ...
NL="
" IRS=";" ORS=";"
conv_IRS() {
exec tr "$1" "$NL"
}
conv_ORS() {
exec tr "$NL" "$1"
}
IN="[email protected];[email protected]"
IN="$(conv_IRS ";" <<< "$IN")"
while read i; do
# process "$i" ... eg.
echo -n "[email:$i]$ORS"
done <<< "$IN"
Dans les deux cas, une sous-liste pouvant être composée dans la boucle est persistante une fois la boucle terminée. Ceci est utile lors de la manipulation de listes en mémoire, au lieu de stocker des listes dans des fichiers. {p.s. reste calme et continue B-)}
Outre les réponses fantastiques qui ont déjà été fournies, s’il ne s’agit que d’imprimer les données, vous pouvez envisager d’utiliser awk
:
awk -F";" '{for (i=1;i<=NF;i++) printf("> [%s]\n", $i)}' <<< "$IN"
Cela définit le séparateur de champ sur ;
afin qu'il puisse parcourir les champs avec une boucle for
et imprimer en conséquence.
$ IN="[email protected];[email protected]"
$ awk -F";" '{for (i=1;i<=NF;i++) printf("> [%s]\n", $i)}' <<< "$IN"
> [[email protected]]
> [[email protected]]
Avec une autre entrée:
$ awk -F";" '{for (i=1;i<=NF;i++) printf("> [%s]\n", $i)}' <<< "a;b;c d;e_;f"
> [a]
> [b]
> [c d]
> [e_]
> [f]
IN='[email protected];[email protected];Charlie Brown <[email protected];!"#$%&/()[]{}*? are no problem;simple is beautiful :-)'
set -f
oldifs="$IFS"
IFS=';'; arrayIN=($IN)
IFS="$oldifs"
for i in "${arrayIN[@]}"; do
echo "$i"
done
set +f
Sortie:
[email protected]
[email protected]
Charlie Brown <[email protected]
!"#$%&/()[]{}*? are no problem
simple is beautiful :-)
Explication: Une simple affectation à l'aide de parenthesis () convertit la liste séparée par des points-virgules en un tableau, à condition que vous disposiez de l'IFS correct. La boucle FOR standard gère les éléments individuels de ce tableau comme d'habitude. Notez que la liste donnée pour la variable IN doit être "dure", c'est-à-dire avec des ticks simples.
IFS doit être sauvegardé et restauré car Bash ne traite pas une affectation de la même manière qu'une commande. Une autre solution consiste à envelopper l'affectation dans une fonction et à l'appeler avec un IFS modifié. Dans ce cas, il n'est pas nécessaire de sauvegarder/restaurer l'IFS séparément. Merci pour "Bize" pour le signaler.
Si pas d'espace, pourquoi pas cela?
IN="[email protected];[email protected]"
arr=(`echo $IN | tr ';' ' '`)
echo ${arr[0]}
echo ${arr[1]}
Ok les gars!
Voici ma réponse!
DELIMITER_VAL='='
read -d '' F_ABOUT_DISTRO_R <<"EOF"
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=14.04
DISTRIB_CODENAME=trusty
DISTRIB_DESCRIPTION="Ubuntu 14.04.4 LTS"
NAME="Ubuntu"
VERSION="14.04.4 LTS, Trusty Tahr"
ID=ubuntu
ID_LIKE=debian
PRETTY_NAME="Ubuntu 14.04.4 LTS"
VERSION_ID="14.04"
HOME_URL="http://www.ubuntu.com/"
SUPPORT_URL="http://help.ubuntu.com/"
BUG_REPORT_URL="http://bugs.launchpad.net/ubuntu/"
EOF
SPLIT_NOW=$(awk -F$DELIMITER_VAL '{for(i=1;i<=NF;i++){printf "%s\n", $i}}' <<<"${F_ABOUT_DISTRO_R}")
while read -r line; do
SPLIT+=("$line")
done <<< "$SPLIT_NOW"
for i in "${SPLIT[@]}"; do
echo "$i"
done
Pourquoi cette approche est-elle "la meilleure" pour moi?
Pour deux raisons:
[]
Dans Android Shell, la plupart des méthodes proposées ne fonctionnent tout simplement pas:
$ IFS=':' read -ra ADDR <<<"$PATH"
/system/bin/sh: can't create temporary file /sqlite_stmt_journals/mksh.EbNoR10629: No such file or directory
Qu'est-ce que le travail est:
$ for i in ${PATH//:/ }; do echo $i; done
/sbin
/vendor/bin
/system/sbin
/system/bin
/system/xbin
où //
signifie remplacement global.
Utilisez le set
intégré pour charger le tableau $@
:
IN="[email protected];[email protected]"
IFS=';'; set $IN; IFS=$' \t\n'
Ensuite, que la fête commence:
echo $#
for a; do echo $a; done
ADDR1=$1 ADDR2=$2
Peut-être pas la solution la plus élégante, mais fonctionne avec *
et des espaces:
IN="bla@so me.com;*;[email protected]"
for i in `delims=${IN//[^;]}; seq 1 $((${#delims} + 1))`
do
echo "> [`echo $IN | cut -d';' -f$i`]"
done
Les sorties
> [bla@so me.com]
> [*]
> [[email protected]]
Autre exemple (délimiteurs au début et à la fin):
IN=";bla@so me.com;*;[email protected];"
> []
> [bla@so me.com]
> [*]
> [[email protected]]
> []
En gros, il supprime tous les caractères autres que ;
rendant delims
eg. ;;;
. Ensuite, la boucle for
passe de 1
à number-of-delimiters
telle que comptée par ${#delims}
. La dernière étape consiste à obtenir en toute sécurité la partie $i
à l'aide de cut
.
Une ligne pour diviser une chaîne séparée par un ';' dans un tableau est:
IN="[email protected];[email protected]"
ADDRS=( $(IFS=";" echo "$IN") )
echo ${ADDRS[0]}
echo ${ADDRS[1]}
Cela ne fait que définir IFS dans un sous-shell, vous évitant ainsi de sauvegarder et de restaurer sa valeur.
Cela va même gérer les espaces:
IFS=';' read ADDR1 ADDR2 <<< $(echo ${IN})