How do I iterate over a range of numbers defined b

2018-12-31 01:45发布

How do I iterate over a range of numbers in Bash when the range is given by a variable?

I know I can do this (called "sequence expression" in the Bash documentation):

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

Which gives:

1
2
3
4
5

Yet, how can I replace either of the range endpoints with a variable? This doesn't work:

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

Which prints:

{1..5}

17条回答
若你有天会懂
2楼-- · 2018-12-31 02:16

I know this question is about bash, but - just for the record - ksh93 is smarter and implements it as expected:

$ 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}
查看更多
弹指情弦暗扣
3楼-- · 2018-12-31 02:17
for i in $(seq 1 $END); do echo $i; done

edit: I prefer seq over the other methods because I can actually remember it ;)

查看更多
只靠听说
4楼-- · 2018-12-31 02:18

If you want to stay as close as possible to the brace-expression syntax, try out the range function from bash-tricks' range.bash.

For example, all of the following will do the exact same thing as echo {1..10}:

source range.bash
one=1
ten=10

range {$one..$ten}
range $one $ten
range {1..$ten}
range {1..10}

It tries to support the native bash syntax with as few "gotchas" as possible: not only are variables supported, but the often-undesirable behavior of invalid ranges being supplied as strings (e.g. for i in {1..a}; do echo $i; done) is prevented as well.

The other answers will work in most cases, but they all have at least one of the following drawbacks:

  • Many of them use subshells, which can harm performance and may not be possible on some systems.
  • Many of them rely on external programs. Even seq is a binary which must be installed to be used, must be loaded by bash, and must contain the program you expect, for it to work in this case. Ubiquitous or not, that's a lot more to rely on than just the Bash language itself.
  • Solutions that do use only native Bash functionality, like @ephemient's, will not work on alphabetic ranges, like {a..z}; brace expansion will. The question was about ranges of numbers, though, so this is a quibble.
  • Most of them aren't visually similar to the {1..10} brace-expanded range syntax, so programs that use both may be a tiny bit harder to read.
  • @bobbogo's answer uses some of the familiar syntax, but does something unexpected if the $END variable is not a valid range "bookend" for the other side of the range. If END=a, for example, an error will not occur and the verbatim value {1..a} will be echoed. This is the default behavior of Bash, as well--it is just often unexpected.

Disclaimer: I am the author of the linked code.

查看更多
余生请多指教
5楼-- · 2018-12-31 02:22

Replace {} with (( )):

tmpstart=0;
tmpend=4;

for (( i=$tmpstart; i<=$tmpend; i++ )) ; do 
echo $i ;
done

Yields:

0
1
2
3
4
查看更多
旧人旧事旧时光
6楼-- · 2018-12-31 02:26

This works fine in bash:

END=5
i=1 ; while [[ $i -le $END ]] ; do
    echo $i
    ((i = i + 1))
done
查看更多
回忆,回不去的记忆
7楼-- · 2018-12-31 02:30

Here is why the original expression didn't work.

From man bash:

Brace expansion is performed before any other expansions, and any characters special to other expansions are preserved in the result. It is strictly textual. Bash does not apply any syntactic interpretation to the context of the expansion or the text between the braces.

So, brace expansion is something done early as a purely textual macro operation, before parameter expansion.

Shells are highly optimized hybrids between macro processors and more formal programming languages. In order to optimize the typical use cases, the language is made rather more complex and some limitations are accepted.

Recommendation

I would suggest sticking with Posix1 features. This means using for i in <list>; do, if the list is already known, otherwise, use while or seq, as 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 is a great shell and I use it interactively, but I don't put bash-isms into my scripts. Scripts might need a faster shell, a more secure one, a more embedded-style one. They might need to run on whatever is installed as /bin/sh, and then there are all the usual pro-standards arguments. Remember shellshock, aka bashdoor?

查看更多
登录 后发表回答