Wie iteriere ich in Bash über einen Zahlenbereich, wenn der Bereich durch eine Variable angegeben wird?
Ich weiß, dass ich dies tun kann (in der Bash- Dokumentation als "Sequenzausdruck" bezeichnet ):
for i in {1..5}; do echo $i; done
Welches gibt:
1
2
3
4
5
Wie kann ich jedoch einen der Bereichsendpunkte durch eine Variable ersetzen? Das funktioniert nicht:
END=5
for i in {1..$END}; do echo $i; done
Welche Drucke:
{1..5}
for i in {01..10}; do echo $i; done
würde Zahlen wie geben01, 02, 03, ..., 10
.myarray=('a' 'b' 'c'); for i in ${!myarray[@]}; do echo $i; done
(Beachten Sie das Ausrufezeichen). Es ist spezifischer als die ursprüngliche Frage, könnte aber helfen. Siehe Bash-Parametererweiterungen{jpg,png,gif}
die hier nicht direkt angesprochen werden, obwohl die Antwort identisch ist. Siehe Klammererweiterung mit Variable? [Duplikat], das als Duplikat von diesem markiert ist.Antworten:
edit: Ich bevorzuge
seq
die anderen Methoden, weil ich mich tatsächlich daran erinnern kann;)quelle
seq $END
dies ausreichen würde, da der Standardwert bei 1 beginnt. Vonman seq
: "Wenn FIRST oder INCREMENT weggelassen wird, wird standardmäßig 1 verwendet."Die
seq
Methode ist die einfachste, aber Bash verfügt über eine integrierte arithmetische Auswertung.Das
for ((expr1;expr2;expr3));
Konstrukt funktioniert genau wiefor (expr1;expr2;expr3)
in C und ähnlichen Sprachen, und wie in anderen((expr))
Fällen behandelt Bash sie als arithmetisch.quelle
seq
. Benutze es!#!/bin/bash
die erste Zeile Ihres Skripts erstellen . wiki.ubuntu.com/…Diskussion
Die Verwendung
seq
ist in Ordnung, wie Jiaaro vorgeschlagen hat. Pax Diablo schlug eine Bash-Schleife vor, um das Aufrufen eines Unterprozesses zu vermeiden, mit dem zusätzlichen Vorteil, speicherfreundlicher zu sein, wenn $ END zu groß ist. Zathrus entdeckte einen typischen Fehler in der Schleifenimplementierung und deutete auch an, dassi
fortlaufende Konvertierungen hin und her mit einer damit verbundenen Verlangsamung durchgeführt werden , da es sich um eine Textvariable handelt.Ganzzahlige Arithmetik
Dies ist eine verbesserte Version der Bash-Schleife:
Wenn das einzige, was wir wollen, das ist
echo
, dann könnten wir schreibenecho $((i++))
.Ephemient hat mir etwas beigebracht: Bash erlaubt
for ((expr;expr;expr))
Konstrukte. Da ich nie die gesamte Manpage für Bash gelesen habe (wie ich es mit derksh
Manpage von Korn shell ( ) getan habe , und das ist lange her), habe ich das verpasst.Damit,
scheint der speichereffizienteste Weg zu sein (es ist nicht erforderlich, Speicher für die
seq
Ausgabe des Verbrauchs zuzuweisen , was ein Problem sein könnte, wenn END sehr groß ist), obwohl dies wahrscheinlich nicht der „schnellste“ ist.die erste Frage
eschercycle stellte fest, dass die Bash-Notation { a .. b } nur mit Literalen funktioniert. wahr, entsprechend dem Bash-Handbuch. Man kann dieses Hindernis mit einem einzigen (internen)
fork()
ohne ein überwindenexec()
(wie es beim Aufrufen der Fall istseq
, da für ein anderes Bild eine Gabel + Exec erforderlich ist):Beide
eval
undecho
sind Bash-Builtins, aberfork()
für die Befehlssubstitution (das$(…)
Konstrukt) ist a erforderlich .quelle
for ((i=$1;i<=$2;++i)); do echo $i; done
In einem Skript funktioniert es gut mit Bash v.4.1.9, daher sehe ich kein Problem mit Befehlszeilenargumenten. Meinst du etwas anderes?time for i in $(seq 100000); do :; done
ist viel schneller!Hier ist, warum der ursprüngliche Ausdruck nicht funktioniert hat.
Von Mann schlagen :
Die Klammererweiterung wird also früh als rein textuelle Makrooperation vor der Parametererweiterung durchgeführt.
Shells sind hochoptimierte Hybride zwischen Makroprozessoren und formaleren Programmiersprachen. Um die typischen Anwendungsfälle zu optimieren, wird die Sprache etwas komplexer gestaltet und einige Einschränkungen werden akzeptiert.
Ich würde vorschlagen, bei den Funktionen von Posix 1 zu bleiben . Dies bedeutet, dass Sie
for i in <list>; do
, wenn die Liste bereits bekannt ist, andernfalls verwendenwhile
oderseq
wie in:1. Bash ist eine großartige Shell und ich benutze sie interaktiv, aber ich füge keine Bash-Ismen in meine Skripte ein. Skripte benötigen möglicherweise eine schnellere, sicherere und eingebettete Shell. Sie müssen möglicherweise auf dem als / bin / sh installierten Computer ausgeführt werden, und dann gibt es alle üblichen Pro-Standard-Argumente. Erinnerst du dich an Shellshock, auch bekannt als Bashdoor?
quelle
seq
großen Bereichen nicht viel Speicher spart .echo {1..1000000} | wc
Zeigt beispielsweise, dass das Echo 1 Zeile, eine Million Wörter und 6.888.896 Bytes erzeugt. Der Versuchseq 1 1000000 | wc
ergibt eine Million Zeilen, eine Million Wörter und 6.888.896 Bytes und ist auch mehr als siebenmal schneller, gemessen mit demtime
Befehl.while
Methode bereits in meiner Antwort erwähnt: stackoverflow.com/a/31365662/895245 Aber ich bin froh, dass Sie zustimmen :-)Der POSIX-Weg
Wenn Sie sich für Portabilität interessieren, verwenden Sie das Beispiel aus dem POSIX-Standard :
Ausgabe:
Dinge, die nicht POSIX sind:
(( ))
ohne Dollar, obwohl es eine übliche Erweiterung ist, wie von POSIX selbst erwähnt .[[
.[
ist genug hier. Siehe auch: Was ist der Unterschied zwischen einfachen und doppelten eckigen Klammern in Bash?for ((;;))
seq
(GNU Coreutils){start..end}
, und das kann nicht mit Variablen funktionieren, wie im Bash-Handbuch erwähnt .let i=i+1
: POSIX 7 2. Die Shell-Befehlssprache enthält das Wort nichtlet
und schlägt ambash --posix
4.3.42 fehlDer Dollar
i=$i+1
könnte erforderlich sein, aber ich bin mir nicht sicher. POSIX 7 2.6.4 Arithmetische Erweiterung sagt:Aber es wörtlich zu lesen bedeutet nicht, dass es sich
$((x+1))
ausdehnt, dax+1
es keine Variable ist.quelle
x
nicht auf den gesamten Ausdruck.$((x + 1))
ist in Ordnung.seq
(mit BSDseq
können Sie eine Sequenzabschlusszeichenfolge festlegen-t
). Sie haben jedochseq
seit 9.0 bzw. 3.0 auch.$((x+1))
und$((x + 1))
Parse genau das gleiche, wie wenn der Parser tokenizesx+1
wird es aufgeteilt in 3 - Token sein:x
,+
, und1
.x
ist kein gültiges numerisches Token, aber es ist ein gültiges Variablennamen-Token,x+
ist es jedoch nicht, daher die Aufteilung.+
ist ein gültiges arithmetisches Operator-Token,+1
ist es aber nicht, daher wird das Token dort erneut aufgeteilt. Und so weiter.Eine weitere Indirektionsebene:
quelle
Sie können verwenden
quelle
Wenn Sie es Präfix benötigen, als Sie dies mögen könnten
das wird nachgeben
quelle
printf "%02d\n" $i
einfacher alsprintf "%2.0d\n" $i |sed "s/ /0/"
?Wenn Sie mit BSD / OS X arbeiten, können Sie jot anstelle von seq verwenden:
quelle
Dies funktioniert gut in
bash
:quelle
echo $((i++))
arbeitet und kombiniert es in einer Zeile.bash
können wir einfachwhile [[ i++ -le "$END" ]]; do
das (Post-) Inkrement im Test machenIch habe einige der Ideen hier kombiniert und die Leistung gemessen.
TL; DR Takeaways:
seq
und{..}
sind wirklich schnellfor
undwhile
Schleifen sind langsam$( )
ist langsamfor (( ; ; ))
Schleifen sind langsamer$(( ))
ist noch langsamerDies sind keine Schlussfolgerungen . Sie müssten sich den C-Code hinter jedem dieser Codes ansehen, um Schlussfolgerungen zu ziehen. Hier geht es mehr darum, wie wir dazu neigen, jeden dieser Mechanismen zum Durchlaufen von Code zu verwenden. Die meisten Einzeloperationen sind nahe genug an der Geschwindigkeit, die in den meisten Fällen keine Rolle spielt. Aber ein Mechanismus wie
for (( i=1; i<=1000000; i++ ))
ist viele Operationen, wie Sie visuell sehen können. Es sind auch viel mehr Operationen pro Schleife, als Sie erhaltenfor i in $(seq 1 1000000)
. Und das ist für Sie vielleicht nicht offensichtlich, weshalb es wertvoll ist, solche Tests durchzuführen.Demos
quelle
$(seq)
es ungefähr so schnell wie{a..b}
. Außerdem dauert jede Operation ungefähr dieselbe Zeit, sodass für jede Iteration der Schleife ungefähr 4 μs hinzugefügt werden. Hier ist eine Operation ein Echo im Körper, ein arithmetischer Vergleich, ein Inkrement usw. Ist irgendetwas davon überraschend? Wen interessiert es, wie lange es dauert, bis die Schleifenutensilien ihre Arbeit erledigt haben - die Laufzeit wird wahrscheinlich vom Inhalt der Schleife dominiert.Ich weiß, dass es um diese Frage geht
bash
, aber - nur zur Veranschaulichung - sieksh93
ist schlauer und setzt sie wie erwartet um:quelle
Dies ist ein anderer Weg:
quelle
Wenn Sie so nah wie möglich an der Syntax des Klammerausdrucks bleiben möchten, probieren Sie die
range
Funktion aus Bash-Tricks ausrange.bash
.Zum Beispiel machen alle folgenden Aktionen genau dasselbe wie
echo {1..10}
:Es wird versucht, die native Bash-Syntax mit möglichst wenigen "Fallstricken" zu unterstützen: Es werden nicht nur Variablen unterstützt, sondern auch das häufig unerwünschte Verhalten ungültiger Bereiche, die als Zeichenfolgen (z. B.
for i in {1..a}; do echo $i; done
) angegeben werden, wird verhindert.Die anderen Antworten funktionieren in den meisten Fällen, haben jedoch alle mindestens einen der folgenden Nachteile:
seq
ist eine Binärdatei, die installiert sein muss, um verwendet zu werden, von bash geladen werden muss und das erwartete Programm enthalten muss, damit sie in diesem Fall funktioniert. Allgegenwärtig oder nicht, das ist viel mehr als nur die Bash-Sprache.{a..z}
. Klammer Erweiterung wird. Die Frage betraf jedoch Zahlenbereiche , daher ist dies ein Streitpunkt.{1..10}
Syntax für den erweiterten Bereich, daher sind Programme, die beide verwenden, möglicherweise etwas schwieriger zu lesen.$END
Ereignissen, wenn die Variable kein gültiger Bereich "Buchstütze" für die andere Seite des Bereichs ist. WennEND=a
zum Beispiel kein Fehler auftritt und der wörtliche Wert wiedergegeben{1..a}
wird. Dies ist auch das Standardverhalten von Bash - es ist nur oft unerwartet.Haftungsausschluss: Ich bin der Autor des verlinkten Codes.
quelle
Ersetzen
{}
durch(( ))
:Ausbeuten:
quelle
Diese sind alle nett, aber seq ist angeblich veraltet und die meisten arbeiten nur mit numerischen Bereichen.
Wenn Sie Ihre for-Schleife in doppelte Anführungszeichen setzen, werden die Start- und Endvariablen beim Echo der Zeichenfolge dereferenziert, und Sie können die Zeichenfolge zur Ausführung direkt an BASH zurücksenden.
$i
muss mit \ 's maskiert werden, damit es NICHT ausgewertet wird, bevor es an die Subshell gesendet wird.Dieser Ausgang kann auch einer Variablen zugeordnet werden:
Der einzige "Overhead", den dies erzeugen sollte, sollte die zweite Instanz von Bash sein, damit sie für intensive Operationen geeignet ist.
quelle
Wenn Sie Shell-Befehle ausführen und (wie ich) einen Fetisch für Pipelining haben, ist dieser gut:
seq 1 $END | xargs -I {} echo {}
quelle
Es gibt viele Möglichkeiten, dies zu tun, aber die, die ich bevorzuge, sind unten angegeben
Verwenden von
seq
Voller Befehl
seq first incr last
Beispiel:
Nur mit erstem und letztem:
Nur mit letzter:
Verwenden von
{first..last..incr}
Hier sind zuerst und zuletzt obligatorisch und inkr ist optional
Verwenden Sie nur zuerst und zuletzt
Mit Inkr
Sie können dies auch für Zeichen wie unten verwenden
quelle
Wenn Sie nicht das Format '
seq
' oder 'eval
' oderjot
oder das arithmetische Erweiterungsformat verwenden möchten, z.for ((i=1;i<=END;i++))
oder andere Schleifen, z.while
, und Sie möchten nicht nurprintf
glücklich seinecho
, dann könnte diese einfache Problemumgehung zu Ihrem Budget passen:a=1; b=5; d='for i in {'$a'..'$b'}; do echo -n "$i"; done;' echo "$d" | bash
PS: Meine Bash hat
seq
sowieso keinen ' ' Befehl.Getestet unter Mac OSX 10.6.8, Bash 3.2.48
quelle
Dies funktioniert in Bash und Korn, kann auch von höheren zu niedrigeren Zahlen gehen. Wahrscheinlich nicht am schnellsten oder am schönsten, funktioniert aber gut genug. Behandelt auch Negative.
quelle