Search code examples
zshzsh-completion

Generate an associative array from command output


I am writing a zsh completion function to complete IDs from a database. There is a program listnotes which outputs a list like this:

bf848bf6-63d2-474b-a2c0-e7e3c4865ce8   Note Title
aba21e55-22c6-4c50-8bf6-bf3b337468e2   Another one
09ead915-bf2d-449d-a943-ff589e79794a   yet another "one"
...

How do I generate an associative array note_ids from the output of the listnotes command such that I get an associative array like this?

( bf848bf6-63d2-474b-a2c0-e7e3c4865ce8 "Note Title" aba21e55-22c6-4c50-8bf6-bf3b337468e2 "Another one" 09ead915-bf2d-449d-a943-ff589e79794a "yet another \"one\"" )

Note that there may be whitespace in the keys. I tried to generate something with sed:

note_ids=($(listnotes | sed 's/^\(.*\)   \(.*\)$/\1 "\2"/'))

but quoting strings like this doesn’t seem to work, and double quotes in the title make it even more difficult.


Solution

  • Try something like

    typeset -A note_ids
    for line in ${(f)"$(listnotes)"}; do
        note_ids+=(${line%% *} ${line#*   })
    done
    
    • ${(f)PARAM}: split the result of the expansion of $PARAM at newlines
    • "$(listnotes)": put the output of listnotes verbatim into the expansion.
    • for line in LIST: iterate over the items in LIST as split by ${(f)…}.
    • note_ids+=(key value): add key-value pair to an the associative array note_ids
    • ${line%% *}: cut the largest portion matching " *" (a space followed by anything) from the end of the expansion of line. So remove everying after including the first space, leaving only the key.
    • ${line#* }: cut the smallest portion matching "* " (anything followed by three spaces) from the beginning of the expansion of $line. So remove the key and the three spaces used as separator.

    Instead of using the parameter expansion flag (f) you could also read the output of listnotes line by line with read:

    listnotes | while read; do 
        note_ids+=(${REPLY%% *} ${REPLY#*   })
    done
    

    Unless specified otherwise read puts the read values into the REPLY parameter.