Dynamic variable failure & equiv-const strings compare unequal
Greg Wooledge wrote: Also I think you are completely misrepresenting the dynamic variable scope system that bash uses. Variables are not just global or local. There's an entire stack of them. When you reference a variable (let's say i) inside a function, bash searches up through the call stack looking for a variable named i until it finds one. So where in the manpage is the behavior documented -- I'd like to reread it to ensure I don't have any misconceptions. The reason being is that out of 4 tests below, 2 fail. It appears test 2 failing is a failure of the dynamic scoping system. Test 3 was meant to be an extension, as I have some code where I change a member in a hash, and try to compare the final result with a known correct value, but as in test-2 all the other members of the hash disappear Notice that 'parts' is defined at the global level as an array. in tst0 & tst1, I locally change the value of IFS, and call "assignparts" that splits the value "$ip" based on the "localized" values of IFS -- tst0 + tst1 work as I'd expect with dynamic scoping. Since parts was declared 'global', the different splitting caused by differing 'IFS' values is reflected in the global. However, in tst3, I only change 1 member in the parts array, via "parts[1]=222". before the assignment, parts=(1 .2 .3 .4) but after the assignment "parts[1]=222", the entire array was wiped. I.e. test output was: Case 2 got/Expected: "222" "1\ 222\ .3\ .4" instead of it only replacing the 2nd member of parts, the entire array was wiped. The same happens if parts is a 'hash' -- trying to only replace 1 member of the hash results in the entire hash being wiped. I don't see how this is correct function for "dynamically scoped" variables -- which is why I need to reread (or read) the manpage descriptions of how dynamic vars are *supposed* to work, as they don't seem to be working by "unwinding" the call stack and using the last definition of 'parts' (which is at the global level). test 3 --- was supposed to check the values of the hash field-by-field, but first I thought to check them when printed out in a specific order as 2 identical strings. The strings look identical, yet don't compare equal. so it seemed pointless to try to break down test 3's output into a field-by-field comparison when I couldn't even get the identical strings that contained the fields to compare. tests 2 & 3 work backward from a program where I try to change 1 value in a hash (vs. the array in test2), but end up with the local assignments of values->[keys] not being propagated, at all, to the global frame where the has is declared. I.e. these examples of dynamic variable declarations and usage don't seem to work as I understand the concept, but there is nothing about bash's 'dynamic variables' in the manpage. -l - #!/bin/bash shopt -s expand_aliases ; alias my=declare alias array='my -a' int='my -i' ip=10.20.30.40 array parts=() array answers=( '10 20 30 40' '1 .2 .3 .4' '1 222 .3 .4' '192.168.0.1/24::{ [ipaddr]="192.168.0.1/24" [address]="192.168.0.1" [prefixlen]="24" ; }' ) array addr_fields=(ipaddr address prefixlen) assignparts() { parts=( $ip ) ; } tst0 () { my IFS=. ; assignparts ; echo -E "${parts[@]}"; } tst1 () { my IFS=0 ; assignparts ; echo -E "${parts[@]}"; } tst2 () { parts[1]=222; echo -E "${parts[@]}" ; } tst3 () { my ipaddr="$1"; shift; int status=0 my pat='^([^/]+)/([0-9]+)\s*$' my address prefixlen if [[ ! $ipaddr =~ $pat ]]; then echo >&2 "Error in ip/netsize format: \"$ipaddr\"" status=1 else address=${BASH_REMATCH[1]} prefixlen=${BASH_REMATCH[2]} my out="" for flds in "${addr_fields[@]}"; do out+="[$flds]=\"${!flds}\" " done printf "{ %s; }" "$out" fi return $status } int passno=0 tests=0 my fmt="Case %d got/Expected:\n \"%q\"\n \"%q\"\n" testor () { int tstno my out="" for tstno in {0..3}; do tests+=1 exp="${answers[tstno]}" if [[ $exp =~ :: ]]; then my args="${exp%::*}" exp="${exp#*::}" out="$(tst$tstno $args)" else out="$(tst$tstno)" fi if [[ $out != $exp ]]; then printf >&2 "$fmt" "$tstno" "$out" "$exp" continue fi passno+=1 done } testor echo "Passed $passno/$tests tests." output: Case 2 got/Expected: "222" "1\ 222\ .3\ .4" Case 3 got/Expected: "\{\ \[ipaddr\]=\"192.168.0.1/24\"\ \[address\]=\"192.168.0.1\"\ \[prefixlen\]=\"24\"\ \;\ \}" "\{\ \[ipaddr\]=\"192.168.0.1/24\"\ \[address\]=\"192.168.0.1\"\ \[prefixlen\]=\"24\"\ \;\ \}" Passed 2/4 tests. The outputs for case 3 look identical -- I was using %s to print them out, but switched to "%q", to ensure no hidden chars... case 2 -- ??? Why didn't it only change the 1 member?
Re: Dynamic variable failure & equiv-const strings compare unequal
Oleg Popov wrote: On Thu, Oct 22, 2015 at 03:01:06AM -0700, Linda Walsh wrote: [cut] I.e. test output was: Case 2 got/Expected: "222" "1\ 222\ .3\ .4" [cut] You didn't initialize the array. By the time you do "parts[1]=222" it's still empty. And in your previous message you tried to initialize it in a subshell. Variables don't retain their values after returning from subshells. I was testing if dynamic scoping included subshells, I didn't think so, but that doesn't mean I don't test it. I removed it though, as it confused the example. ip and 'parts' are both initialized in global. testor calls (tst0, tst1, tst2 & tst3). tst0 & tst1 both call "assignparts" which uses the global value of $ip to set the global value of parts. I.e. since neither "ip" nor 'parts' are declared inside of any of the functions, they should use the top-level global values, no? tst2, using the last global value set in tst1, only tries to change 1 value in 'parts'... i.e. why would 'ip' reference the global value of 'ip', but not parts? ip and parts are declared at the same scope (global), so why wouldn't the global 'parts' be initialized as well? Bash uses unquoted characters on the right side of == and != as wildcard patterns. For example, [ipaddr] in $exp means "any of i, p, a, d, r". You should quote the right operand. --- Ahh... or if I quote both sides using 'printf "%q"' first, that should do the same, no? So, ok, I get that one -- but the first looks sketchy, as ip and parts are both, only defined at the global level, thus my expectation that just like it used the global value of 'ip' for tst0 & tsts1 -- it should have stored the split version of it in the global value of parts... I really don't get why it would use the global value as a dynamic in 1 case but not the other...?
Re: Dynamic variable failure & equiv-const strings compare unequal
On 10/22/15 8:13 AM, Linda Walsh wrote: > > > Oleg Popov wrote: >> On Thu, Oct 22, 2015 at 03:01:06AM -0700, Linda Walsh wrote: >>> [cut] >>> I.e. test output was: >>> Case 2 got/Expected: >>> "222" >>> "1\ 222\ .3\ .4" >>> [cut] >> >> You didn't initialize the array. By the time you do "parts[1]=222" it's >> still empty. And in your previous message you tried to initialize it in a >> subshell. Variables don't retain their values after returning from >> subshells. > > I was testing if dynamic scoping included subshells, I didn't think so, > but that doesn't mean I don't test it. I removed > it though, as it confused the example. You don't show what you `removed', so I am looking at the original script you posted. > ip and 'parts' are both initialized in global. Yes. parts is assigned the empty array at the global scope. > testor calls (tst0, tst1, tst2 & tst3). In subshells started to run command substitution. Those subshells modify `parts', but the changed value is not reflected in the parent shell. > > tst0 & tst1 both call "assignparts" which uses the global > value of $ip to set the global value of parts. I.e. since > neither "ip" nor 'parts' are declared inside of any of the functions, they > should use the top-level global values, no? You. Run. Your. Tests. In. Subshells. > tst2, using the last global value set in tst1, only tries to > change 1 value in 'parts'... i.e. why would 'ip' reference the > global value of 'ip', but not parts? You modify parts in a subshell. The global value of parts is initialized to and remains an empty array. -- ``The lyf so short, the craft so long to lerne.'' - Chaucer ``Ars longa, vita brevis'' - Hippocrates Chet Ramey, ITS, CWRUc...@case.eduhttp://cnswww.cns.cwru.edu/~chet/
Re: Dynamic variable failure & equiv-const strings compare unequal
Oleg Popov wrote: $(...) is a subshell. Variables cannot be passed back from a subshell, no matter how and where they are declared. --- Um... oh.. in testor, still calling that way. I missed that. This is even more annoying for passing back results than I thought. Grrr.
Re: Dynamic variable failure & equiv-const strings compare unequal
On Thu, Oct 22, 2015 at 03:01:06AM -0700, Linda Walsh wrote: > [cut] > I.e. test output was: > Case 2 got/Expected: > "222" > "1\ 222\ .3\ .4" > [cut] You didn't initialize the array. By the time you do "parts[1]=222" it's still empty. And in your previous message you tried to initialize it in a subshell. Variables don't retain their values after returning from subshells. > [cut] > test 3 --- was supposed to check the values > of the hash field-by-field, but first I thought to check > them when printed out in a specific order as 2 identical > strings. The strings look identical, yet don't compare equal. > so it seemed pointless to try to break down test 3's output > into a field-by-field comparison when I couldn't even get > the identical strings that contained the fields to compare. > [cut] > [cut] >if [[ $out != $exp ]]; then > [cut] Bash uses unquoted characters on the right side of == and != as wildcard patterns. For example, [ipaddr] in $exp means "any of i, p, a, d, r". You should quote the right operand. > #!/bin/bash > > shopt -s expand_aliases ; alias my=declare > alias array='my -a' int='my -i' > > ip=10.20.30.40 > array parts=() > array answers=( '10 20 30 40' '1 .2 .3 .4' '1 222 .3 .4' > '192.168.0.1/24::{ [ipaddr]="192.168.0.1/24" [address]="192.168.0.1" > [prefixlen]="24" ; }' > ) > > > array addr_fields=(ipaddr address prefixlen) > > assignparts() { parts=( $ip ) ; } > > tst0 () { my IFS=. ; assignparts ; echo -E "${parts[@]}"; } > tst1 () { my IFS=0 ; assignparts ; echo -E "${parts[@]}"; } > tst2 () { parts[1]=222; echo -E "${parts[@]}" ; } > > tst3 () { > my ipaddr="$1"; shift; > int status=0 > my pat='^([^/]+)/([0-9]+)\s*$' > my address prefixlen > if [[ ! $ipaddr =~ $pat ]]; then >echo >&2 "Error in ip/netsize format: \"$ipaddr\"" >status=1 > else >address=${BASH_REMATCH[1]} >prefixlen=${BASH_REMATCH[2]} >my out="" >for flds in "${addr_fields[@]}"; do > out+="[$flds]=\"${!flds}\" " >done >printf "{ %s; }" "$out" > fi > return $status > } > > > > int passno=0 tests=0 > my fmt="Case %d got/Expected:\n \"%q\"\n \"%q\"\n" > > > testor () { > int tstno > my out="" > for tstno in {0..3}; do >tests+=1 >exp="${answers[tstno]}" >if [[ $exp =~ :: ]]; then > my args="${exp%::*}" > exp="${exp#*::}" > out="$(tst$tstno $args)" >else > out="$(tst$tstno)" >fi >if [[ $out != $exp ]]; then > printf >&2 "$fmt" "$tstno" "$out" "$exp" > continue >fi >passno+=1 > done > } > > testor > echo "Passed $passno/$tests tests." > > > output: > Case 2 got/Expected: > "222" > "1\ 222\ .3\ .4" > Case 3 got/Expected: > "\{\ \[ipaddr\]=\"192.168.0.1/24\"\ \[address\]=\"192.168.0.1\"\ > \[prefixlen\]=\"24\"\ \;\ \}" > "\{\ \[ipaddr\]=\"192.168.0.1/24\"\ \[address\]=\"192.168.0.1\"\ > \[prefixlen\]=\"24\"\ \;\ \}" > Passed 2/4 tests. > > The outputs for case 3 look identical -- I was using %s to print > them out, but switched to "%q", to ensure no hidden chars... > > case 2 -- > ??? Why didn't it only change the 1 member? > > > > > > >
Re: Dynamic variable failure & equiv-const strings compare unequal
On Thu, Oct 22, 2015 at 05:13:45AM -0700, Linda Walsh wrote: > Oleg Popov wrote: > > On Thu, Oct 22, 2015 at 03:01:06AM -0700, Linda Walsh wrote: > >> [cut] > >> I.e. test output was: > >> Case 2 got/Expected: > >> "222" > >> "1\ 222\ .3\ .4" > >> [cut] > > > > You didn't initialize the array. By the time you do "parts[1]=222" it's > > still empty. And in your previous message you tried to initialize it in > > a subshell. Variables don't retain their values after returning from > > subshells. > > I was testing if dynamic scoping included subshells, I > didn't think so, but that doesn't mean I don't test it. I removed > it though, as it confused the example. > > ip and 'parts' are both initialized in global. > > testor calls (tst0, tst1, tst2 & tst3). > > tst0 & tst1 both call "assignparts" which uses the global > value of $ip to set the global value of parts. I.e. since > neither "ip" nor 'parts' are declared inside of any of the > functions, they should use the top-level global values, no? > > tst2, using the last global value set in tst1, only tries to > change 1 value in 'parts'... i.e. why would 'ip' reference the > global value of 'ip', but not parts? > > ip and parts are declared at the same scope (global), so why > wouldn't the global 'parts' be initialized as well? $(...) is a subshell. Variables cannot be passed back from a subshell, no matter how and where they are declared. > > Bash uses unquoted characters on the right side of == and != as > > wildcard patterns. For example, [ipaddr] in $exp means "any of i, p, > > a, d, r". You should quote the right operand. > --- > Ahh... or if I quote both sides > using 'printf "%q"' first, that should do the same, no? No. Just use double quotes: [[ $var1 == "$var2" ]] > So, ok, I get that one -- but the first looks sketchy, > as ip and parts are both, only defined at the global level, > thus my expectation that just like it used the global value of > 'ip' for tst0 & tsts1 -- it should have stored the split version > of it in the global value of parts... I really don't get why > it would use the global value as a dynamic in 1 case but not > the other...?