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.

1566
demandé sur codeforester 2009-05-28 06:03:43

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"
958
répondu Johannes Schaub - litb 2012-03-08 20:31:44

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:

  1. si la chaîne originale comporte des espaces, vous devrez utiliser IFS :
    • IFS=':'; arrIN=($IN); unset IFS;
  2. Si la chaîne d'origine a les espaces et le délimiteur est une nouvelle ligne, vous pouvez définir IFS avec:
    • IFS=$'\n'; arrIN=($IN); unset IFS;
768
répondu palindrom 2017-04-13 12:36:28

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.

214
répondu Chris Lutz 2009-05-28 02:09:44

réponse Compatible

à cette question, il y a déjà beaucoup de façons différentes de le faire dans . Mais bash a beaucoup de spécial caractéristiques, ainsi appelé bashism qui fonctionnent bien, mais qui ne fonctionnera dans aucun autre .

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é , mais je connais beaucoup de gens qui aiment utiliser .

enfin, en très petite situation, il existe un outil spécial appelé avec son propre interpréteur de commandes ( ).

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 (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

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 , , , 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!

134
répondu F. Hauri 2017-12-23 17:37:51

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.

85
répondu DougW 2015-04-28 22:17:52

Que pensez-vous de cette approche:

IN="bla@some.com;john@home.com" 
set -- "$IN" 
IFS=";"; declare -a Array=($*) 
echo "${Array[@]}" 
echo "${Array[0]}" 
echo "${Array[1]}" 

Source

81
répondu BLeB 2011-07-20 16:21:05

cela a fonctionné pour moi:

string="1;2"
echo $string | cut -d';' -f1 # output is 1
echo $string | cut -d';' -f2 # output is 2
75
répondu Steven Lizarazo 2017-01-24 02:33:53
echo "bla@some.com;john@home.com" | sed -e 's/;/\n/g'
bla@some.com
john@home.com
59
répondu lothar 2009-05-28 02:12:59

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.

58
répondu Ashok 2014-04-17 01:39:20

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.

32
répondu Tony 2015-04-19 22:26:52

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)
25
répondu nickjb 2017-05-23 12:34:44

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")
23
répondu gniourf_gniourf 2015-10-27 16:03:48

Que pensez-vous de cette doublure, si vous n'utilisez pas de tableaux:

IFS=';' read ADDR1 ADDR2 <<<$IN
18
répondu Darron 2010-09-13 20:10:42

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

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 .

17
répondu kenorb 2016-10-26 10:26:38

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
16
répondu Emilien Brigand 2016-08-01 13:15:07

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 {}
7
répondu Victor Choy 2015-09-16 03:34:51

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 de IFS 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 "$@"
5
répondu Halle Knast 2017-06-13 18:24:31

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[*]}
4
répondu Prospero 2012-02-28 08:18:47
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

3
répondu rashok 2016-10-25 12:55:51

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"
3
répondu shuaihanhungry 2018-01-21 11:34:13

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]}
2
répondu ghost 2013-04-24 13:13:57

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 .

2
répondu eukras 2017-01-21 20:50:45

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=
1
répondu jeberle 2013-04-30 03:10:43

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 -)}

1
répondu NevilleDNZ 2013-09-02 06:45:57

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]
1
répondu fedorqui 2015-01-08 10:21:45

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

// signifie remplacement global.

1
répondu 18446744073709551615 2015-04-19 22:27:16

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:

  1. Vous ne pas besoin de s'échapper le délimiteur;
  2. Vous n'aurez pas de problème avec les espaces vides . La valeur sera correctement séparée dans le tableau!

[]

1
répondu Eduardo Lucio 2016-04-04 20:22:07

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.

0
répondu Michael Hale 2014-11-29 22:02:13
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.

0
répondu ajaaskel 2015-04-19 22:28:46

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 .

0
répondu Petr Újezdský 2016-02-26 12:20:31