3

Getting a strange error here. I have some json structured like this:

BACKUP_REPOS='''
[
    {"name":"my.server1", "hash":"server1-hash"},
    {"name":"my.server2", "hash":"server2-hash"},
    {"name":"my.server3", "hash":"server3-hash"}
]
'''

What I am attempting to do is generate a selectable menu based on that using jq with the following function:

# generate the server menu
server_menu( ) {

    # hold some arrays
    declare -a _name _hash;

    while read -r _server _pw; do

        # hold the selected item
        _name[$_server]=$_server;
        _hash[$_server]=$_pw;

    # end the loop generation
    done < <( jq -rc '.[] | "\(.name) \(.hash)"' <<< "$BACKUP_REPOS" )

    # now loop the resultset
    select _server in "${!_name[@]}"; do 
        [ -n "$_server" ] && break;
    done;

    # return the server and hash
    SELECTED_SERVER=${_name[$_server]};
    SELECTED_HASH=${_hash[$_server]};

}

Now, this works perfectly fine in Ubuntu 18+, it generates an error on my Mac BigSur in Terminal. The error is: syntax error: invalid arithmetic operator (error token is ".server1") on line 82, which in my full code file is this line: _name[$_server]=$_server;

Where am I going wrong here? At this point, my only concern is getting this script to run on my Mac... I'm ok with keep multiple copies of it

TIA

4
  • 3
    I'm surprised this works for you in bash on Ubuntu 18.04, AFAIK even there the arrays need to be declared with -A (associative) rather than -a (indexed) as per jesse_b's answer Jun 25, 2021 at 19:58
  • @steeldriver: That is correct however depending on the variable contents it may not error, evaluate the object inside the index as 0, and overwrite the first element.
    – jesse_b
    Jun 25, 2021 at 20:01
  • 1
    Suggestion for replacement of that while loop: eval "$( jq -r '.[] | (.name|@sh) as $n | (.hash|@sh) as $h | "_name[\($n)]=\($n)", "_hash[\($n)]=\($h)"' <<<"$BACKUP_REPOS" )". In any case -c is not needed with jq there.
    – Kusalananda
    Jun 25, 2021 at 22:18
  • Why three quotes at the start and end of the variable assignment?
    – roaima
    Jun 26, 2021 at 7:49

2 Answers 2

8

Looks like you are trying to use an associative array which is not possible on the default version of bash that ships with macos so you will have to update your bash to version 4 or greater to do this but you will also need to change:

declare -a _name _hash

to

declare -A _name _hash

As a standard array the only thing that can be in the indices are integers and you can perform arithmetic expansion inside the [ ... ] so it is choking on the non integer/invalid operations you are expanding in there.

1
  • OP can always switch to zsh which is always installed on macos and is not as ancient, or to a proper programming language such as perl or python which will also have proper json parsing support. Jun 26, 2021 at 6:49
3

As jesse_b pointed out, the issue is that your array is not an associative array. The bash on macOS doesn't know how to deal with associative arrays, so consider rewriting it in zsh or use ordinary arrays. Alternatively, install an updated bash shell using e.g. Homebrew and correct the declare -a to declare -A.

The following will work with the default bash on macOS, assuming jq is also installed:

menu_select_server () {
        local server_json=file.json  # JSON containing server config

        # Set positional parameters to list of servers
        eval "$( jq -r '[ "set --", (.[].name|@sh) ] | @tsv' "$server_json" )"

        # Select wanted server
        local PS3='Select server: '
        select SELECTED_SERVER; do
                [[ -n $SELECTED_SERVER ]] && break
        done

        # Get corresponding hash
        SELECTED_HASH=$(
                jq -r --argjson i "$REPLY" \
                        '.[($i-1)].hash' "$server_json"
        )
}

We parse the file twice here, once to get the list of servers, and once to get the hash for the selected server, but it would be easy to do something similar to what you're doing and just switch to using $REPLY coming out from the select loop to get the hash:

menu_select_server () {
        local server_json=file.json  # JSON containing server config

        local name hash
        local hashes=()
        set --

        # Read names into list of positional parameters
        # Read hashes into "hashes" array
        while IFS=$'\t' read -r name hash; do
                set -- "$@" "$name"
                hashes+=( "$hash" )
        done < <( jq -r '.[] | [ .name, .hash ] | @tsv' "$server_json" )
        # or:
        #     jq -r 'map([ .name, .hash ] | @tsv)[]' "$server_json"

        # Select wanted server
        local PS3='Select server: '
        select SELECTED_SERVER; do
                [[ -n $SELECTED_SERVER ]] && break
        done

        # Get corresponding hash
        SELECTED_HASH=${hashes[REPLY-1]}
}

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .