Comment partager une chaîne sur un délimiteur en Bash?
j'ai cette chaîne stockée dans une variable:
IN="bla@some.com;john@home.com"
maintenant, je voudrais diviser les chaînes par ;
délimiteur de sorte que j'ai:
ADDR1="bla@some.com"
ADDR2="john@home.com"
Je n'ai pas nécessairement besoin des variables ADDR1
et ADDR2
. Si ils sont des éléments d'un tableau qui est encore mieux.
après les suggestions des réponses ci-dessous, je me suis retrouvé avec la suivante qui est ce que je était après:
#!/usr/bin/env bash
IN="bla@some.com;john@home.com"
mails=$(echo $IN | tr ";" "n")
for addr in $mails
do
echo "> [$addr]"
done
sortie:
> [bla@some.com]
> [john@home.com]
il y avait une solution impliquant la mise Internal_field_separator (IFS) à ;
. Je ne suis pas sûr de ce qui s'est passé avec cette réponse, comment réinitialiser IFS
de nouveau à la valeur par défaut?
RE: IFS
solution, j'ai essayé ceci et cela fonctionne, je garde l'ancien IFS
et puis le restaurer:
IN="bla@some.com;john@home.com"
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 eu la première chaîne que lors de l'impression en boucle, sans crochets autour de $IN
ça marche.
30 réponses
vous pouvez définir la variable séparateur de champ interne (IFS), puis la laisser analyser dans un tableau. Lorsque cela se produit dans une commande, alors l'affectation à IFS
n'a lieu que dans l'environnement de cette seule commande (à read
). Il analyse ensuite l'entrée selon la valeur de la variable IFS
dans un tableau, sur lequel nous pouvons ensuite itérer.
IFS=';' read -ra ADDR <<< "$IN"
for i in "${ADDR[@]}"; do
# process "$i"
done
il analysera une ligne d'articles séparés par ;
en le poussant dans un tableau. Des trucs pour le traitement de l'ensemble des $IN
, à chaque fois qu'une ligne d'entrée séparés par des ;
:
while IFS=';' read -ra ADDR; do
for i in "${ADDR[@]}"; do
# process "$i"
done
done <<< "$IN"
extrait de shell Bash script split tableau :
IN="bla@some.com;john@home.com"
arrIN=(${IN//;/ })
explication:
cette construction remplace toutes les occurrences de ';'
(l'initiale //
signifie global replace) dans la chaîne IN
par ' '
(un seul espace), puis interprète la chaîne délimitée par l'espace comme un tableau (c'est ce que font les parenthèses environnantes).
la syntaxe utilisée à l'intérieur des accolades pour remplacer chaque caractère ';'
par un caractère ' '
est appelée Parameter Expansion .
il y a quelques gotchas communs:
si cela ne vous dérange pas de les traiter immédiatement, j'aime faire ceci:
for i in $(echo $IN | tr ";" "\n")
do
# process
done
, Vous pouvez utiliser ce genre de boucle d'initialiser un tableau, mais il y a probablement un moyen plus facile de le faire. Espérons que cela aide, cependant.
réponse Compatible
à cette question, il y a déjà beaucoup de façons différentes de le faire dans bash . Mais bash a beaucoup de spécial caractéristiques, ainsi appelé bashism qui fonctionnent bien, mais qui ne fonctionnera dans aucun autre shell .
en particulier, tableaux , associatif tableau , et substitution de motif sont purs bashismes et ne peuvent pas fonctionner sous d'autres coquillages .
sur mon Debian GNU/Linux , il y a un standard shell appelé dash , mais je connais beaucoup de gens qui aiment utiliser ksh .
enfin, en très petite situation, il existe un outil spécial appelé busybox avec son propre interpréteur de commandes ( ash ).
chaîne de caractères demandée
l'échantillon de chaîne en question Est:
IN="bla@some.com;john@home.com"
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 cette chaîne d'échantillon:
IN="bla@some.com;john@home.com;Full Name <fulnam@other.org>"
scinde une chaîne basée sur délimiteur dans bash (version >=4.2)
sous pur bash, nous pouvons utiliser tableaux et IFS :
var="bla@some.com;john@home.com;Full Name <fulnam@other.org>"
oIFS="$IFS"
IFS=";"
declare -a fields=($var)
IFS="$oIFS"
unset oIFS
IFS=\; read -a fields <<<"$var"
en utilisant cette syntaxe sous récente bash ne changez pas $IFS
pour session courante, mais seulement pour la commande courante:
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]="bla@some.com" [1]="john@home.com" [2]="Full Name <fulnam@other.org>")
var='bla@some.com;john@home.com;Full Name <fulnam@other.org>'
nous pourrions demander un contenu variable avec declare -p
:
declare -p var fields
declare -- var="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>")
read
est le le plus rapide façon de faire la séparation, parce qu'il n'y a pas fourches et aucune ressources externes appelé.
de là, vous pouvez utiliser la syntaxe que vous connaissez déjà pour traiter chaque champ:
for x in "${fields[@]}";do
echo "> [$x]"
done
> [bla@some.com]
> [john@home.com]
> [Full Name <fulnam@other.org>]
ou laisser tomber chaque champ après traitement (j'aime ce shifting approach):
while [ "$fields" ] ;do
echo "> [$fields]"
fields=("${fields[@]:1}")
done
> [bla@some.com]
> [john@home.com]
> [Full Name <fulnam@other.org>]
ou même pour l'impression simple (syntaxe plus courte):
printf "> [%s]\n" "${fields[@]}"
> [bla@some.com]
> [john@home.com]
> [Full Name <fulnam@other.org>]
scinde une chaîne basée sur délimiteur dans shell
mais si vous écrirait quelque chose utilisable sous de nombreux coquillages, vous devez pas utiliser bashismes .
Il y a une syntaxe, utilisé dans de nombreuses coquilles, pour scinder une chaîne à travers première ou dernier la présence 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 c'est la principale raison de ma réponse à la publication ;)
comme indiqué par Score_Under :
#
et%
supprimer la chaîne la plus courte possible, et
##
et%%
supprimer le plus long possible.
ce petit exemple de script fonctionne bien sous bash , dash , ksh , busybox et a été testé sous Mac-OS du bash trop:
var="bla@some.com;john@home.com;Full Name <fulnam@other.org>"
while [ "$var" ] ;do
iter=${var%%;*}
echo "> [$iter]"
[ "$var" = "$iter" ] && \
var='' || \
var="${var#*;}"
done
> [bla@some.com]
> [john@home.com]
> [Full Name <fulnam@other.org>]
amusez-vous bien!
j'ai vu quelques réponses faisant référence à la commande cut
, mais elles ont toutes été supprimées. C'est un peu étrange que personne n'ait développé cela, parce que je pense que c'est l'une des commandes les plus utiles pour faire ce genre de chose, surtout pour l'analyse des fichiers journaux délimités.
dans le cas de diviser cet exemple spécifique en un tableau de script bash, tr
est probablement plus efficace, mais cut
peut être utilisé, et est plus efficace si vous souhaitez tirer des domaines spécifiques du milieu.
exemple:
$ echo "bla@some.com;john@home.com" | cut -d ";" -f 1
bla@some.com
$ echo "bla@some.com;john@home.com" | cut -d ";" -f 2
john@home.com
vous pouvez évidemment mettre cela dans une boucle, et itérer le paramètre-f pour tirer chaque champ indépendamment.
cela devient plus utile quand vous avez un fichier log délimité avec des lignes comme ceci:
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 la suite du traitement.
cela a fonctionné pour moi:
string="1;2"
echo $string | cut -d';' -f1 # output is 1
echo $string | cut -d';' -f2 # output is 2
echo "bla@some.com;john@home.com" | sed -e 's/;/\n/g'
bla@some.com
john@home.com
cela fonctionne aussi:
IN="bla@some.com;john@home.com"
echo ADD1=`echo $IN | cut -d \; -f 1`
echo ADD2=`echo $IN | cut -d \; -f 2`
attention, cette solution n'est pas toujours correcte. Au cas où tu passes "bla@some.com" seulement, il l'assignera à ADD1 et ADD2.
je pense AWK est la meilleure et efficace de commande pour résoudre votre problème. AWK est inclus dans Bash par défaut dans presque toutes les distributions Linux.
echo "bla@some.com;john@home.com" | awk -F';' '{print ,}'
donnera
bla@some.com john@home.com
bien sûr, vous pouvez stocker chaque adresse e-mail en redéfinissant le champ d'impression awk.
faire différent Darron la réponse de , c'est comment je le fais:
IN="bla@some.com;john@home.com"
read ADDR1 ADDR2 <<<$(IFS=";"; echo $IN)
en Bash, une façon à l'épreuve des balles, qui fonctionnera même si votre variable contient des lignes nouvelles:
IFS=';' read -d '' -ra array < <(printf '%s;"151900920"' "$in")
Regarde:
$ in=$'one;two three;*;there is\na newline\nin this field'
$ IFS=';' read -d '' -ra array < <(printf '%s;"151910920"' "$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
est forcé de lire tout ce qu'il est alimenté. Et nous alimentons read
avec exactement le contenu de la variable in
, sans newline arrière grâce à printf
. Notez que nous mettons également le délimiteur dans printf
pour s'assurer que la chaîne passée à read
a un délimiteur de fuite. Sans elle, read
compenserait les champs vides potentiels:
$ in='one;two;three;' # there's an empty field
$ IFS=';' read -d '' -ra array < <(printf '%s;"151920920"' "$in")
$ declare -p array
declare -a array='([0]="one" [1]="two" [2]="three" [3]="")'
le champ vide arrière est préservé.
mise à jour pour Bash≥4.4
depuis Bash 4.4, le bâtiment mapfile
(alias readarray
) supporte -d
option pour spécifier un délimiteur. Une autre voie canonique est donc:
mapfile -d ';' -t array < <(printf '%s;' "$in")
Que pensez-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élimite les mots basés sur le séparateur et ()
est utilisé pour créer un tableau . Puis [@]
est utilisé pour retourner chaque élément comme un mot séparé.
si vous avez un code après cela, vous devez aussi restaurer $IFS
, par exemple unset IFS
.
sans Fi
si vous n'avez qu'un côlon, vous pouvez le faire:
a="foo:bar"
b=${a%:*}
c=${a##*:}
vous obtiendrez:
b = foo
c = bar
il y a une manière simple et intelligente comme celle-ci:
echo "add:sfff" | xargs -d: -i echo {}
mais vous devez utiliser gnu xargs, BSD xargs cant support-d delim. Si vous utilisez apple mac comme moi. Vous pouvez installer gnu xargs:
brew install findutils
puis
echo "add:sfff" | gxargs -d: -i echo {}
la fonction Bash/zsh suivante divise son premier argument sur le délimiteur donné par le second argument:
split() {
local string=""
local delimiter=""
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' ';'
rendements
a
b
c
Cette sortie peut, par exemple, être transmis à 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 outrepassé: en raison de la portée dynamique des variables même locales, l'annulation deIFS
sur une boucle provoque la fuite de la nouvelle valeur dans les appels de fonction effectués à partir de l'intérieur de la boucle. -
les tableaux ne sont pas utilisés: la lecture d'une chaîne dans un tableau utilisant
read
nécessite le drapeau-a
en Bash et-A
en zsh.
If désirée, la fonction peut être mise dans un script comme suit:
#!/usr/bin/env bash
split() {
# ...
}
split "$@"
C'est la façon la plus simple de le faire.
spo='one;two;three'
OIFS=$IFS
IFS=';'
spo_array=($spo)
IFS=$OIFS
echo ${spo_array[*]}
IN="bla@some.com;john@home.com"
IFS=';'
read -a IN_arr <<< "${IN}"
for entry in "${IN_arr[@]}"
do
echo $entry
done
sortie
bla@some.com
john@home.com
Système: Ubuntu 12.04.1
vous pouvez appliquer awk à de nombreuses situations
echo "bla@some.com;john@home.com"|awk -F';' '{printf "%s\n%s\n", , }'
vous pouvez également utiliser ce
echo "bla@some.com;john@home.com"|awk -F';' '{print ,}' OFS="\n"
s'il n'y a pas d'Espace, pourquoi pas ceci?
IN="bla@some.com;john@home.com"
arr=(`echo $IN | tr ';' ' '`)
echo ${arr[0]}
echo ${arr[1]}
Il y a quelques frais de réponses ici (errator esp.), mais pour quelque chose d'analogue à split dans d'autres langues, qui est ce que j'ai pris à la question d'origine à dire -- je me suis installé sur ce point:
IN="bla@some.com;john@home.com"
declare -a a="(${IN/;/ })";
maintenant ${a[0]}
, ${a[1]}
, etc, sont comme vous l'attendez. Utilisez ${#a[*]}
pour le nombre de termes. Ou pour effectuer une itération, 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 ce qui a résolu mon problème, mais ne résoudra peut-être pas le vôtre. Dans ce cas, utilisez la ou les solutions $IFS
.
Utiliser le set
intégré pour charger le $@
tableau:
IN="bla@some.com;john@home.com"
IFS=';'; set $IN; IFS=$' \t\n'
alors, que le parti commence:
echo $#
for a; do echo $a; done
ADDR1= ADDR2=
Deux bourne-ish alternatives où ni exiger bash tableaux:
Cas 1 : Garder belle et simple: Utiliser un Saut de ligne comme le Séparateur... par exemple.
IN="bla@some.com
john@home.com"
while read i; do
# process "$i" ... eg.
echo "[email:$i]"
done <<< "$IN"
Note: dans ce premier cas, aucun sous-processus n'est biffé pour faciliter la manipulation de la liste.
idée: peut - être qu'il vaut la peine D'utiliser NL extensivement interne , et de convertir uniquement à un RS différent lorsque générant le résultat final extérieurement .
Cas 2 : à l'Aide d'un ";" comme séparateur d'enregistrement... par exemple.
NL="
" IRS=";" ORS=";"
conv_IRS() {
exec tr "" "$NL"
}
conv_ORS() {
exec tr "$NL" ""
}
IN="bla@some.com;john@home.com"
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 peut ê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. restez calme et continuez B -)}
mis à part les réponses fantastiques qui ont déjà été fournis, si il est juste une question d'imprimer les données, vous pouvez envisager d'utiliser awk
:
awk -F";" '{for (i=1;i<=NF;i++) printf("> [%s]\n", $i)}' <<< "$IN"
ce paramètre définit le séparateur de champ à ;
, de sorte qu'il puisse boucler les champs avec une boucle for
et imprimer en conséquence.
Test
$ IN="bla@some.com;john@home.com"
$ awk -F";" '{for (i=1;i<=NF;i++) printf("> [%s]\n", $i)}' <<< "$IN"
> [bla@some.com]
> [john@home.com]
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]
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
Ce qui fonctionne est:
$ for i in ${PATH//:/ }; do echo $i; done
/sbin
/vendor/bin
/system/sbin
/system/bin
/system/xbin
où //
signifie remplacement global.
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 "la meilleure" pour moi?
pour deux raisons:
- Vous ne pas besoin de s'échapper le délimiteur;
- Vous n'aurez pas de problème avec les espaces vides . La valeur sera correctement séparée dans le tableau!
[]
une doublure unique pour séparer une chaîne séparée par'; 'en un tableau est:
IN="bla@some.com;john@home.com"
ADDRS=( $(IFS=";" echo "$IN") )
echo ${ADDRS[0]}
echo ${ADDRS[1]}
cela ne définit que le IFS dans une sous-cellule, vous n'avez donc pas à vous soucier de sauvegarder et de restaurer sa valeur.
IN='bla@some.com;john@home.com;Charlie Brown <cbrown@acme.com;!"#$%&/()[]{}*? 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:
bla@some.com
john@home.com
Charlie Brown <cbrown@acme.com
!"#$%&/()[]{}*? are no problem
simple is beautiful :-)
explication: l'affectation Simple utilisant la parenthèse () convertit la liste séparée par un point-virgule en un tableau à condition que vous ayez les si corrects tout en faisant cela. Standard FOR loop gère les éléments individuels dans ce tableau comme d'habitude. Notez que la liste donnée pour la variable doit être "dure" Citée, c'est-à-dire avec des tiques simples.
IFS doit être sauvegardé et restauré car Bash ne traite pas une mission de la même manière façon comme une commande. Une solution de rechange consiste à envelopper l'affectation dans une fonction et à appeler cette fonction avec un IFS modifié. Dans ce cas, la sauvegarde/restauration séparée de L'IFS n'est pas nécessaire. Merci pour "Bize" pour avoir pointé ça.
peut-être pas la solution la plus élégante, mais fonctionne avec *
et les espaces:
IN="bla@so me.com;*;john@home.com"
for i in `delims=${IN//[^;]}; seq 1 $((${#delims} + 1))`
do
echo "> [`echo $IN | cut -d';' -f$i`]"
done
sorties
> [bla@so me.com]
> [*]
> [john@home.com]
autre exemple (délimiteurs au début et à la fin):
IN=";bla@so me.com;*;john@home.com;"
> []
> [bla@so me.com]
> [*]
> [john@home.com]
> []
essentiellement, il supprime tous les caractères autres que ;
faire delims
eg. ;;;
. Puis il fait for
boucle de 1
à number-of-delimiters
comme compté par ${#delims}
. La dernière étape est d'obtenir en toute sécurité la $i
th pièce en utilisant cut
.