Question Comment puis-je itérer sur une plage de nombres définis par des variables dans Bash?


Comment puis-je itérer sur une plage de nombres dans Bash lorsque la plage est donnée par une variable?

Je sais que je peux le faire (appelé "expression de séquence" dans le Bash Documentation):

 for i in {1..5}; do echo $i; done

Qui donne:

1
  2
  3
  4
  5

Cependant, comment puis-je remplacer l'un ou l'autre des points de terminaison de la plage par une variable? Cela ne fonctionne pas:

END=5
for i in {1..$END}; do echo $i; done

Qui imprime:

{1..5}


1056
2017-10-04 01:38


origine


Réponses:


for i in $(seq 1 $END); do echo $i; done

éditer: je préfère seq sur les autres méthodes parce que je peux m'en souvenir;)


1186
2017-10-04 01:41



le seq La méthode est la plus simple, mais Bash a une évaluation arithmétique intégrée.

END=5
for ((i=1;i<=END;i++)); do
    echo $i
done
# ==> outputs 1 2 3 4 5 on separate lines

le for ((expr1;expr2;expr3)); construire des œuvres comme for (expr1;expr2;expr3) en C et langues similaires, et comme les autres ((expr)) cas, Bash les traite comme arithmétique.


309
2017-10-04 21:43



discussion

En utilisant seq va bien, comme Jiaaro l'a suggéré. Pax Diablo a suggéré une boucle Bash pour éviter d'appeler un sous-processus, avec l'avantage supplémentaire d'être plus compatible avec la mémoire si $ END est trop grand. Zathrus a repéré un bug typique dans l'implémentation de la boucle, et a également laissé entendre que depuis i est une variable textuelle, les conversions continues de va-et-vient sont effectuées avec un ralentissement associé.

arithmétique entière

C'est une version améliorée de la boucle Bash:

typeset -i i END
let END=5 i=1
while ((i<=END)); do
    echo $i
    …
    let i++
done

Si la seule chose que nous voulons est la echoalors on pourrait écrire echo $((i++)).

éphémère m'a appris quelque chose: Bash permet for ((expr;expr;expr)) construit. Depuis que je n'ai jamais lu toute la page de manuel de Bash (comme je l'ai fait avec le shell Korn (ksh) page de manuel, et c'était il y a longtemps), j'ai raté ça.

Alors,

typeset -i i END # Let's be explicit
for ((i=1;i<=END;++i)); do echo $i; done

semble être le moyen le plus efficace sur le plan de la mémoire (il ne sera pas nécessaire d'allouer de la mémoire pour consommer seqde sortie, ce qui pourrait être un problème si END est très grand), bien que probablement pas le "plus rapide".

la question initiale

eschercycle a noté que le {une..b} La notation Bash fonctionne uniquement avec des littéraux; vrai, selon le manuel de Bash. On peut surmonter cet obstacle avec un seul (interne) fork() sans exec() (comme c'est le cas avec l'appel seq, ce qui étant une autre image nécessite un fork + exec):

for i in $(eval echo "{1..$END}"); do

Tous les deux eval et echo sont Bash builtins, mais un fork() est requis pour la substitution de commande (le $(…) construction).


160
2017-10-04 02:38



Voici pourquoi l'expression originale n'a pas fonctionné.

De homme bash:

L'expansion de l'orthèse est réalisée avant   toute autre expansion, et tout   caractères spéciaux à d'autres   les expansions sont conservées dans le   résultat. C'est strictement textuel. Frapper   ne s'applique pas de syntaxe   interprétation dans le contexte de   l'expansion ou le texte entre le   bretelles.

Alors, expansion de l'accolade est quelque chose fait au début comme une opération de macro purement textuelle, avant l'expansion des paramètres.

Les shells sont des hybrides hautement optimisés entre les macro-processeurs et des langages de programmation plus formels. Afin d'optimiser les cas d'utilisation typiques, le langage est rendu plus complexe et certaines limitations sont acceptées.

Recommandation

Je suggère de coller avec Posix1 fonctionnalités. Cela signifie utiliser for i in <list>; do, si la liste est déjà connue, sinon, utilisez while ou seq, un péché:

#!/bin/sh

limit=4

i=1; while [ $i -le $limit ]; do
  echo $i
  i=$(($i + 1))
done
# Or -----------------------
for i in $(seq 1 $limit); do
  echo $i
done


1. Bash est un super shell et je l'utilise de manière interactive, mais je ne mets pas de bash-ismes dans mes scripts. Les scripts peuvent nécessiter un shell plus rapide, plus sécurisé, plus intégré. Ils peuvent avoir besoin de fonctionner sur tout ce qui est installé en tant que / bin / sh, et il y a ensuite tous les arguments standards habituels. Rappelles toi shellshock, alias bashdoor? 


81
2018-04-19 22:32



La façon POSIX

Si vous vous souciez de la portabilité, utilisez le exemple de la norme POSIX:

i=2
end=5
while [ $i -le $end ]; do
    echo $i
    i=$(($i+1))
done

Sortie:

2
3
4
5

Les choses qui sont ne pas POSIX:


43
2017-07-12 07:54



Une autre couche d'indirection:

for i in $(eval echo {1..$END}); do
    ∶

28
2018-03-14 19:51



Vous pouvez utiliser

for i in $(seq $END); do echo $i; done

21
2017-10-04 01:41



Si vous utilisez BSD / OS X, vous pouvez utiliser jot au lieu de seq:

for i in $(jot $END); do echo $i; done

18
2018-03-15 23:29



Cela fonctionne bien dans bash:

END=5
i=1 ; while [[ $i -le $END ]] ; do
    echo $i
    ((i = i + 1))
done

13
2017-10-04 01:42



Si vous en avez besoin préfixe que vous pourriez aimer ce

 for ((i=7;i<=12;i++)); do echo `printf "%2.0d\n" $i |sed "s/ /0/"`;done

cela donnera

07
08
09
10
11
12

11
2018-06-03 20:14



Je sais que cette question concerne bashmais - juste pour le compte rendu - ksh93 est plus intelligent et l'implémente comme prévu:

$ ksh -c 'i=5; for x in {1..$i}; do echo "$x"; done'
1
2
3
4
5
$ ksh -c 'echo $KSH_VERSION'
Version JM 93u+ 2012-02-29

$ bash -c 'i=5; for x in {1..$i}; do echo "$x"; done'
{1..5}

6
2017-09-19 12:33