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}

Author: codeforester, 2008-10-04

17 answers

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

Edit: wolę seq nad innymi metodami, bo tak naprawdę to pamiętam;)

 1226
Author: Jiaaro,
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ę.

 324
Author: ephemient,
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 $(…)).

 161
Author: tzot,
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?
 84
Author: DigitalRoss,
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:

 46
Author: Ciro Santilli 新疆改造中心 六四事件 法轮功,
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
    ∶
 28
Author: bobbogo,
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
 21
Author: Peter Hoffmann,
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
 18
Author: jefeveizen,
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
 13
Author: paxdiablo,
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
 11
Author: hossbear,
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}
 7
Author: Adrian Frühwirth,
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
 6
Author: Jahid,
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.

 5
Author: SuperBob,
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
 5
Author: BashTheKeyboard,
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 {}

 4
Author: Alex Spangher,
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śli END=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.

 2
Author: Zac B,
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"
}
 0
Author: Ethan Post,
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