How can I create an arithmetic loop in a POSIX shell script?
Solution 1
I have found useful information in Shellcheck.net wiki, I quote:
-
Bash¹:
for ((init; test; next)); do foo; done
-
POSIX:
: "$((init))" while [ "$((test))" -ne 0 ]; do foo; : "$((next))"; done
though beware that i++
is not POSIX so would have to be translated, for instance to i += 1
or i = i + 1
.
:
is a null command that always has a successful exit code. "$((expression))"
is an arithmetic expansion that is being passed as an argument to :
. You can assign to variables or do arithmetic/comparisons in the arithmetic expansion.
So the above script in the question can be POSIX-wise re-written using those rules like this:
#!/bin/sh
: "$((i=1))"
while [ "$((i != 10))" -ne 0 ]
do
echo "$i"
: "$((i = i + 1))"
done
Though here, you can make it more legible with:
#!/bin/sh
i=1
while [ "$i" -ne 10 ]
do
echo "$i"
i=$((i + 1))
done
as in init
, we're assigning a constant value, so we don't need to evaluate an arithmetic expression. The i != 10
in test
can easily be translated to a [
expression, and for next
, using a shell variable assignment as opposed to a variable assignment inside an arithmetic expression, lets us get rid of :
and the need for quoting.
Beside i++
-> i = i + 1
, there are more translations of ksh/bash-specific constructs that are not POSIX that you might have to do:
-
i=1, j=2
. The,
arithmetic operator is not really POSIX (and conflicts with the decimal separator in some locales with ksh93). You could replace it with another operator like+
as in: "$(((i=1) + (j=2)))"
but usingi=1 j=2
would be a lot more legible. -
a[0]=1
: no arrays in POSIX shells -
i = 2**20
: no power operator in POSIX shell syntax.<<
is supported though so for powers of two, one can usei = 1 << 20
. For other powers, one can resort tobc
:i=$(echo "3 ^ 20" | bc)
-
i = RANDOM % 3
: not POSIX. The closest in the POSIX toolchest isi=$(awk 'BEGIN{srand(); print int(rand() * 3)}')
.
¹ technically, that syntax is from the ksh93 shell and is also available in zsh in addition to bash
Solution 2
thanks for above in-depth background knowledge on the difference. A drop in replacement that worked for me when using shellcheck.net was as below.
BASH
for i in {1..100}; do
...
done
POSIX
i=1; while [ $i -le 100 ]; do
...
i=$(( i + 1 ))
done
some people noted that seq is also an option using seq 1 10 . Creating a loop, however this is dependant that os has seq.
Solution 3
This method is even more general, since you can plug arbitrary $
variable in it:
#!/bin/sh
for i in $(seq 1 10); do
echo $i
done
Just make sure, you have seq
utility installed, but since it is in CoreUtils
, you do.
Related videos on Youtube
![Vlastimil Burián](https://i.stack.imgur.com/4XOCT.jpg?s=256&g=1)
Vlastimil Burián
I am passionate about Linux systems in general and POSIX shell scripting in particular.
Updated on September 18, 2022Comments
-
Vlastimil Burián almost 2 years
I know how to create an arithmetic
for
loop inbash
.How can one do an equivalent loop in a POSIX shell script?
As there are various ways of achieving the same goal, feel free to add your own answer and elaborate a little on how it works.
An example of one such
bash
loop follows:#!/bin/bash for (( i=1; i != 10; i++ )) do echo "$i" done
-
mirageglobe about 6 yearsnote that i have placed i=0 in the same line as while. although readability is not great, it is a one liner drop-in. This ensures that variable i is not tainted by anywhere else defined in the script.
-
dosentmatter over 3 yearsThe second code block should be
i=1
in order to be equivalent to the first code block. -
Stéphane Chazelas over 3 yearsNote that though not standard that
{1..100}
syntax is not specific tobash
. It was actually copied from zsh. -
Stéphane Chazelas over 2 yearsNote that it cannot be used in contexts where
$IFS
may not contain the newline character or may contain decimal digits. -
Admin about 2 yearsJust a note about the
test
condition: It is generally better not to check just against a single value (10
in the example). Usei < 10
instead ofi != 10
. Why? It is much more robust and logically correct. The condition will not stop working when you change the incrementation step, change thei
value outside of thenext
expression, when you use float instead of int (in other languages than shell) etc. --- BTW I am wondering if there are reasons to prefer one of these two variants of a numerical variable assignment:: $((x = a + b))
orx=$((a + b))