bug-bash
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: "${assoc[@]@k}" doesn't get expanded to separate words within compou


From: Greg Wooledge
Subject: Re: "${assoc[@]@k}" doesn't get expanded to separate words within compound assignment syntax
Date: Wed, 20 Mar 2024 07:49:15 -0400

On Wed, Mar 20, 2024 at 07:11:34AM -0400, Zachary Santer wrote:
> On Wed, Mar 20, 2024 at 12:29 AM Lawrence Velázquez <vq@larryv.me> wrote:
> > A couple of previous discussions:
> >   - https://lists.gnu.org/archive/html/bug-bash/2020-12/msg00066.html
> >   - https://lists.gnu.org/archive/html/bug-bash/2023-06/msg00128.html
> 
> There I go, reporting a bug that isn't a bug again.
> 
> One would think that enabling this behavior would be the entire
> purpose of the alternate ( key value ) syntax. If it doesn't do that,
> what benefit does it give over the standard ( [key]=value ) syntax?
> Maybe it;s easier to use eval with?

I believe the "X" in this X-Y problem is "I want to serialize an
associative array to a string, send the string to another bash script,
and de-serialize it back into an associative array there."

So, the first thing we observe is the behavior of the @k and @K expansions:

hobbit:~$ declare -A aa=('key 1' 'value 1' 'key 2' 'value 2')
hobbit:~$ declare -p aa
declare -A aa=(["key 2"]="value 2" ["key 1"]="value 1" )
hobbit:~$ printf '<%s> ' "${aa[@]@k}"; echo
<key 2> <value 2> <key 1> <value 1> 
hobbit:~$ printf '<%s> ' "${aa[@]@K}"; echo
<"key 2" "value 2" "key 1" "value 1" > 

Essentially, @k serializes the associative array to a list of alternating
keys/values, while @K serializes it to a string.

For the purposes of sending the serialization to another script, the
list is not suitable unless we NUL-terminate each element.  We could
pursue that, but it's going to involve more work, I suspect.

Given the string serialization we get from @K, it makes sense to start
there, and try to determine whether it's eval-safe.

hobbit:~$ declare -A mess=('*' star '?' qmark $'\n' nl '$(date)' cmdsub '`id`' 
backticks)
hobbit:~$ declare -p mess
declare -A mess=([$'\n']="nl" ["?"]="qmark" ["*"]="star" ["\$(date)"]="cmdsub" 
["\`id\`"]="backticks" )

That's not comprehensive, but it's a start.

hobbit:~$ printf '<%s> ' "${mess[@]@K}"; echo
<$'\n' "nl" "?" "qmark" "*" "star" "\$(date)" "cmdsub" "\`id\`" "backticks" > 
hobbit:~$ serial="${mess[@]@K}"
hobbit:~$ printf '<%s>\n' "$serial"
<$'\n' "nl" "?" "qmark" "*" "star" "\$(date)" "cmdsub" "\`id\`" "backticks" >

There's our serialization to a string.  Now we'll try to de-serialize:

hobbit:~$ eval "declare -A copy=($serial)"
hobbit:~$ declare -p copy
declare -A copy=([$'\n']="nl" ["?"]="qmark" ["*"]="star" ["\$(date)"]="cmdsub" 
["\`id\`"]="backticks" )

Looks OK.  Let's verify another way:

hobbit:~$ for i in "${!copy[@]}"; do printf '<%s>: <%s>\n' "$i" "${copy[$i]}"; 
done
<
>: <nl>
<?>: <qmark>
<*>: <star>
<$(date)>: <cmdsub>
<`id`>: <backticks>

Unless someone else comes up with a key or value that breaks the eval,
this looks like the simplest way.

By comparison, the NUL-terminated @k list can't even be stored in a
variable, so you'd need to transmit it to the other script in some way
that's equivalent to using a temp file.  Thus:

hobbit:~$ printf '%s\0' "${mess[@]@k}" > tmpfile
hobbit:~$ unset -v copy; declare -A copy; while IFS= read -rd '' key && IFS= 
read -rd '' value; do copy[$key]=$value; done < tmpfile
hobbit:~$ declare -p copy
declare -A copy=([$'\n']="nl" ["?"]="qmark" ["*"]="star" ["\$(date)"]="cmdsub" 
["\`id\`"]="backticks" )

I can't think of any way to restore an @k serialization other than a
IFS= read -rd '' loop.  The only advantage I can see here is that it
doesn't use eval, and therefore is visibly safe.  With the eval @K
solution, we're still left wondering whether the script is a ticking
time bomb.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]