Frage Wie iteriere ich über einen Bereich von Zahlen, die durch Variablen in Bash definiert sind?


Wie wiederhole ich über einen Bereich von Zahlen in Bash, wenn der Bereich durch eine Variable gegeben ist?

Ich weiß, dass ich das tun kann (genannt "Sequenzausdruck" in der Bash) Dokumentation):

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

Was gibt:

1
  2
  3
  4
  5

Wie kann ich jedoch einen der Bereichsendpunkte durch eine Variable ersetzen? Dies funktioniert nicht:

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

Welche Drucke:

{1..5}


1056
2017-10-04 01:38


Ursprung


Antworten:


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

edit: Ich bevorzuge seq über die anderen Methoden, weil ich mich daran erinnern kann;)


1186
2017-10-04 01:41



Das seq Methode ist die einfachste, aber Bash hat eine eingebaute arithmetische Auswertung.

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

Das for ((expr1;expr2;expr3)); Konstrukt funktioniert genauso for (expr1;expr2;expr3) in C und ähnlichen Sprachen und wie andere ((expr)) Fälle behandelt Bash sie als arithmetisch.


309
2017-10-04 21:43



Diskussion

Verwenden seq ist in Ordnung, wie Jiaaro vorgeschlagen hat. Pax Diablo schlug eine Bash-Schleife vor, um den Aufruf eines Subprozesses zu vermeiden, mit dem zusätzlichen Vorteil, speicherfreundlicher zu sein, wenn $ END zu groß ist. Zathrus entdeckte einen typischen Bug in der Loop-Implementierung und deutete dies auch an i Ist eine Textvariable, werden Continuous-Conversions-Hin-und-Her-Zahlen mit einer zugehörigen Verlangsamung ausgeführt.

Ganzzahlarithmetik

Dies ist eine verbesserte Version der Bash-Schleife:

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

Wenn das einzige, was wir wollen, ist das echoDann könnten wir schreiben echo $((i++)).

Ephemie hat mir etwas beigebracht: Bash erlaubt for ((expr;expr;expr)) konstruiert. Da ich nie die ganze Manpage für Bash gelesen habe (wie ich es mit der Korn Shell gemacht habe (ksh) Man Seite, und das war vor langer Zeit), das habe ich vermisst.

Damit,

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

scheint der speichereffizienteste Weg zu sein (es wird nicht notwendig sein, Speicher zum Konsumieren zuzuweisen seqAusgabe, die ein Problem sein könnte, wenn END sehr groß ist), obwohl wahrscheinlich nicht die "schnellste".

die erste Frage

eschercycle hat festgestellt, dassein..b} Bash Notation funktioniert nur mit Literalen; wahr, entsprechend dem Bash Handbuch. Man kann dieses Hindernis mit einem einzigen (internen) überwinden fork() ohne ein exec() (wie beim Anruf seq, welches ein anderes Bild benötigt, benötigt einen fork + exec):

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

Beide eval und echo sind Bash Builtins, aber a fork() wird für die Befehlsersetzung benötigt (der $(…) bauen).


160
2017-10-04 02:38



Hier ist der Grund, warum der ursprüngliche Ausdruck nicht funktioniert hat.

Von Mann bash:

Klammerexpansion wird vorher durchgeführt   irgendwelche anderen Erweiterungen und irgendwelche   Zeichen speziell für andere   Erweiterungen bleiben in der   Ergebnis. Es ist streng textlich. Bash   wendet keine Syntax an   Interpretation zum Kontext von   die Erweiterung oder der Text zwischen den   Hosenträger.

Damit, Klammer Erweiterung ist etwas früher als eine rein textuelle Makrooperation getan, vorher Parametererweiterung.

Shells sind hochoptimierte Hybride zwischen Makroprozessoren und formaleren Programmiersprachen. Um die typischen Anwendungsfälle zu optimieren, wird die Sprache etwas komplexer und einige Einschränkungen werden akzeptiert.

Empfehlung

Ich würde vorschlagen, mit Posix zu bleiben1 Eigenschaften. Dies bedeutet Verwendung for i in <list>; doWenn die Liste bereits bekannt ist, verwenden Sie andernfalls while oder seq, wie in:

#!/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 ist eine großartige Shell und ich benutze sie interaktiv, aber ich setze keine Bash-Ismen in meine Skripte. Skripte benötigen möglicherweise eine schnellere Shell, eine sicherere, eine eingebettetere. Sie müssen möglicherweise auf dem laufen, was als / bin / sh installiert ist, und dann gibt es alle üblichen Pro-Standard-Argumente. Merken Neurose, aka Bashdoor? 


81
2018-04-19 22:32



Der POSIX-Weg

Wenn Sie auf Portabilität Wert legen, verwenden Sie die Beispiel aus dem POSIX-Standard:

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

Ausgabe:

2
3
4
5

Dinge, die sind nicht POSIX:


43
2017-07-12 07:54



Eine andere Ebene der Indirektion:

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

28
2018-03-14 19:51



Sie können verwenden

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

21
2017-10-04 01:41



Wenn Sie BSD / OS X verwenden, können Sie jot anstelle von seq verwenden:

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

18
2018-03-15 23:29



Dies funktioniert gut in bash:

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

13
2017-10-04 01:42