2

For some reason, I am unable to access the array keys with the exclamation point syntax:

declare -a sites
sites=(["fr"]="frederick" ["an"]="annapolis")

for i in "${!sites[@]}"
  do
    echo "key: $i "
  done

This Just echo's out "key : 0"

What am I doing wrong here?

Also, I would like to add the value.

So the our put would be:

key : fr , value : frederick

LeoNerd
  • 8,344
  • 1
  • 29
  • 36
jshawl
  • 3,315
  • 2
  • 22
  • 34

3 Answers3

0

It Can be done in old bash versions.

In older versions of bash you can use the whole environment variable set to implement associative arrays (also called hashes)

export HASH_PREFIX="I_AM_A_HASH"

hash-set() {
    HASH_NAME="$1" ; shift
    HASH_KEY="$1"  ; shift
    HASH_VAL="$1"  ; shift
    eval "export ${HASH_PREFIX}_${HASH_NAME}_KEY_${HASH_KEY}='$HASH_VAL'"
}
hash-get() {
    HASH_NAME="$1" ; shift
    HASH_KEY="$1"  ; shift
    eval "echo \"\$${HASH_PREFIX}_${HASH_NAME}_KEY_${HASH_KEY}\""
}
hash-keys() {
    HASH_NAME="$1" ; shift
    HASH_PREFIX_NAME_LENGTH=$(( ${#HASH_PREFIX} + ${#HASH_NAME} + 6 ))
    declare -x | while read -r LINE_READ ; do
        LINE_READ="${LINE_READ:11}"
        if [   x"${LINE_READ:0:HASH_PREFIX_NAME_LENGTH}" \
             = x"${HASH_PREFIX}_${HASH_NAME}_KEY_" \
           ]
        then
            LINE_READ="${LINE_READ:HASH_PREFIX_NAME_LENGTH}"
            LINE_READ="${LINE_READ/=*/}"
           echo "${LINE_READ}"
        fi
    done
}

hash-set sites "fr" "frederick"
hash-set sites "an" "annapolis"

for i in $(hash-keys sites) ; do
    echo "key: $i, value: $(hash-get sites $i)"
done

The keys are restricted to the same characters as Environment Variables (0-9,a-z,A-Z,_).

You could workaround by using "_xx" to mean non-alphanumeric ascii values (example "_5f" for "_" and "_3f" for "?"). rosettacode has how to convert back and forth between ascii characters and hex in pure-bash.

Also on mac laptop you can install homebrew then use it to install a newer bash. You could also use the full associative arrays in awk or perl or ruby or python.

Fenn
  • 81
  • 1
  • 7
  • 1
    Please do __not__ use `eval`, instead see [@lhunath](https://stackoverflow.com/a/3467959/2632107)'s work-around for Bash version that predate `4`. – S0AndS0 May 05 '19 at 18:58
-1

The problem is declare -a.

As per the man page, it should be declare -A.

declare [-aAfFgilrtux] [-p] [name[=value] ...]
    ...
    -a     Each name is an indexed array variable (see Arrays above).
    -A     Each name is an associative array variable (see Arrays above).

Try this instead:

declare -A sites
sites=(["fr"]="frederick" ["an"]="annapolis")

for i in "${!sites[@]}"
  do
    echo "key: $i, value: ${sites[$i]}"
  done
Mikel
  • 24,855
  • 8
  • 65
  • 66
  • That was what I tried at first. But if I run the script you posted, I get this output: line 3: declare: -A: invalid option declare: usage: declare [-afFirtx] [-p] [name[=value] ...] key: 0, value: annapolis – jshawl Apr 11 '12 at 16:12
  • 1
    This might be helpful as well: bash --version GNU bash, version 3.2.48(1) – jshawl Apr 11 '12 at 16:23
  • 8
    Associative arrays require bash >= 4.0. See http://tiswww.case.edu/php/chet/bash/NEWS. And the fact that your man pages for bash 3.2 don't say anything about associative arrays. ;-) – Mikel Apr 11 '12 at 17:38
  • 1
    @Mikel, the question was tagged `macos`, which is always bash 3.2 unless "special measures" (like [macports](https://macports.org/) or [brew](https://brew.sh)) are taken. – ghoti May 06 '19 at 13:47
-1

I think it maybe a lack of capitalization getting in the way...

declare -A _sites=( ["fr"]="frederick" ["an"]="annapolis" )

for i in "${!_sites[@]}"; do
    printf '%s -> %s\n' "${i}" "${_sites[$i]}"
done

Resources that where helpful in sorting the above out are not limited to the following;

  • An answer from @anubhava regarding declare -A usage

  • An answer from @lhunath that also covers a work around for Bash versions 3 or lower.

After reading the other posted answer's comments I think you'll want that last listed answer's work-around if stuck with older version of Bash that cannot be updated for reasons.


@Fenn a few more notes...

hash-set() {
    HASH_NAME="$1" ; shift
    HASH_KEY="$1"  ; shift
    HASH_VAL="$1"  ; shift
    eval "export ${HASH_PREFIX}_${HASH_NAME}_KEY_${HASH_KEY}='$HASH_VAL'"
}

... without shifting, or eval, and with required arguments might look like...

hash_set(){
    local _name="${1:?${FUNCNAME[0]} not provided a Hash Name}"
    local _key="${2:?${FUNCNAME[0]} not provided a Hash Key}"
    local _value="${3:?${FUNCNAME[0]} not provided a Value}"
    declare -g "${HASH_PREFIX}_${_name}_KEY_${_key}='${_value}'"
}

... hopefully this is a bit more helpful in translating that answer into something that can be up-voted.

S0AndS0
  • 860
  • 1
  • 7
  • 20