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é "séquence d'expression" dans le Bash documentation ):

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

qui donne:

1

2

3

4

5

pourtant, comment puis-je remplacer l'un ou l'autre des paramètres de portée par une variable? Cela ne fonctionne pas:

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

qui imprime:

{1..5}

1094
demandé sur codeforester 2008-10-04 05:38:43

17 réponses

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

edit: je préfère seq sur les autres méthodes car je peux en souvenir ;)

1226
répondu Jiaaro 2016-04-23 00:41:06

la méthode seq est la plus simple, mais Bash a intégré l'évaluation arithmétique.

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 travaux tout comme for (expr1;expr2;expr3) en C et langues similaires, et comme d'autres ((expr)) cas, Bash les traite comme arithmétique.

324
répondu ephemient 2014-09-03 04:23:45

discussion

en utilisant seq est très bien, comme Jiaaro suggéré. Pax Diablo a suggéré une boucle de Bash pour éviter d'appeler un sous-processus, avec l'avantage supplémentaire d'être plus convivial en mémoire si $END est trop grand. Zathrus a repéré un bug typique dans l'implémentation de la boucle, et a aussi laissé entendre que puisque i est une variable texte, les conversions continues vers-et-vers les nombres sont effectuées avec un ralentissement associé.

entier arithmétique

c'est une version améliorée de la boucle de 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 le echo , alors nous pourrions écrire echo $((i++)) .

ephemient m'a appris quelque chose: Bash permet for ((expr;expr;expr)) constructions. Puisque je n'ai jamais lu la page de man entière pour Bash( comme j'ai fait avec la page de man Korn shell ( ksh ), et c'était il y a longtemps), j'ai manqué que.

