Portable POSIX shell alternative to GNU seq(1)?

An alternative to awk is bc:

seq() (first=$1 incr=$2 last=$3
  echo "for (i = $first; i <= $last; i+=$incr) i" | bc -l
)

An advantage is that you're not limited by the size/resolution of your CPU's doubles:

$ seq '(2^200)' '(2^100)' '(2^200+2^102)'
1606938044258990275541962092341162602522202993782792835301376
1606938044258990275541962092342430253122431223184289538506752
1606938044258990275541962092343697903722659452585786241712128
1606938044258990275541962092344965554322887681987282944917504
1606938044258990275541962092346233204923115911388779648122880

But beware of line wrapping when the numbers get too big:

$ seq '(2^500)' '(2^100)' '(2^500+2^101)'
32733906078961418700131896968275991522166420460430647894832913680961\
33796404674554883270092325904157150886684127560071009217256545885393\
053328527589376
32733906078961418700131896968275991522166420460430647894832913680961\
33796404674554883270092325904157150886684127560071010484907146113622\
454825230794752
32733906078961418700131896968275991522166420460430647894832913680961\
33796404674554883270092325904157150886684127560071011752557746341851\
856321934000128

According to the open group POSIX awk supports BEGIN, therefore it can be done in awk:

awk -v MYEND=6 'BEGIN { for(i=1;i<=MYEND;i++) print i }'

Where -v MYEND=6 would stand for the assignment as in the first argument to seq. In other words, this works too:

END=6
for i in `awk -v MYEND=$END 'BEGIN { for(i=1;i<=MYEND;i++) print i }'`; do
    echo $i
done

Or even with three variables (start, increment and end):

S=2
I=2
E=12
for i in `awk -v MYS=$S -v MYI=$I -v MYE=$E 'BEGIN { for(i=MYS;i<=MYE;i+=MYI) print i }'`; do
    echo $i
done

Extra Solaris note: On Solaris /usr/bin/awk is not POSIX compliant, you need to use either nawk or /usr/xpg4/bin/awk on Solaris.

On Solaris, you probably want to set /usr/xpg4/bin early in PATH if you are running a POSIX compliant script.

Reference answer:

  • awk hangs on Solaris

I ended up writing one in ksh a while ago; it's not as bullet-proof as it could be, because I wrote it for myself, but it should be a good start. Not sure it'd work with pure Bourne shell, as your Q is currently tagged, because of the $(( )) syntax.

#!/usr/bin/ksh

start=1
end=1
step=1

case $# in
        0)
                echo Usage: $0 '[Start [Step]] End'
                exit 0
                ;;
        1)
                end=$1
                ;;
        2)
                start=$1
                end=$2
                ;;
        3)
                start=$1
                step=$2
                end=$3
                ;;
esac

# quick & dirty validations
if [ $step -eq 0 ]
then
  exit 1
fi

if [ $step -gt 0 ]
then
  if [ $start -gt $end ]
  then
    exit 2
  fi
else
  if [ $start -lt $end ]
  then
    exit 3
  fi
fi

i=$start
if [ $step -gt 0 ]
then
  while [ $i -le $end ]
  do
    printf "%d\n" $i
    i=$(( i + step ))
  done
else
  while [ $i -ge $end ]
  do
    printf "%d\n" $i
    i=$(( i + step ))
  done
fi