2

The following works in a Bash script:

PACKAGES=(
    'curl'
    'git'
    'htop'
    'mc'
    'tree'
    'vim'
)

apt --yes install ${PACKAGES[@]}

But how can I do the same in POSIX so that I can use it in a #!/bin/sh script?

Jeff Schaller
  • 67,283
  • 35
  • 116
  • 255
rfgamaral
  • 455
  • 2
    /bin/sh can't be guaranteed to support arrays; https://unix.stackexchange.com/a/137571/117549 – Jeff Schaller Apr 19 '18 at 16:52
  • Do you need to use /bin/sh? i.e. is there some system with apt that doesn't have Bash as a mandatory install anyway? (I know it's Essential in Debian and Ubuntu, but I don't know if there's some system that has gotten rid of it.) – ilkkachu Apr 19 '18 at 17:06
  • You don't want to join them with space here, you want to pass each element as separate argument to apt (not as one argument which contains the elements separated with space, which apt would consider as one package to install) – Stéphane Chazelas Apr 19 '18 at 17:14

2 Answers2

7

In a POSIX sh shell, you have exactly one array: $@ (the array of positional parameters, i.e. $1, $2, ...). You set its values with set:

set -- curl git htop mc tree vim

or from the command line:

./myscript.sh curl git htop mc tree vim

Then,

apt --yes install "$@"

Quoting the expansion of $@ makes the array expand to its quoted elements. That is, if the $@ array contains a word and another word, "$@" will expand to those two strings. Not quoting $@ will make it expand to the four strings a, word, another and word. The unquoted behaviour depends on the contents of $IFS.

Note that in bash too, you'd like to double quote the ${PACKAGES[@]} expansion:

apt --yes install "${PACKAGES[@]}"

Related: Arrays in Unix Bourne Shell


You don't really "join the elements with spaces" here though. Yes, if you echo the values you'll get spaces between them, but it's the difference between

set -- a b c d
printf '>%s<\n' "$@"

which yields

>a<
>b<
>c<
>d<

(four separate arguments, which is what you want)

and

printf '>%s<' "$*"

which yields

>a b c d<

(one single argument with the elements joined by spaces (the first character of $IFS), which is not what you actually want to use with your apt install command)

Kusalananda
  • 333,661
  • 1
    "Always", unless it was modified. Some shells including dash import it from the environment if there, most other shells ignore a IFS environment variable for security reasons. – Stéphane Chazelas Apr 19 '18 at 17:19
0

Since what you have is package names, you could just store them in a simple string variable, if you want to use a standard shell. At least according to Debian manuals, the package names can only contain the characters -+.:~a-z0-9A-Z, none of which is a glob character or whitespace.

So, this should be ok:

#!/bin/sh
packages="curl git htop ..."
IFS=" "                           # make sure there's nothing weird here
apt --yes install $packages       # no quotes!

If you're paranoid, add set -f to the start to disable file name globbing. That would leave only spaces as an issue, but I don't think any sane system would allow whitespace in package names.

ilkkachu
  • 138,973
  • 2
    Note that several shells including dash inherit $IFS from the environment, so here you'd want to also fix $IFS to SPC (like every time you use that split+glob operator) – Stéphane Chazelas Apr 19 '18 at 17:16