,

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 de la mémoire (il ne sera pas nécessaire d'allouer la mémoire pour consommer la sortie de seq , qui pourrait être un problème si la fin est très grande), mais probablement pas le"plus rapide".

la question initiale

eschercycle a noté que le { a .. 1519380920 b 1519390920; vrai, en conséquence, pour le Bash manuel. On peut surmonter cet obstacle avec un simple (interne) fork() sans un exec() (comme c'est le cas avec l'appel seq , qui est une autre image nécessite une fourche+exec):

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

les deux eval et echo sont des constructions Bash, mais un fork() est nécessaire pour la substitution de commande (la $(…) construction).

161
répondu tzot 2017-05-23 12:18:21

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

à Partir de man bash :

l'expansion de L'attelle est effectuée avant toute autre expansion, et toute caractères spéciaux pour les autres les expansions sont préservées dans les résultat. Il est strictement textuelle. Bash n'applique pas de syntaxique l'interprétation au contexte de l'extension ou le texte entre accolade.

So, brace expansion est quelque chose fait très tôt comme une opération de macro purement textuelle, avant paramètre expansion.

Les Shells

sont des hybrides hautement optimisés entre Macro processeurs et langages de programmation plus formels. Afin d'optimiser les cas d'utilisation typiques, le langage est rendu un peu plus complexe et certaines limites sont acceptées.

recommandation

je suggère de coller avec les caractéristiques de Posix 1 . Cela signifie qu'il faut utiliser for i in <list>; do , si la liste est déjà connue, sinon, utiliser while ou seq , comme dans:

#!/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-isms dans mes scripts. Les Scripts peuvent avoir besoin d'un interpréteur de commandes plus rapide, plus sûr, plus intégré. Ils vous pourriez avoir besoin de lancer sur tout ce qui est installé comme /bin/sh, et puis il y a tous les arguments pro-standards habituels. Vous vous souvenez de shellshock, alias bashdoor?
84
répondu DigitalRoss 2017-02-13 23:32:46

POSIX façon

si vous vous souciez de la portabilité, utilisez l'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 pas POSIX:

46

une autre couche d'effet indirect:

for i in $(eval echo {1..$END}); do
    ∶
28
répondu bobbogo 2011-03-14 19:51:36

vous pouvez utiliser

for i in $(seq $END); do echo $i; done
21
répondu Peter Hoffmann 2008-10-04 01:41:23

si vous êtes sur BSD / OS X, vous pouvez utiliser jot au lieu de seq:

for i in $(jot $END); do echo $i; done
18
répondu jefeveizen 2011-03-15 23:29:16

cela fonctionne très bien dans bash :

END=5
i=1 ; while [[ $i -le $END ]] ; do
    echo $i
    ((i = i + 1))
done
13
répondu paxdiablo 2014-04-01 08:21:17

si vous avez besoin d'un préfixe que vous pourriez aimer ceci

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

qui donnera

07
08
09
10
11
12
11
répondu hossbear 2017-06-03 20:14:38

je sais que cette question concerne bash , mais - juste pour info - ksh93 est plus intelligent et le met en œuvre 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}
7
répondu Adrian Frühwirth 2013-09-19 12:56:05

C'est une autre façon:

end=5
for i in $(bash -c "echo {1..${end}}"); do echo $i; done
6
répondu Jahid 2015-07-12 12:36:51

ceux-ci sont tous beaux mais seq est supposé déprécié et la plupart ne fonctionnent avec des gammes numériques.

si vous enfermez votre boucle for dans des guillemets doubles, les variables start et end seront déréférencées lorsque vous faites écho à la chaîne, et vous pouvez renvoyer la chaîne directement à BASH pour exécution. $i doit être échappé avec \'S de sorte qu'il n'est pas évalué avant d'être envoyé à la subshell.

RANGE_START=a
RANGE_END=z
echo -e "for i in {$RANGE_START..$RANGE_END}; do echo \${i}; done" | bash

cette sortie peut aussi être assignée à un variable:

VAR=`echo -e "for i in {$RANGE_START..$RANGE_END}; do echo \${i}; done" | bash`

La seule "frais généraux" ce qui devrait générer devrait être la deuxième instance de bash de sorte qu'il devrait être adapté pour des opérations intensives.

5
répondu SuperBob 2011-08-17 10:27:34

remplacer {} par (( )) :

tmpstart=0;
tmpend=4;

for (( i=$tmpstart; i<=$tmpend; i++ )) ; do 
echo $i ;
done

Donne:

0
1
2
3
4
5
répondu BashTheKeyboard 2014-03-12 01:27:06

si vous faites des commandes shell et que vous (comme moi) avez un fétiche pour le pipelinage, celui-ci est bon:

seq 1 $END | xargs -I {} echo {}

4
répondu Alex Spangher 2017-03-27 19:32:05

si vous voulez rester aussi près que possible de la syntaxe de l'expression des attelles, essayez la fonction range de bash-tricks' range.bash .

par exemple, tout ce qui suit fera exactement la même chose que echo {1..10} :

source range.bash
one=1
ten=10

range {$one..$ten}
range $one $ten
range {1..$ten}
range {1..10}

il essaie de supporter la syntaxe de bash natif avec aussi peu de "gotchas" que possible: non seulement les variables sont supportées, mais le comportement souvent indésirable des Invalides les gammes fournies sous forme de chaînes (par exemple for i in {1..a}; do echo $i; done ) sont également empêchées.

les autres réponses fonctionneront dans la plupart des cas, mais elles présentent toutes au moins un des inconvénients suivants:

  • beaucoup d'entre eux utilisent subshells , qui peut performances de dommage et peut ne pas être possible sur certains systèmes.
  • beaucoup d'entre eux dépendent de programmes externes. Même seq est un binaire qui doit être installé pour être utilisé, doit être chargé par bash, et doit contenir le programme que vous attendez, pour que cela fonctionne dans ce cas. Omniprésent ou pas, c'est bien plus que le Bash lui-même.
  • les Solutions qui n'utilisent que la fonctionnalité native de Bash, comme @ephemient, ne fonctionneront pas sur les plages alphabétiques, comme {a..z} ; l'expansion de brace le fera. La question portait sur les fourchettes de nombres , mais, si c'est une argutie.
  • la plupart d'entre eux ne sont pas visuellement similaires à la syntaxe {1..10} brace-expanded range, de sorte que les programmes qui utilisent les deux peuvent être un tout petit peu plus difficile à lire.
  • la réponse de @bobbogo utilise une partie de la syntaxe familière, mais fait quelque chose d'inattendu si la variable $END n'est pas une plage valide "bookend" pour l'autre côté de la plage. Si END=a , par exemple, une erreur ne se produira pas et la valeur mot à mot {1..a} fera écho. C'est aussi le comportement par défaut de Bash, c'est juste souvent inattendu.

clause de non-responsabilité: je suis l'auteur du code lié.

2
répondu Zac B 2017-07-27 13:38:13

cela fonctionne à Bash et Korn, peut aussi aller de plus en plus bas nombres. Probablement pas le plus rapide ou le plus beau mais fonctionne assez bien. Poignées négatifs.

function num_range {
   # Return a range of whole numbers from beginning value to ending value.
   # >>> num_range start end
   # start: Whole number to start with.
   # end: Whole number to end with.
   typeset s e v
   s=
   e=
   if (( ${e} >= ${s} )); then
      v=${s}
      while (( ${v} <= ${e} )); do
         echo ${v}
         ((v=v+1))
      done
   elif (( ${e} < ${s} )); then
      v=${s}
      while (( ${v} >= ${e} )); do
         echo ${v}
         ((v=v-1))
      done
   fi
}

function test_num_range {
   num_range 1 3 | egrep "1|2|3" | assert_lc 3
   num_range 1 3 | head -1 | assert_eq 1
   num_range -1 1 | head -1 | assert_eq "-1"
   num_range 3 1 | egrep "1|2|3" | assert_lc 3
   num_range 3 1 | head -1 | assert_eq 3
   num_range 1 -1 | tail -1 | assert_eq "-1"
}
0
répondu Ethan Post 2018-04-11 02:37:36