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 la documentation de Bash ):
for i in {1..5}; do echo $i; done
Qui donne:
1
2
3
4
5
Pourtant, comment puis-je remplacer l'un des points de terminaison de plage par une variable? Cela ne fonctionne pas:
END=5
for i in {1..$END}; do echo $i; done
Qui imprime:
{1..5}
for i in {01..10}; do echo $i; done
donnerait des nombres comme01, 02, 03, ..., 10
.myarray=('a' 'b' 'c'); for i in ${!myarray[@]}; do echo $i; done
(notez le point d'exclamation). C'est plus précis que la question d'origine, mais cela pourrait aider. Voir les extensions de paramètres bash{jpg,png,gif}
qui ne sont pas directement traitées ici, bien que la réponse soit identique. Voir Expansion Brace avec variable? [duplicate] qui est marqué comme un double de celui-ci.Réponses:
edit: je préfère
seq
les autres méthodes car je m'en souviens;)la source
seq $END
cela suffirait, car la valeur par défaut est de partir de 1. Deman seq
: "Si FIRST ou INCREMENT est omis, il est par défaut 1".La
seq
méthode est la plus simple, mais Bash a une évaluation arithmétique intégrée.La
for ((expr1;expr2;expr3));
construction fonctionne commefor (expr1;expr2;expr3)
en C et dans des langages similaires, et comme dans d'autres((expr))
cas, Bash les traite comme de l'arithmétique.la source
seq
. Utilise le!#!/bin/bash
la première ligne de votre script. wiki.ubuntu.com/…discussion
L'utilisation
seq
est très bien, comme l'a suggéré Jiaaro. Pax Diablo a suggéré une boucle 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 bogue typique dans l'implémentation de la boucle et a également laissé entendre que puisqu'ili
s'agit d'une variable de texte, des conversions continues de nombres en va-et-vient sont effectuées avec un ralentissement associé.arithmétique entière
Il s'agit d'une version améliorée de la boucle Bash:
Si la seule chose que nous voulons est la
echo
, alors nous pourrions écrireecho $((i++))
.L'éphémient m'a appris quelque chose: Bash autorise les
for ((expr;expr;expr))
constructions. Comme je n'ai jamais lu toute la page de manuel de Bash (comme je l'ai fait avec laksh
page de manuel Korn shell ( ), et c'était il y a longtemps), j'ai raté cela.Donc,
semble être le moyen le plus économe en mémoire (il ne sera pas nécessaire d'allouer de la mémoire pour consommer
seq
la sortie de, ce qui pourrait être un problème si END est très volumineux), bien que ce ne soit probablement pas le «plus rapide».la question initiale
eschercycle a noté que la notation { a .. b } Bash ne fonctionne qu'avec des littéraux; vrai, conformément au manuel de Bash. On peut surmonter cet obstacle avec un seul (interne)
fork()
sans unexec()
(comme c'est le cas avec les appelsseq
, qui étant une autre image nécessite un fork + exec):Les deux
eval
etecho
sont des commandes intégrées Bash, mais unfork()
est requis pour la substitution de commande (la$(…)
construction).la source
for ((i=$1;i<=$2;++i)); do echo $i; done
dans un script fonctionne très bien pour moi sur bash v.4.1.9, donc je ne vois pas de problème avec les arguments de la ligne de commande. Voulez-vous dire autre chose?time for i in $(seq 100000); do :; done
est beaucoup plus rapide!Voici pourquoi l'expression originale n'a pas fonctionné.
De l' homme bash :
Ainsi, l' expansion d'accolade est quelque chose qui se fait tôt 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 les 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.
Je suggère de rester avec les fonctionnalités de Posix 1 . Cela signifie utiliser
for i in <list>; do
, si la liste est déjà connue, sinon, utiliserwhile
ouseq
, comme dans:1. Bash est un excellent 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 shell plus rapide, plus sécurisé, plus intégré. Ils peuvent avoir besoin de s'exécuter sur tout ce qui est installé sous / bin / sh, et puis il y a tous les arguments pro-standards habituels. Rappelez-vous shellshock, alias bashdoor?
la source
seq
de grandes plages. Par exemple,echo {1..1000000} | wc
révèle que l'écho produit 1 ligne, un million de mots et 6 888 896 octets. Essayerseq 1 1000000 | wc
donne un million de lignes, un million de mots et 6 888 896 octets et est également plus de sept fois plus rapide, tel que mesuré par latime
commande.while
méthode POSIX dans ma réponse: stackoverflow.com/a/31365662/895245 Mais content que vous soyez d'accord :-)La façon POSIX
Si vous vous souciez de la portabilité, utilisez l' exemple de la norme POSIX :
Production:
Choses qui ne sont pas POSIX:
(( ))
sans dollar, bien que ce soit une extension courante comme mentionné par POSIX lui-même .[[
.[
suffit ici. Voir aussi: Quelle est la différence entre les crochets simples et doubles dans Bash?for ((;;))
seq
(GNU Coreutils){start..end}
, et cela ne peut pas fonctionner avec des variables comme mentionné dans le manuel de Bash .let i=i+1
: POSIX 7 2. Le langage de commande shell ne contient pas le motlet
et il échoue surbash --posix
4.3.42le dollar
i=$i+1
pourrait être nécessaire, mais je ne suis pas sûr. L'expansion arithmétique POSIX 7 2.6.4 dit:mais le lire littéralement n'implique pas que se
$((x+1))
développe puisquex+1
n'est pas une variable.la source
x
, et non à l'expression entière.$((x + 1))
est très bien.seq
(BSDseq
vous permet de définir une chaîne de terminaison de séquence avec-t
), FreeBSD et NetBSD ont égalementseq
depuis 9.0 et 3.0, respectivement.$((x+1))
et$((x + 1))
parse exactement le même, que lorsque l'analyseur tokenizesx+1
il sera divisé en 3 jetons:x
,+
et1
.x
n'est pas un jeton numérique valide, mais c'est un jeton de nom de variable valide, maisx+
ne l'est pas, d'où la division.+
est un jeton d'opérateur arithmétique valide, mais+1
ne l'est pas, donc le jeton y est de nouveau divisé. Et ainsi de suite.Une autre couche d'indirection:
la source
Vous pouvez utiliser
la source
Si vous en avez besoin, préférez-vous
cela donnera
la source
printf "%02d\n" $i
plus simple que çaprintf "%2.0d\n" $i |sed "s/ /0/"
?Si vous êtes sur BSD / OS X, vous pouvez utiliser jot au lieu de seq:
la source
Cela fonctionne bien dans
bash
:la source
echo $((i++))
fonctionne et le combine sur une seule ligne.bash
, nous pouvons simplement fairewhile [[ i++ -le "$END" ]]; do
pour faire l'incrémentation (post-) dans le testJ'ai combiné quelques idées ici et mesuré les performances.
TL; DR Points à retenir:
seq
et{..}
sont vraiment rapidesfor
et leswhile
boucles sont lentes$( )
est lentfor (( ; ; ))
les boucles sont plus lentes$(( ))
est encore plus lentCe ne sont pas des conclusions . Vous devriez regarder le code C derrière chacun d'eux pour tirer des conclusions. C'est plus sur la façon dont nous avons tendance à utiliser chacun de ces mécanismes pour boucler le code. La plupart des opérations individuelles sont suffisamment proches pour avoir la même vitesse que cela n'aura pas d'importance dans la plupart des cas. Mais un mécanisme comme de
for (( i=1; i<=1000000; i++ ))
nombreuses opérations comme vous pouvez le voir visuellement. C'est aussi beaucoup plus d'opérations par boucle que vous n'en obtenezfor i in $(seq 1 1000000)
. Et cela peut ne pas être évident pour vous, c'est pourquoi faire des tests comme celui-ci est précieux.Démos
la source
$(seq)
c'est à peu près la même vitesse que{a..b}
. De plus, chaque opération prend environ le même temps, ajoute donc environ 4 μs à chaque itération de la boucle pour moi. Ici, une opération est un écho dans le corps, une comparaison arithmétique, un incrément, etc. Est-ce surprenant? Peu importe combien de temps il faut à l'attirail de la boucle pour faire son travail - le temps d'exécution sera probablement dominé par le contenu de la boucle.Je sais que cette question concerne
bash
, mais - pour mémoire -ksh93
est plus intelligente et la met en œuvre comme prévu:la source
C'est une autre façon:
la source
Si vous voulez rester aussi proche que possible de la syntaxe des accolades, essayez la
range
fonction de bash-tricks 'range.bash
.Par exemple, tous les éléments suivants feront exactement la même chose que
echo {1..10}
:Il essaie de prendre en charge la syntaxe bash native avec le moins de "gotchas" possible: non seulement les variables sont prises en charge, mais le comportement souvent indésirable des plages non valides fournies sous forme de chaînes (par exemple
for i in {1..a}; do echo $i; done
) est également empêché.Les autres réponses fonctionneront dans la plupart des cas, mais elles présentent toutes au moins l'un des inconvénients suivants:
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 qu'il fonctionne dans ce cas. Omniprésent ou non, c'est beaucoup plus sur lequel s'appuyer que le langage Bash lui-même.{a..z}
; l'expansion de l'accolade sera. La question portait sur les gammes de nombres , cependant, c'est donc un problème.{1..10}
syntaxe de la plage étendue accolades, donc les programmes qui utilisent les deux peuvent être un peu plus difficiles à lire.$END
variable n'est pas un "serre-livre" de plage valide pour l'autre côté de la plage. SiEND=a
, par exemple, une erreur ne se produit pas et la valeur textuelle{1..a}
est répercutée. Il s'agit également du comportement par défaut de Bash - il est souvent inattendu.Avertissement: je suis l'auteur du code lié.
la source
Remplacez
{}
par(( ))
:Rendements:
la source
Ce sont tous bien, mais seq est censé être obsolète et la plupart ne fonctionnent qu'avec des plages numériques.
Si vous placez votre boucle for entre guillemets doubles, les variables de début et de fin 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 \ pour qu'il ne soit PAS évalué avant d'être envoyé au sous-shell.Cette sortie peut également être affectée à une variable:
La seule "surcharge" que cela devrait générer devrait être la deuxième instance de bash, donc elle devrait convenir aux opérations intensives.
la source
Si vous faites des commandes shell et que vous (comme moi) avez un fétiche pour le pipelining, celui-ci est bon:
seq 1 $END | xargs -I {} echo {}
la source
Il existe de nombreuses façons de le faire, mais celles que je préfère sont indiquées ci-dessous
En utilisant
seq
Commande complète
seq first incr last
Exemple:
Uniquement avec le premier et le dernier:
Seulement avec dernier:
En utilisant
{first..last..incr}
Ici premier et dernier sont obligatoires et incr est facultatif
Utiliser juste le premier et le dernier
Utilisation de incr
Vous pouvez l'utiliser même pour les personnages comme ci-dessous
la source
si vous ne voulez pas utiliser '
seq
' ou 'eval
' oujot
ou le format d'expansion arithmétique par exemple.for ((i=1;i<=END;i++))
, ou d'autres boucles, par exemple.while
, et vous ne voulez pas 'printf
' et heureux 'echo
' seulement, alors cette solution de contournement simple pourrait correspondre à votre budget:a=1; b=5; d='for i in {'$a'..'$b'}; do echo -n "$i"; done;' echo "$d" | bash
PS: Mon bash n'a pas
seq
de commande ' ' de toute façon.Testé sur Mac OSX 10.6.8, Bash 3.2.48
la source
Cela fonctionne dans Bash et Korn, peut également passer d'un nombre supérieur à un nombre inférieur. Probablement pas plus rapide ou plus joli mais fonctionne assez bien. Gère également les négatifs.
la source