[Toybox] Would someone please explain what bash is doing here?

Rob Landley rob at landley.net
Tue Mar 31 21:42:26 PDT 2020



On 3/31/20 9:23 AM, Rob Landley wrote:
> 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!

$ export SECONDS
$ env | grep SECONDS
SECONDS=4
$ env | grep SECONDS
SECONDS=4
$ env | grep SECONDS
SECONDS=4
$ export SECONDS
$ env | grep SECONDS
SECONDS=4
$ echo $SECONDS
91
$ env | grep SECONDS
SECONDS=4

I do not understand the rationale behind this at all.

Rob



More information about the Toybox mailing list