Jak iterować w zakresie liczb w Bash, gdy zakres jest podany przez zmienną?
Wiem, że mogę to zrobić (zwane „wyrażeniem sekwencji” w dokumentacji Bash ):
for i in {1..5}; do echo $i; done
Co daje:
1
2
3
4
5
Jak jednak zastąpić jeden z punktów końcowych zakresu zmienną? To nie działa:
END=5
for i in {1..$END}; do echo $i; done
Które wydruki:
{1..5}
for i in {01..10}; do echo $i; done
dałoby liczby takie jak01, 02, 03, ..., 10
.myarray=('a' 'b' 'c'); for i in ${!myarray[@]}; do echo $i; done
(zwróć uwagę na wykrzyknik). To jest bardziej szczegółowe niż pierwotne pytanie, ale może pomóc. Zobacz rozszerzenia parametrów bash{jpg,png,gif}
które nie są tutaj bezpośrednio omówione, chociaż odpowiedź będzie identyczna. Zobacz rozwinięcie nawiasu klamrowego ze zmienną? [duplikat], który jest oznaczony jako duplikat tego.Odpowiedzi:
edycja: Wolę
seq
inne metody, bo tak naprawdę pamiętam;)źródło
seq $END
by wystarczyło, ponieważ domyślnie zaczyna się od 1. Odman seq
: „Jeśli PIERWSZY lub INCREMENT zostanie pominięty, domyślnie wynosi 1”.seq
Metoda jest najprostsza, ale Bash ma wbudowany oceny arytmetycznej.for ((expr1;expr2;expr3));
Konstrukt działa podobnie jakfor (expr1;expr2;expr3)
w C i podobnych języków, jak i innych((expr))
przypadkach Bash traktuje je jako arytmetyka.źródło
seq
. Użyj tego!#!/bin/bash
pierwszą linię skryptu. wiki.ubuntu.com/...dyskusja
Używanie
seq
jest w porządku, jak sugerował Jiaaro. Pax Diablo zasugerował pętlę Bash, aby uniknąć wywołania podprocesu, z dodatkową zaletą polegającą na tym, że jest bardziej przyjazna dla pamięci, jeśli $ END jest zbyt duże. Zathrus zauważył typowy błąd w implementacji pętli, a także zasugerował, że skoroi
jest zmienną tekstową, ciągłe konwersje na liczby tam i z powrotem są wykonywane z powiązanym spowolnieniem.arytmetyka liczb całkowitych
To jest ulepszona wersja pętli Bash:
Jeśli jedyne, czego chcemy
echo
, to moglibyśmy pisaćecho $((i++))
.ephemient nauczył mnie czegoś: Bash pozwala na
for ((expr;expr;expr))
konstrukcje. Ponieważ nigdy nie czytałem całej strony podręcznika użytkownika dla Basha (tak jak zrobiłem to zeksh
stroną podręcznika powłoki Korn ( ) i to było dawno temu), przegapiłem to.Więc,
wydaje się być najbardziej wydajnym sposobem na pamięć (nie będzie konieczne przydzielanie pamięci na zużycie
seq
danych wyjściowych, co może być problemem, jeśli END jest bardzo duży), chociaż prawdopodobnie nie jest to „najszybszy”.pierwsze pytanie
eschercycle zauważył, że notacja Bash { a .. b } działa tylko z literałami; prawda, zgodnie z instrukcją Bash. Można pokonać tę przeszkodę jednym (wewnętrznym)
fork()
bezexec()
(jak w przypadku wywoływaniaseq
, który jest innym obrazem wymaga fork + exec):Zarówno
eval
iecho
są atakujących builtins, alefork()
są wymagane do zastąpienia polecenia (The$(…)
konstrukcie).źródło
for ((i=$1;i<=$2;++i)); do echo $i; done
w skrypcie działa dobrze na bash v.4.1.9, więc nie widzę problemu z argumentami wiersza poleceń. Masz na myśli coś innego?time for i in $(seq 100000); do :; done
jest znacznie szybszy!Oto dlaczego oryginalne wyrażenie nie działało.
From man bash :
Tak więc interpretacja nawiasu klamrowego jest wykonywana wcześnie jako czysto tekstowa operacja makra, przed rozszerzeniem parametru.
Powłoki to wysoce zoptymalizowane hybrydy między makroprocesorami a bardziej formalnymi językami programowania. Aby zoptymalizować typowe przypadki użycia, język jest bardziej złożony i pewne ograniczenia są akceptowane.
Sugeruję pozostanie przy funkcjach Posix 1 . Oznacza to użycie
for i in <list>; do
, jeśli lista jest już znana, w przeciwnym razie użyjwhile
lubseq
, jak w:1. Bash jest świetną powłoką i używam jej interaktywnie, ale nie umieszczam bash-isms w moich skryptach. Skrypty mogą wymagać szybszej powłoki, bezpieczniejszej, bardziej osadzonej. Może być konieczne uruchomienie na czymkolwiek zainstalowanym jako / bin / sh, a następnie są wszystkie zwykłe argumenty pro-standardów. Pamiętasz shellshock, alias bashdoor?
źródło
seq
dużych zakresów. Np.echo {1..1000000} | wc
Ujawnia, że echo produkuje 1 linię, milion słów i 6 888 896 bajtów. Próbaseq 1 1000000 | wc
daje milion wierszy, milion słów i 6 888 896 bajtów, a także jest ponad siedem razy szybsza, zgodnie ztime
komendą.while
metodzie POSIX : stackoverflow.com/a/31365662/895245 Ale cieszę się, że się zgadzasz :-)Sposób POSIX
Jeśli zależy Ci na przenośności, skorzystaj z przykładu standardu POSIX :
Wynik:
Rzeczy, które nie są POSIX:
(( ))
bez dolara, chociaż jest to powszechne rozszerzenie, o którym wspomina sam POSIX .[[
.[
wystarczy tutaj. Zobacz także: Jaka jest różnica między pojedynczymi i podwójnymi nawiasami kwadratowymi w Bash?for ((;;))
seq
(GNU Coreutils){start..end}
, i to nie może działać ze zmiennymi, jak wspomniano w podręczniku Bash .let i=i+1
: POSIX 7 2. Język poleceń powłoki nie zawiera słowalet
i nie działa wbash --posix
4.3.42dolara
i=$i+1
może być wymagane, ale nie jestem pewien. POSIX 7 2.6.4 Rozbudowa arytmetyczna mówi:ale czytanie tego dosłownie nie oznacza, że się
$((x+1))
rozszerza, ponieważx+1
nie jest zmienną.źródło
x
nie do całego wyrażenia.$((x + 1))
jest w porządku.seq
(BSDseq
pozwala ustawić ciąg zakończenia sekwencji za pomocą-t
), FreeBSD i NetBSD mają również odpowiednioseq
od 9.0 i 3.0.$((x+1))
i$((x + 1))
parse dokładnie tak samo, jak wtedy, gdy parser tokenizesx+1
zostanie on podzielony na 3 tokeny:x
,+
, i1
.x
nie jest poprawnym tokenem numerycznym, ale jest prawidłowym tokenem nazwy zmiennej, alex+
nie jest, dlatego podział.+
jest poprawnym tokenem operatora arytmetycznego, ale+1
nie jest, więc token jest tam ponownie dzielony. I tak dalej.Kolejna warstwa pośrednictwa:
źródło
Możesz użyć
źródło
Jeśli potrzebujesz prefiksu, może ci się to spodobać
to da
źródło
printf "%02d\n" $i
byłoby łatwiej niżprintf "%2.0d\n" $i |sed "s/ /0/"
?Jeśli korzystasz z BSD / OS X, możesz użyć jot zamiast seq:
źródło
Działa to dobrze w
bash
:źródło
echo $((i++))
działa i łączy go w jedną linię.bash
, możemy po prostu zrobićwhile [[ i++ -le "$END" ]]; do
(przyrostowy) przyrost w teściePołączyłem tutaj kilka pomysłów i zmierzyłem wydajność.
TL; DR Na wynos:
seq
i{..}
są naprawdę szybkiefor
awhile
pętle są wolne$( )
jest wolnyfor (( ; ; ))
pętle są wolniejsze$(( ))
jest jeszcze wolniejszyTo nie są wnioski . Będziesz musiał spojrzeć na kod C za każdym z nich, aby wyciągnąć wnioski. To więcej o tym, jak zwykle używamy każdego z tych mechanizmów do zapętlania kodu. Większość pojedynczych operacji jest wystarczająco zbliżona do tej samej prędkości, że w większości przypadków nie będzie to miało znaczenia. Ale takim mechanizmem
for (( i=1; i<=1000000; i++ ))
jest wiele operacji, które można zobaczyć wizualnie. Jest to również o wiele więcej operacji na pętlę niż z niej otrzymujeszfor i in $(seq 1 1000000)
. I może to nie być dla ciebie oczywiste, dlatego wykonywanie takich testów jest tak cenne.Dema
źródło
$(seq)
jest o tej samej prędkości co{a..b}
. Ponadto każda operacja trwa mniej więcej w tym samym czasie, więc dodaje mi około 4 μs do każdej iteracji pętli. Tutaj operacja to echo w ciele, porównanie arytmetyczne, przyrost itd. Czy któreś z tych rzeczy jest zaskakujące? Kogo to obchodzi, ile czasu zajmuje wykonanie niezbędnych czynności w pętli - środowisko wykonawcze prawdopodobnie będzie zdominowane przez zawartość pętli.Wiem, że chodzi o to pytanie
bash
, ale - dla przypomnienia -ksh93
jest mądrzejsze i implementuje je zgodnie z oczekiwaniami:źródło
To jest inny sposób:
źródło
Jeśli chcesz pozostać jak najbliżej składni wyrażenia nawiasów klamrowych, wypróbuj
range
funkcję z bash-tricks 'range.bash
.Na przykład wszystkie poniższe czynności będą wykonywać dokładnie to samo, co
echo {1..10}
:Próbuje obsługiwać natywną składnię bash z możliwie najmniejszą liczbą „gotchas”: nie tylko obsługiwane są zmienne, ale również
for i in {1..a}; do echo $i; done
zapobiega się często niepożądanemu zachowaniu nieprawidłowych zakresów dostarczanych jako ciągi znaków (np. ).Inne odpowiedzi będą działać w większości przypadków, ale wszystkie mają co najmniej jedną z następujących wad:
seq
binarny, który musi być zainstalowany, aby go użyć, musi zostać załadowany przez bash i musi zawierać oczekiwany program, aby działał w tym przypadku. Wszechobecny czy nie, na czym można polegać bardziej niż sam język Bash.{a..z}
; rozwinie nawias klamrowy. Pytanie dotyczyło jednak zakresów liczb , więc jest to spór.{1..10}
składni zakresu rozszerzonego nawiasami, więc programy używające obu mogą być nieco trudniejsze do odczytania.$END
zmienna nie jest poprawnym „bookend” zakresu dla drugiej strony zakresu. JeśliEND=a
na przykład błąd nie wystąpi, a pełna wartość{1..a}
zostanie wyświetlona. Jest to również domyślne zachowanie Bash - często jest to nieoczekiwane.Oświadczenie: Jestem autorem powiązanego kodu.
źródło
Wymienić
{}
z(( ))
:Wydajność:
źródło
Wszystkie są ładne, ale sekwencja jest podobno przestarzała i większość działa tylko z zakresami liczbowymi.
Jeśli umieścisz pętlę for w podwójnych cudzysłowach, zmienne początkowa i końcowa zostaną usunięte podczas echa łańcucha i możesz wysłać łańcuch z powrotem do BASH w celu wykonania.
$i
musi być poprzedzony znakiem \, więc NIE jest analizowany przed wysłaniem do podpowłoki.Dane wyjściowe można również przypisać do zmiennej:
Jedynym „narzutem”, jaki powinno to wygenerować, powinno być drugie wystąpienie bash, więc powinno być odpowiednie do intensywnych operacji.
źródło
Jeśli wykonujesz polecenia powłoki i masz (podobnie jak ja) fetysz do potoków, ten jest dobry:
seq 1 $END | xargs -I {} echo {}
źródło
Można to zrobić na wiele sposobów, jednak te, które preferuję, podano poniżej
Za pomocą
seq
Pełna komenda
seq first incr last
Przykład:
Tylko z pierwszym i ostatnim:
Tylko z ostatnim:
Za pomocą
{first..last..incr}
Tutaj pierwsze i ostatnie są obowiązkowe, a incr jest opcjonalne
Używanie tylko pierwszego i ostatniego
Korzystanie z incr
Możesz użyć tego nawet dla postaci takich jak poniżej
źródło
jeśli nie chcesz używać „
seq
” lub „eval
”jot
lub arytmetycznego formatu rozszerzenia np.for ((i=1;i<=END;i++))
lub inne pętle np.while
, a ty nie chcesz tylkoprintf
i z przyjemnościąecho
, to proste obejście może pasować do Twojego budżetu:a=1; b=5; d='for i in {'$a'..'$b'}; do echo -n "$i"; done;' echo "$d" | bash
PS: Mój bash i tak nie ma
seq
polecenia „ ”.Testowane na Mac OSX 10.6.8, Bash 3.2.48
źródło
Działa to w Bash i Korn, może również przechodzić z wyższych na niższe liczby. Prawdopodobnie nie jest najszybszy ani najładniejszy, ale działa wystarczająco dobrze. Obsługuje również negatywy.
źródło