4

How can I map an unknown value alongside other known values in bash using an associative array, so that:

#!/bin/bash

array=("foo" "foo" "bar" "something else" "also something else" "bar")

declare -A map
map["foo"]="1"
map["bar"]="2"
map[unknown]="?"

for KEY in "${array[@]}"; do
   echo ${map[$KEY]}
done

outputs:

1
1
2
?
?
2

I do not know in which index my element is located, nor the length of the array to be mapped. I have tried doing map[${array[@]}]="?" before setting the other values, but this does not work

1
  • If KEY is set to foo and map["foo"]="" then what should echo ${map[$KEY]} output - a) an empty string or b) the character ?? Commented Nov 17 at 0:26

4 Answers 4

5

Default value

See Parameter Expansion in man page:

man -Pless\ +/parameter:-word bash
  ${parameter:-word}
       Use  Default  Values.  If parameter is unset or null, the expan‐
       sion of word is substituted.  Otherwise, the value of  parameter
       is substituted.

So

#!/bin/bash

array=("foo" "foo" "bar" "something else" "also something else" "bar")

declare -A map
map["foo"]="1"
map["bar"]="2"

for key in "${array[@]}";do
    printf '%-20s -> %s\n' "$key" "${map["$key"]:-?}"
done

Will produce:

foo                  -> 1
foo                  -> 1
bar                  -> 2
something else       -> ?
also something else  -> ?
bar                  -> 2

Default to another variable

But, if you really want to use map[unknown], you could:

#!/bin/bash

array=("foo" "foo" "bar" "something else" "also something else" "bar")

declare -A map="( ["foo"]="1"  ["bar"]="2"  ["unknown"]="?" )"

for key in "${array[@]}";do
    printf '%-20s -> %s\n' "$key" "${map["$key"]:-${map["unknown"]}}"
done

This will produce same output!

Note: you could even use "default to variable, with default":

echo "${map["$key"]:-${map["unknown"]:-?}}"

This will print

  • content of map["$key"], or if not exist,
    • content of map["unknown"], or if not exist,
      • a single interrogation mark: ?.

You could map your array, using printf

array=("foo" "foo" "bar" "something else" "also something else" "bar")
declare -A map="( ["foo"]="1"  ["bar"]="2"  ["unknown"]="?" )"

printf -v mapStr '"${map["%s"]:-${map["unknown"]}}" ' "${array[@]}"
declare -a "mappedArray=($mapStr)"
echo "${mappedArray[*]@Q}"
'1' '1' '2' '?' '?' '2'

Then

printf -v mapStr '%-20s -> %%s\n' "${array[@]}"
printf "$mapStr" "${mappedArray[@]}"
foo                  -> 1
foo                  -> 1
bar                  -> 2
something else       -> ?
also something else  -> ?
bar                  -> 2

But using this way could lead to security issues!
See Warning: limitation at end of Array elements in sed operation

Sign up to request clarification or add additional context in comments.

Comments

3

You can use bash's alternate value expansion feature to accomplish this, e.g. echo "${map[$KEY]-?}". The - indicates that the alternate value ("?" in this case) should be used if the element is not set. If you use :- instead, it'd use the alternate value if the element is unset or set to the null string.

Note: I also added double-quotes around the variable expansion, which is just good scripting hygiene. Without that, the ? might be expanded to a list of single-character filenames in the current directory. shellcheck.net is good at spotting common mistakes like this, so I recommend running your scripts past it and following its suggestions.

Comments

3

Don't create fake array elements to handle this, just test for the key being present and use the associated value if so, otherwise use ?:

$ cat tst.sh
#!/usr/bin/env bash

array=("foo" "foo" "bar" "something else" "also something else" "bar")

declare -A map
map["foo"]="1"
map["bar"]="2"

mapval() {
    local idx=$1 val='?'
    [[ -v map[$idx] ]] && val="${map[$idx]}"
    printf '%s' "$val"
}

for key in "${array[@]}"; do
    echo "$key -> $(mapval "$key")"
done

$ ./tst.sh
foo -> 1
foo -> 1
bar -> 2
something else -> ?
also something else -> ?
bar -> 2

or a little more efficiently:

$ cat tst.sh
#!/usr/bin/env bash

array=("foo" "foo" "bar" "something else" "also something else" "bar")

declare -A map
map["foo"]="1"
map["bar"]="2"

mapval() {
    local idx=$1
    local -n _val=$2
    if [[ -v map[$idx] ]]; then
        _val="${map[$idx]}"
    else
        _val='?'
    fi
}

for key in "${array[@]}"; do
    mapval "$key" val
    echo "$key -> $val"
done

$ ./tst.sh
foo -> 1
foo -> 1
bar -> 2
something else -> ?
also something else -> ?
bar -> 2

Comments

2

One potential option is to conditionally fill in the associative array, e.g. using bash v4.4+:

#!/bin/bash
array=("foo" "foo" "bar" "something else" "also something else" "bar")

declare -A map

for KEY in "${array[@]}"
do
    if [ "$KEY" == "foo" ]; then
        map["$KEY"]="1"
    elif [ "$KEY" == "bar" ]; then
        map["$KEY"]="2"
    else
        map["$KEY"]="?"
    fi
done

for KEY in "${array[@]}"
do
    echo ${map[$KEY]}
done
# 1
# 1
# 2
# ?
# ?
# 2

Would that suit your use-case?

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.