Boucle sur les tuples en bash?

88

Est-il possible de boucler sur des tuples dans bash?

À titre d'exemple, ce serait formidable si ce qui suit fonctionnait:

for (i,j) in ((c,3), (e,5)); do echo "$i and $j"; done

Existe-t-il une solution de contournement qui me permet en quelque sorte de boucler sur les tuples?

Franc
la source
4
Venant de fond python, c'est une question très utile en effet!
John Jiang
5
en regardant cela quatre ans plus tard, je me demande s'il n'y a toujours pas de meilleure façon de faire cela. OMG.
Giszmo
Près de 8 ans plus tard, je me suis également demandé s'il n'y avait toujours pas de meilleure façon de faire cela. Mais cette réponse de 2018 me semble plutôt bonne: stackoverflow.com/a/52228219/463994
MountainX

Réponses:

87
$ for i in c,3 e,5; do IFS=","; set -- $i; echo $1 and $2; done
c and 3
e and 5

À propos de cette utilisation de set(de man builtins):

Tous les arguments restants après le traitement des options sont traités comme des valeurs pour les paramètres de position et sont affectés, dans l'ordre, à $ 1, $ 2, ... $ n

Le IFS=","définit le séparateur de champ afin que chacun $isoit segmenté $1et $2correctement.

Via ce blog .

Edit: version plus correcte, comme suggéré par @SLACEDIAMOND:

$ OLDIFS=$IFS; IFS=','; for i in c,3 e,5; do set -- $i; echo $1 and $2; done; IFS=$OLDIFS
c and 3
e and 5
Eduardo Ivanec
la source
7
Nice - je veux juste souligner IFSdoit être enregistré et réinitialisé à sa valeur d'origine si cela est exécuté sur la ligne de commande. En outre, le nouveau IFSpeut être défini une fois, avant l'exécution de la boucle, plutôt qu'à chaque itération.
Eggxactly
1
Dans le cas où l'un des $ i commence par un trait d'union, il est plus sûr deset -- $i
glenn jackman
1
Au lieu d'enregistrer IFS, ne régler pour la setcommande: for i in c,3 e,5; do IFS="," set -- $i; echo $1 and $2; done. Veuillez modifier votre réponse: si tous les lecteurs choisissent une seule des solutions répertoriées, il est inutile de lire l'historique complet du développement. Merci pour cette super astuce!
cfi
Si je déclare tuples="a,1 b,2 c,3"et mets IFS=','comme dans la version modifiée, et au lieu de l' c,3 e,5utiliser, $tuplesil ne s'imprime pas du tout. Mais au lieu de cela, si je mets IFS=','juste après le domot - clé dans la boucle for, cela fonctionne bien lorsque vous utilisez $tuplesainsi que des valeurs littérales. Je pensais juste que ça valait la peine de le dire.
Simonlbc
@Simonlbc c'est parce que la boucle for utilise IFSpour diviser les itérations. c'est-à-dire que si vous bouclez sur un tableau comme arr=("c,3" "e,5")et que vous placez IFSavant la boucle for, la valeur de $isera juste cet e, elle sera séparée 3et ne sera 5donc setpas analysée correctement car elle $in'aura rien à analyser. Cela signifie que si les valeurs à itérer ne sont pas insérées, le IFSdoit être placé à l'intérieur de la boucle et la valeur extérieure doit respecter le séparateur prévu pour la variable sur laquelle itérer. Dans les cas, $tuplesil devrait être simplement IFS=ce qui est par défaut et se divise sur des espaces.
untore
25

Je pense que cette solution est un peu plus propre que les autres qui ont été soumises, h / t à ce guide de style bash pour illustrer comment read peut être utilisé pour diviser des chaînes à un délimiteur et les affecter à des variables individuelles.

for i in c,3 e,5; do 
    IFS=',' read item1 item2 <<< "${i}"
    echo "${item1}" and "${item2}"
done
Grant Humphries
la source
17

