On Wed, Mar 20, 2024, 12:49 Greg Wooledge <g...@wooledge.org> wrote: > 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 <v...@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." >
as to that , simply declare -p and run that back 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. > >