arraysbashassociative-array

Map unknown associative array element


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


Solution

  • 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

    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
    

    Same into a function:

    mapA2Array () { 
        if [[ $1 == -A ]]; then
            local -n _map=$2
            shift 2
        else
            local -n _map=map
        fi
        local _resArry=$1
        shift
        local _mapStr
        printf -v _mapStr '"${_map["%s"]:-${_map["unknown"]}}" ' "$@"
        declare -ga "$_resArry=($_mapStr)"
    }
    
    declare -A map="( ["foo"]="1"  ["bar"]="2"  ["unknown"]="?" )"
    
    mapA2Array tstResult foo foo bar 'something else' 'also something else' bar
    declare -p tstResult
    
    declare -a tstResult=([0]="1" [1]="1" [2]="2" [3]="?" [4]="?" [5]="2")
    
    declare -A otherMap=( ["foo"]="Hello world."  ["bar"]="42"  ["unknown"]="?" )
    mapA2Array -A otherMap tstResult foo foo bar 'something else' 'also something else' bar
    declare -p tstResult
    
    tstResult=([0]="Hello world." [1]="Hello world." [2]="42" [3]="?" [4]="?" [5]="42")
    

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