Sur la base de la réponse donnée par @ eduardo-ivanec sans paramétrer / réinitialiser le IFS, on pourrait simplement faire:

for i in "c 3" "e 5"
do
    set -- $i
    echo $1 and $2
done

Le résultat:

c and 3
e and 5
MZHm
la source
Cette approche me semble beaucoup plus simple que l'approche acceptée et la plus votée. Y a-t-il une raison de ne pas le faire de cette façon, contrairement à ce que @Eduardo Ivanec a suggéré?
spurra le
@spurra cette réponse est de 6 ans et ½ plus récente, et basée sur elle. Crédit là où il est dû.
Diego le
1
@Diego J'en suis conscient. C'est explicitement écrit dans la réponse. Je demandais s'il y avait une raison de ne pas utiliser cette approche plutôt que la réponse acceptée.
spurra le
2
@spurra, vous voudriez utiliser la réponse d'Eduardo si le séparateur par défaut (espace, tabulation ou nouvelle ligne) ne fonctionne pas pour vous pour une raison quelconque ( bash.cyberciti.biz/guide/$IFS )
Diego
11

Utilisez un tableau associatif (également appelé dictionnaire / hashMap):

declare -A pairs=(
  [c]=3
  [e]=5
)
for key in "${!pairs[@]}"; do
  value="${pairs[$key]}"
  echo "key is $key and value is $value"
done

Fonctionne pour bash4.0 +.


Si vous avez besoin de triples au lieu de paires, vous pouvez utiliser l'approche plus générale:

animals=(dog cat mouse)
declare -A sound=(
  [dog]=barks
  [cat]=purrs
  [mouse]=cheeps
)
declare -A size=(
  [dog]=big
  [cat]=medium
  [mouse]=small
)
for animal in "${animals[@]}"; do
  echo "$animal ${sound[$animal]} and it is ${size[$animal]}"
done
VasiliNovikov
la source
FYI, cela n'a pas fonctionné pour moi sur Mac avec GNU bash, version 4.4.23(1)-release-(x86_64-apple-darwin17.5.0), qui a été installé via brew, donc YMMV.
David
il a cependant fonctionné à GNU bash, version 4.3.11(1)-release-(x86_64-pc-linux-gnu)partir d'Ubuntu 14.04 dans le conteneur Docker.
David
semble que les anciennes versions de bash ou celles qui ne prennent pas en charge cette fonctionnalité fonctionnent hors de l'index? où la clé est un nombre plutôt qu'une chaîne. tldp.org/LDP/abs/html/declareref.html , et au lieu de cela, -Anous avons -a.
David
David, semble-t-il. Je pense que vous pouvez essayer les indices de tableau pour obtenir "l'associativité" alors. Comme declare -a indices=(1 2 3); declare -a sound=(barks purrs cheeps); declare -a size=(big medium small)etc. Je ne l'ai pas encore essayé dans le terminal, mais je pense que cela devrait fonctionner.
VasiliNovikov
7
c=('a' 'c')
n=(3    4 )

