Boucle à travers une variable shell séparée par des virgules

109

Supposons que j'ai une variable shell Unix comme ci-dessous

variable=abc,def,ghij

Je veux extraire toutes les valeurs ( abc, defet ghij) en utilisant une boucle for et passer chaque valeur dans une procédure.

Le script doit permettre d'extraire un nombre arbitraire de valeurs séparées par des virgules $variable.

Ramanathan K
la source
Que voulez-vous exactement faire en répétant?
SMA
1
Je veux passer chacun des champs (disons abc) comme argument à une procédure.
Ramanathan K

Réponses:

125

Vous pouvez utiliser le script suivant pour parcourir dynamiquement votre variable, quel que soit le nombre de champs qu'elle contient, à condition qu'elle ne soit séparée que par des virgules.

variable=abc,def,ghij
for i in $(echo $variable | sed "s/,/ /g")
do
    # call your procedure/other scripts here below
    echo "$i"
done

Au lieu de l' echo "$i"appel ci-dessus, entre la doet doneà l'intérieur de la boucle for, vous pouvez appeler votre procédure proc "$i".


Mise à jour : l'extrait de code ci-dessus fonctionne si la valeur de la variable ne contient pas d'espaces. Si vous avez une telle exigence, veuillez utiliser l'une des solutions qui peuvent changer IFS, puis analyser votre variable.


J'espère que cela t'aides.

anacron
la source
8
Cela ne fonctionne pas si $variablecontient des espaces, par exemple variable=a b,c,d=> imprime 4 lignes (a | b | c | d) au lieu de 3 (ab | c | d)
Dan
De plus, il reçoit de nouvelles citations comme ** "value **. pourriez-vous s'il vous plaît mettre à jour s'il y a une regex pour supprimer cela ..
gks
136

Ne pas jouer avec IFS
Ne pas appeler de commande externe

