25

While replacing external commands in a shell script, I used an array to get rid of awk's NF.

Now, since I moved from bash to POSIX sh, I cannot get the array marked right:

#!/bin/bash
export RANGE="0 1 4 6 8 16 24 46 53"
RANGE=($RANGE)
echo arrayelements: $((${#RANGE[@]}))
LAST=$((${#RANGE[@]}-1))
echo "Last element(replace NF): ${RANGE[$LAST]}"

# ./foo
arrayelements: 9
Last element(replace NF): 53

I'm using OpenBSD's, sh and it has exactly the same size as the ksh. Changing above to /bin/sh, it seems that the following doesn't work:

set -A "$RANGE"
set -- "$RANGE"

How could I realise the above script in /bin/sh? (Note that it works fine if you invoke bash with --posix, that's not what I look for.)

Benjamin W.
  • 46,058
  • 19
  • 106
  • 116
Charles
  • 251
  • 1
  • 3
  • 3
  • 1
    Aside: All-caps variable names are bad form. See conventions for environment variable names at http://pubs.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap08.html, fourth paragraph, keeping in mind that environment variables and shell variables share a namespace -- so using lower-case names for your local variables will prevent accidentally overwriting something with meaning to the system. – Charles Duffy Aug 01 '15 at 01:04

3 Answers3

34

Arrays are not part of the POSIX sh specification.

There are various other ways to find the last item. A couple of possibilities:

#!/bin/sh
export RANGE="0 1 4 6 8 16 24 46 53"
for LAST_ITEM in $RANGE; do true; done
echo "Last element(replace NF): $LAST_ITEM"

or:

#!/bin/sh
export RANGE="0 1 4 6 8 16 24 46 53"
LAST_ITEM="${RANGE##* }"
echo "Last element(replace NF): $LAST_ITEM"
Matthew Slattery
  • 45,290
  • 8
  • 103
  • 119
  • 3
    @AlikElzin-kilaka, if the existing standard changed in the last 5 (now 7) years, it wouldn't be particularly "standard", would it? – meustrus Sep 14 '18 at 18:19
  • 1
    @meustrus IEEE 1003.1 like other standards are developed continuously and receive new revisions. In this case in 2017 - a year before your comment. While they often add new features this one does not add arrays to the shell language though. – stefanct Jan 30 '19 at 17:06
  • You can make new standards, but the old standard is never really replaced. When it comes to shell scripting, the only standard that matters is "what is implemented everywhere I care about". For most people, that standard is bash, not POSIX, and even then you can only really expect to have bash from at most 5 years ago. For others, only Bourne shell from 1987 is portable. Very few shells in fact even correctly implement POSIX with or without features from 2017, and bash is *not* one of them. – meustrus Feb 05 '19 at 13:23
  • @meustrus The question explicitly asks about POSIX. If you think the POSIX standard is not relevant, better to flag the question to close it. – jinawee Feb 11 '19 at 14:37
  • @jinawee, perhaps I misspoke. POSIX is absolutely relevant, especially when it is directly asked for. Bash and Bourne Shell are merely examples of common "standards". The real problem here is that shells don't have a lot of room to add features that require special syntax, because there's not a lot of reserved syntax that isn't already saturated with features. Basically, adding arrays to POSIX sh is probably not possible without breaking the existing standard. – meustrus Feb 12 '19 at 23:34
5

You can use the following project from Github, which implements a POSIX-compliant array, which works in all shells I tried: https://github.com/makefu/array

It is not very convenient to use, but I found it to work well for my purposes.

Sammy S.
  • 1,280
  • 1
  • 16
  • 31
  • 3
    For those who wonder, this is a WTFPL licensed PoC script which stores array elements in a string, separated by line breaks. To also allow line breaks, elements are encoded (line breaks are replaced by %0A, and percent signs by %25) before putting them into the string and decoded when they are retrieved. – josch Jan 13 '21 at 15:38
3

The following code works for me using the Heirloom Bourne Shell:

#!/usr/local/bin/bournesh
# cf. Heirloom Bourne Shell, 
#     http://freshmeat.net/projects/bournesh/
#     http://www.in-ulm.de/~mascheck/bourne/

# use a caret as a pipe symbol to make sure it's a Bourne shell
# cf. http://mywiki.wooledge.org/BourneShell
ls ^ cat 1>/dev/null 2>&1 || 
   { echo 'No true Bourne shell! ... exiting ...'; exit 1; }

IFS=' '
unset RANGE
RANGE="0 1 4 6 8 16 24 46 53"
export IFS RANGE
set -- $RANGE
echo arrayelements: $#
LAST=$#
eval echo "Last element\(replace NF\): \$$#"

Note that IFS is set to a space and there are no double quotes around $RANGE.

ralph
  • 31
  • 1
  • 6
    This only works for elements that themselves do not contain whitespace. The point of having an array in your shell is to allows such elements. – chepner May 09 '16 at 12:00