6

I've noticed you can't really count on seq(1) being available on anything but GNU systems. What's a simple reimplementation of seq(1) I can bring with me written in POSIX (not bash) shell?

EDIT: Note that I intend to use it on at least various BSD's, Solaris, and Mac OS X.

  • It might be easier to include a Solaris-compatible version if you can use a env sh-bang line that pulls in something other than /bin/sh – Jeff Schaller Jul 25 '16 at 17:31
  • 1
    A little note: You need to be carefully with posix shell testing on Linux, because it can run some bash specific code even if you are using an ancient shell, because Linux have bash compatibility binaries present on /usr/bin, like /usr/bin/test and others, and your shell script cannot run in other systems with the exactly same shell installed! You think some command is accepted by your very specific interpreter, but it is not. – Luciano Andress Martini Jul 25 '16 at 17:39
  • @LucianoAndressMartini I usually test with the old Heirloom bourne shell. I'm not sure if there's things in POSIX not in this shell though, or vis versa. – Elizafox Jul 25 '16 at 17:41
  • Mac OS X and FreeBSD also have seq(1). They seem to take the same arguments as the Linux version. – Edward Falk Jul 25 '16 at 20:23
  • 1
    @EdwardFalk Only as of FreeBSD 9.0 (I still have some 8.0 boxes), and Mac OS X 10.11. – Elizafox Jul 26 '16 at 2:14
6

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:

  • 1
    I didn't even think to use awk for this. Thanks! – Elizafox Jul 25 '16 at 17:39
  • I also have a perl version of the script, but awk is an excellent suggestion. – Jeff Schaller Jul 25 '16 at 17:42
  • 1
    On Solaris, you need to use /usr/bin/nawk not /usr/bin/awk. – fd0 Jul 25 '16 at 18:31
  • 1
    this could easily be extended to support arbitrary start and increment parameters as well (ala Linux seq) – Jeff Schaller Jul 25 '16 at 19:23
  • 2
    Solaris old-awk does have BEGIN (and END too); what it lacks is 'BEGIN plus no middle auto-exit'. You can fix this by saying exit in your BEGIN action, or by supplying input that reaches EOF either by redirection or a file operand; /dev/null is good because it is always present and always empty. Although in general nawk or xpg4 is more convenient. – dave_thompson_085 Jul 26 '16 at 4:23
7

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
2

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

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.