112

I've written a script that uses associative arrays in bash (v 4).

It works fine on my local machine which is using 4.1.5(1)-release.

On the production machine, using 4.1.0(1)-release the following line, which declares the assoc array, fails:

declare -A uniqjars

with the message:

/script.sh: line 11: declare: -A: invalid option
declare: usage: declare [-afFirtx] [-p] [name[=value] ...]

I was under the impression this was a general bash 4 feature?

In the man for bash on the production machine it discusses using -A so I assume it should work.

Associative arrays are created using declare -A name.

I can confirm the script is using the right version of bash by printing out the value of echo 'bash -version.

What could I be doing wrong?

oguz ismail
  • 1
  • 16
  • 47
  • 69
Joel
  • 29,538
  • 35
  • 110
  • 138

10 Answers10

66

The following seems to be a typical scenario on macOS after installing a newer Bash with Homebrew:

  • /bin/bash is the old Bash, 3.2
  • /usr/local/bin/bash is the new Bash that knows about associative arrays (4.0 or newer)
  • type bash points to /usr/local/bin/bash and bash --version is the new one (because it resolves to /usr/local/bin/bash --version)

However, scripts with a #!/bin/bash shebang line that are run with ./script will use the old Bash (the scenario in the question). Solutions are:

  • Call the script with bash script: the new Bash will be used. Disadvantage: you always have to call it like that.
  • Change the shebang line to #!/usr/local/bin/bash. Disadvantage: on many systems, there is no Bash in /usr/local/bin and your script isn't portable any longer.
  • Change the shebang line to #!/usr/bin/env bash. This will use the first bash in your PATH, which should be the new one. This is pretty portable; the only downside is that you don't know exactly which Bash will be executed.

See also these Q&A:

Benjamin W.
  • 46,058
  • 19
  • 106
  • 116
60

