123

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?

2
  • 1
    I'm guessing that since I upgraded from bash 3 to 4 on the machine for some reason it's picking up the old declare function from bash3 ?
    – Joel
    Commented May 18, 2011 at 16:13
  • 3
    may be you're using an incorrect shebang? Commented May 18, 2011 at 16:24

10 Answers 10

78

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:

1
  • Exactly on target !
    – sjsam
    Commented Apr 24, 2019 at 15:59
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.

4
  • 1
    With which bash, I found I was not upgraded to bash 4. I used this link to upgrade the version on my MacBook Pro running Lion. YMMV Commented Jan 31, 2013 at 0:54
  • 5
    Bash 4 for OSX can also be obtained through brew. However, /bin/bash will need to be replaced by or symbolically linked to the version it installs. Commented Aug 5, 2015 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. Commented Mar 8, 2019 at 16:20
  • which bash is less accurate than type bash. which doesn't know anything about aliases, shell functions, hashed lookups, etc. Commented Apr 24, 2019 at 15:57
49

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
3
  • 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
    Commented Apr 10, 2016 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. Commented Jun 29, 2019 at 22:24
  • 1
    how would I subscript it though?
    – mfaani
    Commented Mar 21, 2020 at 20:57
25

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.
10
  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.

3
  • 1
    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
    Commented Mar 13, 2019 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. Commented Mar 7, 2021 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. Commented Nov 19, 2022 at 18:07
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".

4

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[@]}
0
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

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]}

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