How can I create an arithmetic loop in a POSIX shell script?
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 shellsi = 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)}')
.
thanks for above indepth background knowledge on the difference. A drop in replacement that work for me when using shellcheck.net was as below.
BASH
for i in {1..100}; do
...
done
POSIX
i=0; 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.