Cum pot repeta peste o serie de numere în Bash atunci când intervalul este dat de o variabilă?
Știu că pot face acest lucru (numit "secvența de exprimare" în Bash documentația):
for i in {1..5}; do echo $i; done
Care vă oferă:
1
2
3
4
5
Și totuși, cum pot înlocui oricare din gama de obiective cu o variabilă? Asta nu't de lucru:
END=5
for i in {1..$END}; do echo $i; done
Care imprimă:
{1..5}
Anii urm
metodă este cea mai simplă, dar Bash-a construit-în aritmetică de evaluare.
END=5
for ((i=1;i<=END;i++)); do
echo $i
done
# ==> outputs 1 2 3 4 5 on separate lines
Anii pentru ((expr1;expr2;expr3));
construct funcționează la fel ca for (expr1;expr2;expr3) în C și similare de limbi, și, ca și alte
((expr))` cazuri, Bash îi tratează ca pe aritmetică.
Folosind urm
este bine, ca Jiaaro sugerat. Pax Diablo sugerat un Bash buclă pentru a evita apelarea unui subproces, cu avantajul suplimentar de a fi mai multă memorie prietenos, dacă $END este prea mare. Zathrus observat un bug tipic în bucla de punere în aplicare, și, de asemenea, a sugerat că, deoarece " eu " este un text variabilă, continuă conversii to-and-fro numere sunt efectuate cu un asociat lent în jos.
Aceasta este o versiune îmbunătățită a Bash bucla:
typeset -i i END
let END=5 i=1
while ((i<=END)); do
echo $i
…
let i++
done
Dacă singurul lucru pe care îl vreau este "echo", atunci am putea scrie echo $((i++))
.
ephemient m-a învățat ceva: Bash permite pentru ((expr;expr;expr))
constructe. De când am'am mai citit toată pagina man pentru Bash (cum am'am făcut cu Korn shell (ksh
) om pagină, și asta a fost o lungă perioadă de timp în urmă), am ratat asta.
Deci,
typeset -i i END # Let's be explicit
for ((i=1;i<=END;++i)); do echo $i; done
pare a fi cel mai de memorie-eficient (nu o't fi necesar să se aloce memorie pentru a consuma urm
's de ieșire, care ar putea fi o problemă dacă SFÂRȘITUL este foarte mare), deși, probabil, nu "cel mai rapid".
eschercycle remarcat faptul că {a..b} Bash notație funcționează numai cu literali; adevărat, în consecință, pentru a Bash manual. Se poate depăși acest obstacol, cu un singur (interne) fork()
fără exec()
(cum este cazul cu apel urm
, care, fiind o altă imagine necesită o furculita+exec):
for i in $(eval echo "{1..$END}"); do
Ambele "eval" și "echo", sunt Bash builtins, dar un fork()este necesar pentru comanda de substituție (a
$(...)` construct).
Iată de ce inițial expresia n't de lucru.
Din om bash:
Bretele de expansiune este realizată înainte de orice alte extinderi, precum și orice caractere speciale la alte extinderi sunt păstrate în rezultat. Este strict textuale. Bash nu se aplică nici sintactice interpretarea contextului de extinderea sau textul între aparat dentar.
Deci, bretele de expansiune este ceva de făcut mai devreme ca un pur textuale macro funcționare, înainte de a parametru de expansiune.
Scoici sunt extrem de optimizat hibrizi între macro procesoare și mai mult formală a limbajelor de programare. În scopul de a optimiza utilizarea tipică cazuri, limbajul este făcut mai degrabă mai complexe și unele limitări sunt acceptate.
Recomandarea
Aș sugera lipit cu Posix1 caracteristici. Aceasta înseamnă utilizarea pentru am în , dacă lista este deja cunoscut, în caz contrar, utilizați
timpsau
urm`, ca în:
#!/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
La POSIX mod
Dacă îți pasă de portabilitate, utilizați exemplu de standardul POSIX:
i=2
end=5
while [ $i -le $end ]; do
echo $i
i=$(($i+1))
done
Ieșire:
2
3
4
5
Lucruri care nu sunt ** POSIX:
(( ))
fără dolar, deși este o comună în extensie după cum sa menționat de către POSIX sine.[[
. [
este suficient aici. Vezi de asemenea și: https://stackoverflow.com/questions/13542832/bash-if-difference-between-square-brackets-and-double-square-bracketsurm
(GNU Coreutils){start..end}
, și că nu poate lucra cu variabile așa cum am menționat de Bash manual.: [POSIX 7 2. Shell Command Language](http://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_06_04) nu conțin cuvântul
săși nu pe bash --posix
4.3.42i=$i+1
ar putea fi necesar, dar nu'm nu sunt sigur. POSIX 7 2.6.4 Aritmetică Expansiune spune:în Cazul în care shell variabila x contine o valoare care formează un valabilă pe întreg constantă, opțional, inclusiv un lider plus sau cu semnul minus, atunci aritmetică extinderi "$((x))" și "$(($x))" se va întoarce la aceeași valoare.
dar citind-o, literalmente, asta nu înseamnă că $((x+1))
se extinde din x+1
nu este o variabilă.
Am'am combinat câteva dintre ideile de aici și măsurată performanța.
urm
și {..}
sunt foarte repede$( )
este lent$(( ))
este chiar mai lentNu sunt concluzii. Tu ar trebui să se uite la codul C în spatele fiecare dintre acestea pentru a trage concluzii. Acest lucru este mai mult despre cum am tendința de a folosi fiecare dintre aceste mecanisme pentru looping peste cod. Cele mai multe operații singulare sunt destul de aproape pentru a fi la aceeasi viteza cu care se's nu va conta în cele mai multe cazuri. Dar un mecanism pentru (( i=1; i<=1000000; i++ ))e mai multe operații după cum puteți vedea vizual. Este, de asemenea, mult mai multe operațiuni **pe bucla** decat pentru am în $(seq 1 1000000)
. Și care nu poate fi evident pentru tine, care este motivul pentru care fac teste ca acest lucru este valoros.
# show that seq is fast
$ time (seq 1 1000000 | wc)
1000000 1000000 6888894
real 0m0.227s
user 0m0.239s
sys 0m0.008s
# show that {..} is fast
$ time (echo {1..1000000} | wc)
1 1000000 6888896
real 0m1.778s
user 0m1.735s
sys 0m0.072s
# Show that for loops (even with a : noop) are slow
$ time (for i in {1..1000000} ; do :; done | wc)
0 0 0
real 0m3.642s
user 0m3.582s
sys 0m0.057s
# show that echo is slow
$ time (for i in {1..1000000} ; do echo $i; done | wc)
1000000 1000000 6888896
real 0m7.480s
user 0m6.803s
sys 0m2.580s
$ time (for i in $(seq 1 1000000) ; do echo $i; done | wc)
1000000 1000000 6888894
real 0m7.029s
user 0m6.335s
sys 0m2.666s
# show that C-style for loops are slower
$ time (for (( i=1; i<=1000000; i++ )) ; do echo $i; done | wc)
1000000 1000000 6888896
real 0m12.391s
user 0m11.069s
sys 0m3.437s
# show that arithmetic expansion is even slower
$ time (i=1; e=1000000; while [ $i -le $e ]; do echo $i; i=$(($i+1)); done | wc)
1000000 1000000 6888896
real 0m19.696s
user 0m18.017s
sys 0m3.806s
$ time (i=1; e=1000000; while [ $i -le $e ]; do echo $i; ((i=i+1)); done | wc)
1000000 1000000 6888896
real 0m18.629s
user 0m16.843s
sys 0m3.936s
$ time (i=1; e=1000000; while [ $i -le $e ]; do echo $((i++)); done | wc)
1000000 1000000 6888896
real 0m17.012s
user 0m15.319s
sys 0m3.906s
# even a noop is slow
$ time (i=1; e=1000000; while [ $((i++)) -le $e ]; do :; done | wc)
0 0 0
real 0m12.679s
user 0m11.658s
sys 0m1.004s
Stiu ca aceasta intrebare este despre bash
, dar - doar pentru înregistrare - ksh93
este mai inteligent și pune în aplicare așa cum era de așteptat:
$ 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}
Dacă vrei să rămâi cât mai aproape posibil de bretele-sintaxa expresie, încerca "gama" funcția de bash-trucuri' raza.bash`.
De exemplu, toate din următoarele va face exact același lucru ca echo {1..10}
:
source range.bash
one=1
ten=10
range {$one..$ten}
range $one $ten
range {1..$ten}
range {1..10}
Încearcă să suport nativ bash sintaxa cu cât mai puține "chestii" posibil: nu numai sunt variabile suportate, dar de multe ori-comportament nedorit de invalid variază fi furnizate ca siruri de caractere (de exemplu, pentru am în {1..o}; do echo $i done`) este împiedicat fel de bine.
Alte răspunsuri vor lucra în cele mai multe cazuri, dar toate au cel puțin una din următoarele dezavantaje:
urm
este un binar care trebuie să fie instalat pentru a fi utilizate, trebuie să fie încărcate de bash, și trebuie să conțină programul vă așteptați, pentru a funcționa în acest caz. Omniprezente sau nu, ca's mult mai mult decât să se bazeze pe doar Bash limba în sine.{o..z}
; bretele de expansiune va. Întrebarea a fost despre gamele de numbers, deși, astfel încât acesta este un subterfugiu.{1..10}
brace-a extins gama de sintaxă, deci programele care folosesc ambele pot fi un pic mai greu de citit.$END
variabila nu este un interval valid "bookend" pentru cealaltă parte a intervalului. Dacă END=o
, de exemplu, o eroare nu va avea loc și textual valoare {1..o}
va fi repetat. Acesta este comportamentul implicit de Bash, la fel de bine-este doar de multe ori neașteptate.Disclaimer: eu sunt autorul legate de cod.
Toate acestea sunt frumos, dar seq se presupune că este învechită și cele mai lucra doar cu intervalele numerice.
Dacă vă încadrați pentru buclă în ghilimele, începutul și sfârșitul variabile va fi dereferenced când ecou șir, și puteți nava șir înapoi la BASH pentru execuție. $i
are nevoie de a fi scăpat cu \'s, astfel că NU este evaluat înainte de a fi trimis la subshell.
RANGE_START=a
RANGE_END=z
echo -e "for i in {$RANGE_START..$RANGE_END}; do echo \\${i}; done" | bash
Această ieșire poate fi atribuit unei variabile:
VAR=`echo -e "for i in {$RANGE_START..$RANGE_END}; do echo \\${i}; done" | bash`
Singurul "regie" acest lucru ar trebui să genereze ar trebui să fie cel de-al doilea exemplu de bash așa că ar trebui să fie potrivite pentru operațiuni intensive.
Există multe modalități de a face acest lucru, cu toate acestea cele pe care le prefer este dat de mai jos
urm
Sinopsis din
om urm
$ seq [-w] [-f format] [-s string] [-t string] [first [incr]] last
Sintaxa
Full command
urm prima încer ultima
Exemplu:
$ seq 1 2 10
1 3 5 7 9
Numai cu primul și ultimul:
$ seq 1 5
1 2 3 4 5
Doar cu ultima:
$ seq 5
1 2 3 4 5
{prima..ultima..incr}
Aici primul și ultimul sunt obligatorii și incr este opțională
Folosind doar prima și ultima
$ echo {1..5}
1 2 3 4 5
Folosind incr
$ echo {1..10..2}
1 3 5 7 9
Puteți folosi acest lucru chiar și pentru personajele de mai jos
$ echo {a..z}
a b c d e f g h i j k l m n o p q r s t u v w x y z
Acest lucru funcționează în Bash și Korn, de asemenea, pot merge de la mare la numerele mai mici. Probabil nu mai rapidă sau mai frumos, dar funcționează destul de bine. Mânere negativele.
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=${1}
e=${2}
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"
}
dacă tu nu't să utilizați 'urm
' sau ' "eval" ' sau jot
sau aritmetică extinderea formatului de ex. for ((i=1;i<=END;i++)), sau alte bucle de exemplu.
în timp ce` și nu't să ' "printf" ' si fericit sa '"echo" ' numai că, atunci această soluție simplă s-ar potrivi bugetul dvs.:
a=1; b=5; d='pentru că în {'$o'..'$b'}; do echo -n "$i"; facut;' echo "$d" | bash
PS: Mi bash nu't am 'urm
' comanda oricum.
Testat pe Mac OSX 10.6.8, Bash 3.2.48