help-bash
[Top][All Lists]
Advanced

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

Re: [Help-bash] indirection for arrays


From: John Kearney
Subject: Re: [Help-bash] indirection for arrays
Date: Sun, 06 May 2012 02:55:52 +0200
User-agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:12.0) Gecko/20120428 Thunderbird/12.0.1

Am 06.05.2012 01:28, schrieb Dan Douglas:
On Saturday, May 05, 2012 02:16:36 PM Bill Gradwohl wrote:

Via a web site reference, Dan Douglas (Thank You) provided something akin to
this:


func() {
        local -a 'keys=("${!'"$1"'address@hidden")' 
        ...
        ...
}

declare -a myArray=(1 2 3)

func 'myArray'



Neat trick! Took me a while to figure out how it works. The single quotes in
particular stopped me in my tracks for a while.
Yeah, after ages of trying to figure out a few quirks in compound assignment
that appeared specific to declare, I came up with that. The only related
reference to this I've been able to find since then is:
http://lists.gnu.org/archive/html/bug-bash/2004-09/msg00135.html

>From what I can tell, parsing of compound assignment arguments to declaration
commands is dynamically determined according to how things are quoted such that
only one pass of expansion is ordinarily allowed. If Bash thinks a bare
unquoted assignment is given, then it only allows expansions to occur as
arguments to declare, otherwise the expansions are performed by declare itself,
similarly to eval, except the result is assigned to an array rather than
treated as a command.

 # "$x" expanded as arg
 $ unset -v x; x=foo declare -a arr=("$x"); declare -p arr
 declare -a arr='()'

 # declare expanding "$x"
 $ unset -v x; x=foo declare -a 'arr=("$x")'; declare -p arr
 declare -a arr='([0]="foo")'

However, it's possible to trick declare into both letting pre-expanded results
through and performing expansions itself. The trigger (aside from a few
corner-cases I haven't figured out yet) is to quote or escape the parentheses
of the assignment: `arr=\(...\)'. Additionally, the variable must either
already have been given an array attribute by a previous command, or "-a" or
"-A" must be given explicitly, which forces handling as an array rather than
using some heuristic to determine what kind of assignment it is.

There are a huge number of useful applications for this. declare is much safer
and easier to use than eval because certain "printf %q-like" quoting is handled
automatically, and there's no case I've discovered so far in which the result
can be inadvertently treated as a command.

Here's my "random string" function as another example (which additionally
exploits a quirk in the way array parameters are parsed):

 # https://github.com/ormaaj/dotfiles/blob/master/bashrc#L215
 rndstr() {
     [[ $1 == +([[:digit:]]) ]] || return 1
     (( $1 )) || return
     local -a 'a=( {a..z} {A..Z} {0..9} )' \
              'b=( "address@hidden"{1..'"$1"'}"}" )'
     printf '%s' "address@hidden"
 }

Can I rely on this in future versions of bash, or is this a side effect that
might disappear some day? I can find no documentation that says specifically
it's supported.
 
Based upon Chet's message above I'd say probably not. My testing shows this
working in Bash 3 though.

Also, since indirection requires the ! immediately after a {, this means
indirection can never be on the left hand side of an assignment. Correct?
It's been known for some time that expansions on the LHS work as you'd expect
(enables something like printf -v, only more backwards-compatible). That's a
different concept which doesn't rely on this quirk, and works for any type of
assignment - not only arrays.

The "trick" above is doing a type of indirection by getting the parser to
cooperate in a sneaky fashion. At least that's my analysis.

Is there some other mechanism ("trick") besides eval that can do indirection
on the left hand side of an assignment? A mechanism that can be relied on in
future releases.

-- Bill Gradwohl
I'm working on a library which abstracts this. My goal is to provide several
ways of passing and returning arrays, to enable something like ksh93's print -C
and read -C, and some simple functional programming niceties (like things from
the Python itertools). As a preview, here's one of the basic mechanisms:

 arrayBuilder() {
     set -- "$@" "${2}[*]" "address@hidden"
     local -a 'keys=("${!'"$1"'address@hidden")'
     local -a "$2"='( [\${keys[n]}]="\${'"$1"'[keys[n++]]"{1..'address@hidden'}"}" )'
     local -a "$2"='('"${!3}"')'
     declare -p "$2"
     printf '<%s> ' "${!4}"; echo
 }
 
 main() {
     local -a fromArr=(4 [2]=3 5 6 [11]='9 10' 14 [15]=$'15[ \'-6 *')
     arrayBuilder fromArr toArray
 }
 
 main

It's ugly and hard to read (and making it robust will be even worse) but that
should just be a few lines like this that do the dirty work. The "print
-C" I'm having some trouble implementing without parsing the result or passing
each element through printf %q individually and hoping it works, but I have some
ideas...  Getting arrays out of functions is much harder than getting them in.

-- Dan Douglas
I actually don't really understand the intent of the function.
the following seems to do the same with a lot less overhead?
arrayBuilder3() {
     local p=$(declare -p "${1}") v="address@hidden"
     echo "${p/${1}/${2//[![:alnum:]]}}"
     printf '<%s> ' "${!v}"; echo
 }



or why not just do
  arrayBuilder2() {
     set -- "$@" "${2}[*]" "address@hidden"
         local p=$(declare -p "${1}")
         eval "${p/${1}/${2//[![:alnum:]]}}"
     declare -p "$2"
     printf '<%s> ' "${!4}"; echo
 }
it seems less dangerous, the declare -p output is formated to be evaluated.
so the only thing you need to do to make it fully safe is add something to validate that $2 isn't malicious.
furthermore a declare inside a function is locally scoped anyway, so in a function declare is a synonym for local.

if you want to export the array just use declare -g.
 arrayBuilder2() {
      check_valid_var_name "${2}" || return $?
         local p=$(declare -p "${1}")
         p=${p/-a/-ga}
         eval "${p/${1}/${2//[![:alnum:]]}}"
  }
 
 main() {
     local -a fromArr=(4 [2]=3 5 6 [11]='9 10' 14 [15]=$'15[ \'-6 *')
     arrayBuilder2 fromArr toArray2
     declare -p "toArray2"
 }

or am i missing something?



--
View John
          Kearney's profile on LinkedIn John Kearney

reply via email to

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