Make sure the version of bash being invoked as interpreter at the top of your shell script (#!/bin/bash or whatever) is also version 4. If you're doing:

bash --version

and it's giving you v4, do a which bash to check it's location.

Richard H
  • 38,037
  • 37
  • 111
  • 138
  • 1
    With `which bash`, I found I was not upgraded to bash 4. I used [this link](http://concisionandconcinnity.blogspot.com/2009/03/upgrade-bash-to-40-in-mac-os-x.html) to upgrade the version on my MacBook Pro running Lion. YMMV – AWrightIV Jan 31 '13 at 00:54
  • 5
    Bash 4 for OSX can also be obtained through [brew](http://brew.sh/). However, `/bin/bash` will need to be replaced by or symbolically linked to the version it installs. – Tom Sweeney Aug 05 '15 at 17:13
  • 2
    `echo "$BASH_VERSION"` is more useful to check the running instance, not the version of the first one in the PATH. – Charles Duffy Mar 08 '19 at 16:20
  • `which bash` is less accurate than `type bash`. `which` doesn't know anything about aliases, shell functions, hashed lookups, etc. – Charles Duffy Apr 24 '19 at 15:57
46

Here is a Workaround, if you want to use chars as array index with bash v3:

array=(
    'hello::world.'
    'nice::to meet you'
)

for index in "${array[@]}" ; do
    KEY="${index%%::*}"
    VALUE="${index##*::}"
    echo "$KEY - $VALUE"
done

Output:

hello - world.
nice - to meet you
Abhijeet Kasurde
  • 3,937
  • 1
  • 24
  • 33
meigrafd
  • 569
  • 4
  • 8
  • 1
    So it appears this just splits the string, using `::` as the center - `string='hello::world' && LEFT="${string%%::*}" && RIGHT="${string##*::}" && echo $LEFT "<>" $RIGHT` - You still can't lookup a value by key. – jgraup Apr 10 '16 at 12:34
  • @jgraup you would have to create a function that would loop though and return the value for a the key passed. This is a nice workaround if you can't use associative arrays or want to make your script more portable. – Yzmir Ramirez Jun 29 '19 at 22:24
  • 1
    how would I subscript it though? – mfaani Mar 21 '20 at 20:57
20

Here is how to get the updated bash version on OS X, you should install brew and then bash.

$ /bin/bash --version    
GNU bash, version 3.2.57(1)-release (x86_64-apple-darwin14)
    
$ brew install bash    
... install

$ /usr/local/bin/bash --version    
GNU bash, version 4.3.46(1)-release (x86_64-apple-darwin14.5.0)

In 2023, the path has changed. The current one is:

$ /opt/homebrew/bin/bash --version
GNU bash, version 5.2.15(1)-release (aarch64-apple-darwin22.1.0)
Copyright (C) 2022 Free Software Foundation, Inc.
cwallenpoole
  • 79,954
  • 26
  • 128
  • 166
khancell
  • 329
  • 2
  • 4
9

meigrafd's answer solved my problem, so if using an incorrect shebang or still on bash version 3 the following allowed me to return a value based on it's associated key:

array=(
    'hello::world.'
    'nice::to meet you'
)

for index in "${array[@]}" ; do
  KEY="${index%%::*}"
  VALUE="${index##*::}"
  if [ "$KEY" == "nice" ]; then
    echo "$VALUE"
    break
  fi
done

This will return the value "to meet you".

KeithJ
  • 169
  • 3
  • 12
9
  1. Check the current shell you are using with this cmd:

    echo $SHELL
    

    E.g. it could say /bin/bash

  2. Run --version on that $SHELL:

    /bin/bash --version
    

    It may output something like GNU bash, version 3.2.57(1)-release (x86_64-apple-darwin16)

    If it is before version 4, you'll have to upgrade.

  3. Check if you already have a bash shell with version 4. Try running:

    bash --version
    

    If so, you just need to change your default shell to that shell.

    You can use these cmds to do so:

    sudo bash -c 'echo /usr/local/bin/bash >> /etc/shells'
    sudo chsh -s /usr/local/bin/bash
    

    The first adds the shell to the allowed shells. The second actually changes your default shell.

Charles Duffy
  • 280,126
  • 43
  • 390
  • 441
Tyler
  • 1,762
  • 2
  • 15
  • 8
  • it worked for me just without the `sudo` in the second command (i.e. `chsh -s /usr/local/bin/bash`) because I wanted it to change it for my user and not for root – Avia Eyal Mar 13 '19 at 12:58
  • "/usr/local/bin/bash" might already be in "/etc/shells" so it doesn't hurt to check that before adding it for no reason. And using sudo will actually change the shell for user root. So you may want to execute chsh instead of sudo chsh, or both. – Romain Vincent Mar 07 '21 at 21:01
  • This (`chsh`) changes the default shell which includes the interactive shell you're working in every time you open a new Terminal window on desktop. This may not be what you desired. The original poster wanted to execute a shell *script* (as opposed to changing his default interactive shell) with the right version of `bash`. For example, I use `zsh` as my interactive shell, but I'd like to execute a `bash` shell script **without** (permanently) changing my interactive shell from `zsh` to `bash`. I think the most popular answer by @benjamin-w is better. – Vincent Yin Nov 19 '22 at 18:07
3

Nothing above helped me, so I opened /etc/shells and changed the line - /bin/bash to /usr/local/bin/bash, and then reloaded it with source /etc/shells and now I can enjoy new possibilities of v4 of bash

Mihey Mik
  • 1,643
  • 13
  • 18
3

Old BASH version didn't support declare -A syntax of declaring arrays. I suggest using either of these 2 forms to declare arrays in bash to make it compatible with older bash version of your production system:

arr=( '10' '20' '30' )
echo ${arr[@]}

or

arr[0]=10
arr[1]=20
arr[2]=30
echo ${arr[@]}
anubhava
  • 761,203
  • 64
  • 569
  • 643
1

Per the command:

help declare
declare: declare [-aAfFgilnrtux] [-p] [name[=value] ...]
  Set variable values and attributes.

Declare variables and give them attributes.  If no NAMEs are given,
display the attributes and values of all variables.
Options which are set attributes:
  -a        to make NAMEs indexed arrays (if supported)
  -A        to make NAMEs associative arrays (if supported)

Notice lowercase "-a" and uppercase "-A" are "(if supported)". Also if you look at the posted error message for declare usage:

/script.sh: line 11: declare: -A: invalid option
declare: usage: declare [-afFirtx] [-p] [name[=value] ...]

The given options are "[-afFirtx]" showing to use a lowercase "-a" but no uppercase "-A". Compare that to the usage string from the help command. It looks as if it's just not supported on the given machine.

0

Try using a different shebang. On my Mac:

$ which bash
/usr/local/bin/bash

So, this script runs fine, producing "Hello World":

#!/usr/local/bin/bash
declare -A assoc
assoc[hello]="Hello World"
echo ${assoc[hello]}
Khanan
  • 1
  • 1