for i in $(seq 0 $((${#c[*]}-1)))
do
    echo ${c[i]} ${n[i]}
done

Cela peut parfois être plus pratique.

Pour expliquer la uglypartie, comme indiqué dans les commentaires:

seq 0 2 produit la séquence de nombres 0 1 2. $ (cmd) est une substitution de commande, donc pour cet exemple la sortie de seq 0 2, qui est la séquence de nombres. Mais quelle est la limite supérieure, la $((${#c[*]}-1))?

$ (( ${#c[*]}-1quelque chose )) est une expansion arithmétique, donc $ ((3 + 4)) vaut 7 etc. Notre expression est donc quelque chose - 1. Assez simple, si nous savons ce que ${#c[*]}c'est.

c est un tableau, c [*] est juste le tableau entier, $ {# c [*]} est la taille du tableau qui est 2 dans notre cas. Maintenant, nous annulons tout: for i in $(seq 0 $((${#c[*]}-1)))c'est for i in $(seq 0 $((2-1)))c'est for i in $(seq 0 1)c'est for i in 0 1. Parce que le dernier élément du tableau a un index qui est la longueur du tableau - 1.

Utilisateur inconnu
la source
1
vous devriez fairefor i in $(seq 0 $(($#c[*]}-1))); do [...]
reox
1
Wow, cela remporte le prix du «Groupe de personnages arbitraires le plus laid que j'ai vu aujourd'hui». Quelqu'un veut-il expliquer ce que fait exactement cette abomination? Je me suis perdu au signe de hachage ...
koniiiik
1
@koniiiik: Explication ajoutée.
utilisateur inconnu
6
$ echo 'c,3;e,5;' | while IFS=',' read -d';' i j; do echo "$i and $j"; done
c and 3
e and 5
kev
la source
3

Utilisation de GNU Parallel:

parallel echo {1} and {2} ::: c e :::+ 3 5

Ou:

parallel -N2 echo {1} and {2} ::: c 3 e 5

Ou:

parallel --colsep , echo {1} and {2} ::: c,3 e,5
Ole Tange
la source
1
Pas d'amour pour ça? bien m'a fait surmonter l'inertie et installergnu parallel
StephenBoesch
2
brew install parallel
StephenBoesch
2

Utilisation printfdans une substitution de processus:

while read -r k v; do
    echo "Key $k has value: $v"
done < <(printf '%s\n' 'key1 val1' 'key2 val2' 'key3 val3')

Key key1 has value: val1
Key key2 has value: val2
Key key3 has value: val3

Ci-dessus oblige bash. Si bashn'est pas utilisé, utilisez un pipeline simple:

printf '%s\n' 'key1 val1' 'key2 val2' 'key3 val3' |
while read -r k v; do echo "Key $k has value: $v"; done
anubhava
la source
1
Oui! Anubhava, doux génie!
Scott le
1
do echo $key $value
done < file_discriptor

par exemple:

$ while read key value; do echo $key $value ;done <<EOF
> c 3
> e 5
> EOF
c 3
e 5

$ echo -e 'c 3\ne 5' > file

$ while read key value; do echo $key $value ;done <file
c 3
e 5

$ echo -e 'c,3\ne,5' > file

$ while IFS=, read key value; do echo $key $value ;done <file
c 3
e 5
prodriguez903
la source
0

Un peu plus compliqué, mais peut être utile:

a='((c,3), (e,5))'
IFS='()'; for t in $a; do [ -n "$t" ] && { IFS=','; set -- $t; [ -n "$1" ] && echo i=$1 j=$2; }; done
Diego Torres Milan
la source
0

Mais que se passe-t-il si le tuple est supérieur au k / v qu'un tableau associatif peut contenir? Et si c'était 3 ou 4 éléments? On pourrait développer ce concept:

###---------------------------------------------------
### VARIABLES
###---------------------------------------------------
myVars=(
    'ya1,ya2,ya3,ya4'
    'ye1,ye2,ye3,ye4'
    'yo1,yo2,yo3,yo4'
    )


###---------------------------------------------------
### MAIN PROGRAM
###---------------------------------------------------
### Echo all elements in the array
###---
printf '\n\n%s\n' "Print all elements in the array..."
for dataRow in "${myVars[@]}"; do
    while IFS=',' read -r var1 var2 var3 var4; do
        printf '%s\n' "$var1 - $var2 - $var3 - $var4"
    done <<< "$dataRow"
done

Ensuite, la sortie ressemblerait à quelque chose comme:

$ ./assoc-array-tinkering.sh 

Print all elements in the array...
ya1 - ya2 - ya3 - ya4
ye1 - ye2 - ye3 - ye4
yo1 - yo2 - yo3 - yo4

Et le nombre d'éléments est désormais illimité. Ne pas chercher de votes; penser à voix haute. REF1 , REF2

todd_dsm
la source