[Toybox] Would someone please explain what bash is doing here?
Rob Landley
rob at landley.net
Tue Mar 31 07:23:03 PDT 2020
On 3/30/20 5:59 PM, Rob Landley wrote:
> On 3/30/20 5:45 PM, Rob Landley wrote:
>> Bash's handling of $SECONDS is kind of inconsistent:
>>
>> $ SECONDS=0; echo $SECONDS # why does this print nothing?
>> $ readonly SECONDS; declare -p SECONDS ; SECONDS=0
>> declare -ir SECONDS="6"
>> $ echo $SECONDS # readonly was ignored, assignment happened anyway
>> 10
>> $ SECONDS=123+456 # it STARTS integer, but integer assignment sets it to 0
>> $ echo $SECONDS
>> 6
>> $ declare -i SS
>> $ SS=123+456
>> $ echo $SS # integer assignment works elsewhere just fine?
>> 579
>>
>> I can't be the first person to test these corner cases, can I? (Version 4.4.12
>> as in devuan ascii.)
>>
>> The downside is I'm trying to get a test suite that both bash and toysh passes,
>> and to do that I'd have to reproduce it ignoring -i and ignoring -r and I kinda
>> dowanna?
>
> Aha:
>
> $ readonly SECONDS; SECONDS=0; echo $SECONDS; echo hello
> $ readonly SECONDS; SECONDS=0
> $ echo $?
> 1
>
> No error message. Line processing aborted despite ; not being &&.
>
> $ POTATO=abc; readonly POTATO; POTATO=42; echo $POTATO
> bash: POTATO: readonly variable
>
> Well, the line aborting despite ; not being && is at least _consistent_, but...
>
> $ POTATO=123 echo hello
> bash: POTATO: readonly variable
> hello
>
> Ok, "consistent" is the wrong word.
>
> $ bash -c $'SECONDS=42; readonly SECONDS; SECONDS=0\necho $SECONDS'
> 0
>
> And the assignment IS still zeroing it, despite the readonly and error.
>
> $ bash -c 'SECONDS=42; echo $SECONDS; SECONDS=123+456; echo $?; echo $SECONDS'
> 42
> 0
> 0
>
> And the "ignores integer, goes to zero instead" thing is not an error either.
>
> Sigh. At least all this nonsense is reproducible. I can add it to the test suite
> and make mine do what bash does, I just... really don't want to?
Did you know that when RANDOM is exported, the readonly attribute is ignored
(but prserved!) in a DIFFERENT way?
$ readonly RANDOM
$ export RANDOM=0
$ echo $RANDOM
24315
$ export RANDOM=42
$ echo $RANDOM
11151
$ declare -p RANDOM
declare -irx RANDOM="23481"
$ RANDOM=42
$ echo $RANDOM $RANDOM
17766 11151
Now the assignment goes through, and THEN it errors, I.E. the line is still
aborted, but for no obvious reason since the assignment was successful!
(Also, exporting resolves the variable once after assigning it, so instead of
giving the reset value it gives the resolved magic value, and the sequence
continues from there. Eh, I suppose that makes sense?)
Also, calling declare -p RANDOM re-exports the current value?
$ echo $RANDOM $RANDOM
17766 11151
$ declare -p RANDOM
declare -irx RANDOM="23481"
$ declare -p RANDOM
declare -irx RANDOM="32503"
$ env | grep RANDOM
RANDOM=32503
$ env | grep RANDOM
RANDOM=32503
but merely resolving it doesn't?
$ RANDOM=42
$ echo $RANDOM $RANDOM $RANDOM $RANDOM $RANDOM $RANDOM $RANDOM
17766 11151 23481 32503 7018 25817 28529
$ echo $RANDOM
9160
$ env | grep RANDOM
RANDOM=9160
$ echo $RANDOM
16666
$ env | grep RANDOM
RANDOM=9160
Ah, no, that's not what's happening:
$ export RANDOM=42
$ env | grep RANDOM
RANDOM=17766
$ declare -p RANDOM
declare -ix RANDOM="11151"
$ env | grep RANDOM
RANDOM=17766
The actual export is deferred until the first non-builtin is called (echo is a
builtin), but is then persistent. And the $RANDOM value is CACHED somehow?
(Notice how declare showed the value that THEN wound up in the environment?)
$ export RANDOM=42; echo $RANDOM; env | grep RANDOM
11151
RANDOM=11151
$ export RANDOM=42; echo $RANDOM $RANDOM; env | grep RANDOM
11151 23481
RANDOM=23481
Um, what?
$ export RANDOM=42; env | grep RANDOMRANDOM=17766
$ export RANDOM=42; echo $RANDOM $RANDOM $RANDOM
11151 23481 32503
$ export RANDOM=42; echo $RANDOM $RANDOM $RANDOM $RANDOM $RANDOM $RANDOM
11151 23481 32503 7018 25817 28529
$ export -n RANDOM
$ RANDOM=42; echo $RANDOM $RANDOM $RANDOM $RANDOM $RANDOM $RANDOM
17766 11151 23481 32503 7018 25817
So wait, exporting it resolves the value once but doesn't actually put the
resolved value into the ENVIRONMENT because that's deferred to exec time?
Then why did it... Wait, what?
To be honest, the behavior of BOTH $SECONDS and $RANDOM is insane enough I kinda
dowanna exactly match it? The first one I recorded millitime() instead of time()
so in _my_ shell:
$ bash -c 'SECONDS=42; for i in 1 2 3 4; do sleep .25; echo $SECONDS; done'
42
43
43
43
$ bash -c 'SECONDS=42; for i in 1 2 3 4; do sleep .25; echo $SECONDS; done'
42
42
42
43
gives consistent output.
And RANDOM... I understand wanting a repeatable sequence of pseudo-random
numbers, but I dunno what prng they're using so I can't get the SAME sequence
(ltrace isn't catching anything with "rand" in the name and I refuse to look at
GPLv3 source code and potentially open myself to insane
https://lwn.net/Articles/193852/ style lawsuits).
Plus I seem to have already done more testing on combining magic, global,
readonly, and integer flags than the cumulative history of the project so far.
Yes it still gets integer wrong:
$ bash -c 'RANDOM=42; echo $RANDOM $RANDOM $RANDOM'
17766 11151 23481
$ bash -c 'RANDOM=42; echo $RANDOM $RANDOM $RANDOM'
17766 11151 23481
$ bash -c 'RANDOM=40+2; echo $RANDOM $RANDOM $RANDOM'
16920 13741 518
$ bash -c 'RANDOM=0; echo $RANDOM $RANDOM'
20034 24315
$ bash -c 'RANDOM=potato; echo $RANDOM $RANDOM'
20034 24315
declare -p things it's integer, but assignment does not. In fact...
$ RANDOM=40; echo $RANDOM $RANDOM
16920 13741
Yup, smells like an atoi() with no error checking. (No WONDER this thing's a
megabyte executable, there don't seem to be any common codepaths ANYWHERE...)
Rob
P.S. Why is $GROUPS an array? If you just resolve $GROUPS you get $(id -g) but
wouldn't it make more sense since it's PLURAL for it to be $(id -G) since it's
NUMERIC and we have FOR LOOPS? I mean:
$ X="one two three"
$ for i in $X; do echo $i; done
one
two
three
Variable with spaces in it is a THING. Why gratuitously involve ARRAYS here? I'm
CONFUSED!
More information about the Toybox
mailing list