Reputation: 1756
According to this reference sheet on hyperpolyglot.org, the following syntax can be used to set an array.
i=(1 2 3)
But I get an error with dash which is the default for /bin/sh
on Ubuntu and should be POSIX compliant.
# Trying the syntax with dash in my terminal
> dash -i
$ i=(1 2 3)
dash: 1: Syntax error: "(" unexpected
$ exit
# Working fine with bash
> bash -i
$ i=(1 2 3)
$ echo ${i[@]}
1 2 3
$ exit
Is the reference sheet misleading or erroneous?
If yes, what would be the correct way to define an array or a list and be POSIX compliant?
Upvotes: 31
Views: 23907
Reputation: 16128
You can use the argument list $@
as an array in POSIX shells
It's trivial to initialize, shift
, unshift
, and push
:
# initialize $@ containing a string, a variable's value, and a glob's matches
set -- "item 1" "$variable" *.wav
# shift (remove first item, accepts a numeric argument to remove more)
shift
# unshift (prepend new first item)
set -- "new item" "$@"
# push (append new last item)
set -- "$@" "new item"
Here's a pop
implementation:
# pop (remove last item, store it in $last)
i=0 len=$#
for last in "$@"; do
if [ $((i+=1)) = 1 ]; then set --; fi # increment $i. first run: empty $@
if [ $i = $len ]; then break; fi # stop before processing the last item
set -- "$@" "$last" # add $a back to $@
done
echo "$last has been removed from ($*)"
($*
joins the contents of $@
with $IFS
, which defaults to a space character.)
Iterate through the $@
array and modify some of its contents:
i=0
for a in "$@"; do
if [ $((i+=1)) = 1 ]; then set --; fi # increment $i. first run: empty $@
a="${a%.*}.mp3" # example tweak to $a: change extension to .mp3
set -- "$@" "$a" # add $a back to $@
done
Refer to items in the $@
array:
echo "$1 is the first item"
echo "$# is the length of the array"
echo "all items in the array (properly quoted): $@"
echo "all items in the array (in a string): $*"
[ "$n" -ge 0 ] && eval "echo \"the ${n}th item in the array is \$$n\""
(eval
is dangerous, so I've ensured $n
is a number before running it)
There are a few ways to set $last
to the final item of a list without popping it:
with a function:
last_item() { shift $(($# - 1)) 2>/dev/null && printf %s "$1"; }
last="$(last_item "$@")"
... or with an eval
(safe since $#
is always a number):
eval last="\$$#"
... or with a loop:
for last in "$@"; do true; done
⚠️ Warning: Functions have their own $@
arrays. You'll have to pass it to the function, like my_function "$@"
if read-only or else set -- $(my_function "$@")
if you want to manipulate $@
and don't expect spaces in item values.
If you need to handle spaces in item values, it becomes much more cumbersome:
# ensure my_function() returns each list item on its own line
i=1
my_function "$@" |while IFS= read line; do
if [ $i = 1 ]; then unset i; set --; fi
set -- "$@" "$line"
done
This still won't work with newlines in your items. You'd have to escape them to another character (but not null) and then escape them back later. See "Iterate through the $@
array and modify some of its contents" above. You can either iterate through the array in a for
loop and then run the function, then modify the variables in a while IFS= read line
loop, or just do it all in a for
loop without a function.
Upvotes: 6
Reputation: 396
As said by rici, dash doesn't have array support. However, there are workarounds if what you're looking to do is write a loop.
For loop won't do arrays, but you can do the splitting using a while loop + the read builtin. Since the dash read builtin also doesn't support delimiters, you would have to work around that too.
Here's a sample script:
myArray="a b c d"
echo "$myArray" | tr ' ' '\n' | while read item; do
# use '$item'
echo $item
done
Some deeper explanation on that:
The tr ' ' '\n'
will let you do a single-character replace where
you remove the spaces & add newlines - which are the default delim
for the read builtin.
read
will exit with a failing exit code when it detects that stdin
has been closed - which would be when your input has been fully
processed.
Since echo prints an extra newline after its input, that will let you process the last "element" in your array.
This would be equivalent to the bash code:
myArray=(a b c d)
for item in ${myArray[@]}; do
echo $item
done
If you want to retrieve the n-th element (let's say 2-th for the purpose of the example):
myArray="a b c d"
echo $myArray | cut -d\ -f2 # change -f2 to -fn
Upvotes: 30
Reputation: 15603
It is true that the POSIX sh
shell does not have named arrays in the same sense that bash
and other shells have, but there is a list that sh
shells (as well as bash
and others) could use, and that's the list of positional parameters.
This list usually contains the arguments passed to the current script or shell function, but you can set its values with the set
built-in command:
#!/bin/sh
set -- this is "a list" of "several strings"
In the above script, the positional parameters $1
, $2
, ..., are set to the five string shown. The --
is used to make sure that you don't unexpectedly set a shell option (which the set
command is also able to do). This is only ever an issue if the first argument starts with a -
though.
To e.g. loop over these strings, you can use
for string in "$@"; do
printf 'Got the string "%s"\n' "$string"
done
or the shorter
for string do
printf 'Got the string "%s"\n' "$string"
done
or just
printf 'Got the string "%s"\n' "$@"
set
is also useful for expanding globs into lists of pathnames:
#!/bin/sh
set -- "$HOME"/*/
# "visible directory" below really means "visible directory, or visible
# symbolic link to a directory".
if [ ! -d "$1" ]; then
echo 'You do not have any visible directories in your home directory'
else
printf 'There are %d visible directories in your home directory\n' "$#"
echo 'These are:'
printf '\t%s\n' "$@"
fi
The shift
built-in command can be used to shift off the first positional parameter from the list.
#!/bin/sh
# pathnames
set -- path/name/1 path/name/2 some/other/pathname
# insert "--exclude=" in front of each
for pathname do
shift
set -- "$@" --exclude="$pathname"
done
# call some command with our list of command line options
some_command "$@"
Upvotes: 23
Reputation: 241701
Posix does not specify arrays, so if you are restricted to Posix shell features, you cannot use arrays.
I'm afraid your reference is mistaken. Sadly, not everything you find on the internet is correct.
Upvotes: 37