variable=abc,def,ghij
for i in ${variable//,/ }
do
    # call your procedure/other scripts here below
    echo "$i"
done

Utilisation de la manipulation de chaînes bash http://www.tldp.org/LDP/abs/html/string-manipulation.html

neric
la source
3
La bonne chose à ce sujet est que vous pouvez imbriquer plusieurs boucles en utilisant différents délimiteurs. Bonne suggestion!
Brad Parks
5
Bonne astuce pour éviter de gâcher IFS!
Laimoncijus
13
Petit avertissement - si l'une des valeurs de $icontient des espaces, elle sera divisée (et cela pourrait être la raison pour laquelle elle est passée comme séparée par des virgules plutôt que par des espaces)
Toby Speight
4
Si une fonction précédente a changé le réglage IFS, cela ne fonctionne pas ... Modifiez-le comme suit:for i in ${variable//,/$IFS} do; echo "$i"; done
Joep
2
J'obtiens le message d'erreur «Mauvaise substitution» lorsque j'essaye cette approche.
Lost Crotchet
55

Si vous définissez un séparateur de champ différent, vous pouvez directement utiliser une forboucle:

IFS=","
for v in $variable
do
   # things with "$v" ...
done

Vous pouvez également stocker les valeurs dans un tableau, puis le parcourir comme indiqué dans Comment diviser une chaîne sur un délimiteur dans Bash? :

IFS=, read -ra values <<< "$variable"
for v in "${values[@]}"
do
   # things with "$v"
done

Tester

$ variable="abc,def,ghij"
$ IFS=","
$ for v in $variable
> do
> echo "var is $v"
> done
var is abc
var is def
var is ghij

Vous pouvez trouver une approche plus large dans cette solution pour parcourir une liste séparée par des virgules et exécuter une commande pour chaque entrée .

Exemples sur la deuxième approche:

$ IFS=, read -ra vals <<< "abc,def,ghij"
$ printf "%s\n" "${vals[@]}"
abc
def
ghij
$ for v in "${vals[@]}"; do echo "$v --"; done
abc --
def --
ghij --
fedorqui 'Alors arrêtez de nuire'
la source
Voir aussi stackoverflow.com/questions/918886/… :-) Bonne chance à tous.
shellter
Oui! J'ai également pensé à cela, seulement qu'il fallait ensuite effectuer des étapes: un whilepour lire dans un tableau et un autre pour parcourir les résultats.
fedorqui 'SO arrêtez de nuire'
3
Cela me rend heureux quand quelqu'un sait vraiment comment utiliser le shell, plutôt que de rassembler un tas de binutils.
PeterT
devez-vous IFSrevenir à ce que c'était avant?
pstanton le
1
@fedorqui C'est vrai! C'est la seule variable que je voulais parcourir et cela a rendu mon fichier yaml plus facile à lire (au lieu d'une variable d'environnement LONG, il a un tas de lignes)
GammaGames
5
#/bin/bash   
TESTSTR="abc,def,ghij"

for i in $(echo $TESTSTR | tr ',' '\n')
do
echo $i
done

Je préfère utiliser tr au lieu de sed, car sed a des problèmes avec des caractères spéciaux comme \ r \ n dans certains cas.

une autre solution consiste à régler IFS sur un certain séparateur

Marek Lisiecki
la source
1

Voici une solution alternative basée sur tr qui n'utilise pas d'écho, exprimé sous la forme d'une ligne unique.

for v in $(tr ',' '\n' <<< "$var") ; do something_with "$v" ; done

C'est plus net sans écho, mais ce n'est que ma préférence personnelle.

6EQUJ5
la source
0

Essaye celui-là.

#/bin/bash   
testpid="abc,def,ghij" 
count=`echo $testpid | grep -o ',' | wc -l` # this is not a good way
count=`expr $count + 1` 
while [ $count -gt 0 ]  ; do
     echo $testpid | cut -d ',' -f $i
     count=`expr $count - 1 `
done
Karthikeyan.RS
la source
mais que faire si je ne suis pas sûr du nombre de champs dans la variable testpid?
Ramanathan K
De plus, je dois transmettre chaque champ de la variable ci-dessus comme argument à une procédure. Et je veux faire cela jusqu'à ce que la longueur de la variable devienne zéro. (c'est-à-dire; tous les champs doivent être passés une fois à la procédure de traitement)
Ramanathan K
Je ne connais pas la procédure. À partir de ce lien, vous pouvez obtenir le nombre de champs. http://stackoverflow.com/questions/8629330/unix-count-of-columns-in-file. Après cela, faites une boucle for en boucle while. Vous pouvez obtenir celui-là.
Karthikeyan.RS
C'est pour compter les champs d'un fichier je suppose. Que faire si je dois compter les champs dans une variable (supposons que la variable soit testpid = abc, def, ghij)
Ramanathan K
echo la variable et redirige la sortie vers awk. Ou bien voir ma réponse mise à jour. C'est peut-être utile.
Karthikeyan.RS
0

Autre solution n'utilisant pas IFS et préservant toujours les espaces:

$ var="a bc,def,ghij"
$ while read line; do echo line="$line"; done < <(echo "$var" | tr ',' '\n')
line=a bc
line=def
line=ghij
utilisateur3071170
la source
Cela fonctionne correctement dans bash, mais zsh avale les espaces.
lleaff
0

Voici ma solution pure bash qui ne change pas IFS et peut prendre un délimiteur d'expression régulière personnalisé.

loop_custom_delimited() {
    local list=$1
    local delimiter=$2
    local item
    if [[ $delimiter != ' ' ]]; then
        list=$(echo $list | sed 's/ /'`echo -e "\010"`'/g' | sed -E "s/$delimiter/ /g")
    fi
    for item in $list; do
        item=$(echo $item | sed 's/'`echo -e "\010"`'/ /g')
        echo "$item"
    done
}
placidwolverine
la source