W jaki sposób mogę iterować zakres liczb zdefiniowanych przez zmienne w Bash?
Jak iterować zakres 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
Jednak, Jak mogę zastąpić którekolwiek z punktów końcowych zakresu zmienną? To nie działa:
END=5
for i in {1..$END}; do echo $i; done
Który drukuje:
{1..5}
17 answers
for i in $(seq 1 $END); do echo $i; done
Edit: wolę seq
nad innymi metodami, bo tak naprawdę to pamiętam;)
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-04-23 00:41:06
Metoda seq
jest najprostsza, ale Bash ma wbudowaną ocenę arytmetyczną.
END=5
for ((i=1;i<=END;i++)); do
echo $i
done
# ==> outputs 1 2 3 4 5 on separate lines
Konstrukcja for ((expr1;expr2;expr3));
działa podobnie jak for (expr1;expr2;expr3)
w języku C i podobnych językach, a podobnie jak inne przypadki ((expr))
, Bash traktuje je jako arytmetykę.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-09-03 04:23:45
Dyskusja
Używanie seq
jest w porządku, jak zasugerowała Jiaaro. Pax Diablo zasugerował pętlę Bash, aby uniknąć wywołania podprocesu, z dodatkową zaletą bycia bardziej przyjaznym dla pamięci, jeśli $END jest zbyt duży. Zathrus zauważył typowy błąd w implementacji pętli, a także zasugerował, że ponieważ i
jest zmienną tekstową, ciągłe konwersje na liczby iz powrotem są wykonywane z powiązanym zwolnieniem.
Arytmetyka całkowita
Jest to ulepszona wersja Pętla Bash:
typeset -i i END
let END=5 i=1
while ((i<=END)); do
echo $i
…
let i++
done
Jeśli chcemy tylko echo
, to możemy napisać echo $((i++))
.
Ephemient nauczył mnie czegoś: Bash pozwala for ((expr;expr;expr))
konstruować. Ponieważ nigdy nie przeczytałem całej strony podręcznika dla Basha (tak jak zrobiłem z powłoką Korn (ksh
), a to było dawno temu), przegapiłem to.
Więc,
typeset -i i END # Let's be explicit
for ((i=1;i<=END;++i)); do echo $i; done
Wydaje się być najbardziej efektywnym sposobem pamięci (nie będzie konieczne przydzielanie pamięci do zużywania wyjścia seq
, co może być problemem, jeśli koniec jest bardzo duży), choć chyba nie "najszybszy".
Pierwsze pytanie
Eschercycle zauważył, że {a .. b } notacja Bash działa tylko z literałami; true, zgodnie z instrukcją Bash. Można pokonać tę przeszkodę pojedynczym (wewnętrznym) fork()
bez exec()
(Jak to ma miejsce w przypadku wywołania seq
, które jako inny obraz wymaga forka+exec):
for i in $(eval echo "{1..$END}"); do
Zarówno eval
jak i echo
to Bash builtins, ale fork()
to wymagane do zastąpienia polecenia (konstrukcja $(…)
).
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:18:21
Oto dlaczego oryginalne wyrażenie nie działało.
From man bash :
Rozprężanie klamry wykonuje się przed wszelkie inne rozszerzenia, oraz wszelkie znaki specjalne dla innych ekspansje są zachowane w wynik. Jest ściśle tekstowy. Bash nie stosuje żadnej składni interpretacja w kontekście rozszerzenie lub tekst pomiędzy Aparat ortodontyczny.
Tak więc, Rozszerzanie nawiasów jest czymś dokonanym na początku jako czysto tekstowy działanie makr, przed rozszerzeniem parametru .
Powłoki są wysoce zoptymalizowanymi hybrydami między procesorami makro i bardziej formalnymi językami programowania. W celu optymalizacji typowych przypadków użycia, język jest bardziej złożony i akceptowane są pewne ograniczenia.
Zalecenie
Proponuję trzymać się Posix1 funkcje. Oznacza to użycie for i in <list>; do
, Jeśli lista jest już znana, w przeciwnym razie użyj while
lub seq
, jako 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 jest świetną powłoką i używam jej interaktywnie, ale nie umieszczam bash-isms w moich skryptach. Skrypty mogą potrzebować szybszej powłoki, bezpieczniejszej, bardziej osadzonej. Być może będą musieli działać na tym, co jest zainstalowane jako/bin / sh, a potem są wszystkie zwykłe argumenty pro-Standard. Pamiętasz shellshock, aka bashdoor?
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-02-13 23:32:46
Sposób POSIX
Jeśli zależy ci na przenośności, użyj przykładu ze standardu POSIX:
i=2
end=5
while [ $i -le $end ]; do
echo $i
i=$(($i+1))
done
Wyjście:
2
3
4
5
Rzeczy, które są Nie POSIX:
-
(( ))
bez dolara, chociaż jest to wspólne rozszerzenie , o którym wspomina sam POSIX. -
[[
. Wystarczy. Zobacz też: Jaka jest różnica między pojedynczym i podwójnym nawiasem kwadratowym w Bash? for ((;;))
-
seq
(GNU Coreutils) -
{start..end}
, a to nie może działać ze zmiennymi wymienionymi w podręczniku Bash. -
let i=i+1
: POSIX 7 2. Język poleceń powłoki Nie zawiera słowalet
i nie działa nabash --posix
4.3.42 -
Dolar może być wymagany, ale nie jestem pewien. POSIX 7 2.6.4 rozszerzenie arytmetyczne mówi:
Jeśli zmienna shell x zawiera wartość, która tworzy poprawną stałą całkowitą, opcjonalnie zawierającą wiodący znak plus lub minus, wtedy rozszerzenia arytmetyczne " $((x)) "i" $(($x)) " zwrócą tę samą wartość.
Ale odczytanie go dosłownie nie oznacza, że
$((x+1))
rozszerza się, ponieważx+1
nie jest zmienną.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:02:48
Kolejna warstwa indrection:
for i in $(eval echo {1..$END}); do
∶
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-03-14 19:51:36
Możesz użyć
for i in $(seq $END); do echo $i; done
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-10-04 01:41:23
Jeśli jesteś na BSD / OS X możesz użyć jot zamiast seq:
for i in $(jot $END); do echo $i; done
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-03-15 23:29:16
To działa dobrze w bash
:
END=5
i=1 ; while [[ $i -le $END ]] ; do
echo $i
((i = i + 1))
done
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-04-01 08:21:17
If you need it prefix than you might like this
for ((i=7;i<=12;i++)); do echo `printf "%2.0d\n" $i |sed "s/ /0/"`;done
To da
07
08
09
10
11
12
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-06-03 20:14:38
Wiem, że to pytanie dotyczy bash
, ale-tak dla przypomnienia- ksh93
jest mądrzejsze i wdraża je zgodnie z oczekiwaniami:
$ 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}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-09-19 12:56:05
To jest inny sposób:
end=5
for i in $(bash -c "echo {1..${end}}"); do echo $i; done
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-07-12 12:36:51
Wszystkie są ładne, ale SEQ jest podobno przestarzały i większość działa tylko z zakresami liczbowymi.
Jeśli zamkniesz pętlę for w podwójnych cudzysłowach, zmienne początek i koniec zostaną dereferowane podczas echo łańcucha i możesz wysłać go z powrotem do BASH w celu wykonania. $i
musi być zabezpieczony przez \ ' s, więc nie jest oceniany przed wysłaniem do podshell.
RANGE_START=a
RANGE_END=z
echo -e "for i in {$RANGE_START..$RANGE_END}; do echo \\${i}; done" | bash
To wyjście może być również przypisane do zmiennej:
VAR=`echo -e "for i in {$RANGE_START..$RANGE_END}; do echo \\${i}; done" | bash`
Jedynym "napowietrznym" tym powinien generować powinien być drugą instancją bash, więc powinien być odpowiedni do intensywnych operacji.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-08-17 10:27:34
Zastąp {}
przez (( ))
:
tmpstart=0;
tmpend=4;
for (( i=$tmpstart; i<=$tmpend; i++ )) ; do
echo $i ;
done
0
1
2
3
4
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-03-12 01:27:06
Jeśli wykonujesz polecenia powłoki i (jak ja) masz Fetysz do pipeliningu, ten jest dobry:
seq 1 $END | xargs -I {} echo {}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-03-27 19:32:05
Jeśli chcesz być jak najbliżej składni wyrażenia nawiasowego, wypróbuj range
Funkcja z bash-tricks' range.bash
.
Na przykład, wszystkie z poniższych działań zrobią dokładnie to samo, co echo {1..10}
:
source range.bash
one=1
ten=10
range {$one..$ten}
range $one $ten
range {1..$ten}
range {1..10}
Próbuje obsługiwać natywną składnię bash z jak najmniejszą liczbą "gotchas": nie tylko obsługiwane są zmienne, ale również zapobiega często niepożądanemu zachowaniu nieprawidłowych zakresów dostarczanych jako ciągi znaków (np. for i in {1..a}; do echo $i; done
).
The inne odpowiedzi będą działać w większości przypadków, ale wszystkie mają co najmniej jedną z następujących wad:]}
- wiele z nich używa podshellów , które mogą zaszkodzić wydajności i może nie być możliwe w niektórych systemach. Wiele z nich opiera się na zewnętrznych programach. Even {[5] } jest plikiem binarnym, który musi być zainstalowany, aby mógł być używany, musi być załadowany przez Basha i musi zawierać program, którego oczekujesz, aby działał w tym przypadku. Wszechobecne czy nie, to o wiele więcej do polegaj nie tylko na samym języku Bash.
- rozwiązania, które używają tylko natywnej funkcjonalności Bash, takie jak @ephemient, nie będą działać na zakresach alfabetycznych, takich jak
{a..z}
. Pytanie dotyczyło zakresów liczb , więc jest to sprzeczka. - większość z nich nie jest wizualnie podobna do składni
{1..10}
brace-expanded range, więc programy, które używają obu mogą być nieco trudniejsze do odczytania. - @bobbogo ' s answer uses some of the familiar syntax, ale robi coś nieoczekiwanego, jeśli zmienna
$END
nie jest poprawnym zakresem "bookend" dla drugiej strony zakresu. JeśliEND=a
, na przykład, błąd nie wystąpi, a wartość słowna{1..a}
zostanie wyświetlona. Jest to również domyślne zachowanie basha-jest to po prostu często nieoczekiwane.
Zastrzeżenie: jestem autorem linked code.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-07-27 13:38:13
To działa w Bash i Korn, również może przejść od wyższych do niższych numerów. Prawdopodobnie nie najszybszy lub najładniejszy, ale działa wystarczająco dobrze. Obsługuje też negatywy.
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"
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-04-11 02:37:36