Shell – How to create an arithmetic loop in a POSIX shell script

bourne-shellposixshell-script

I know how to create an arithmetic for loop in bash.

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

Best Answer

I have found useful information in Shellcheck.net wiki, I quote:

  1. Bash:

     for ((init; test; next)); do foo; done
    
  2. 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 using i=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 use i = 1 << 20. For other powers, one can resort to bc: i=$(echo "3 ^ 20" | bc)

  • i = RANDOM % 3: not POSIX. The closest in the POSIX toolchest is i=$(awk 'BEGIN{srand(); print int(rand() * 3)}').

Related Question