Note that there are some explanatory texts on larger screens.

plurals
  1. POScoping anomalies with 'typeset -r' and 'readonly' keywords in ksh
    primarykey
    data
    text
    <p>First off, I am aware of the general scoping differences (dynamic/static) between bash and ksh when declaring functions using the <code>function</code> keyword vs. <code>myfunction()</code> and this post only discusses scoping issues with regards to readonly variables.</p> <p>Today I stumbled across something that confuses me, though. I have a script that sources files from within a function declared with the <code>function</code> keyword (and thus I was not immediately aware of why the following happened at all, as I looked at those separate files through "global-scope goggles"). As part of a recent cleanup I made various variables inside those sourced files readonly and noticed that some parts of the code stopped working under ksh93, depending on how I marked the variables as readonly. More specifically, if I used <code>readonly FOO=bar</code>, <code>${FOO}</code> would be unset for the remaining parts of the sourced file.</p> <p>This demonstrates the issue(s):</p> <p>(Note: The behaviour is the same with inlined code (vs. a second script that gets sourced), but since it saves some lines here and the post is pretty long already I kept it as it is)</p> <p>readonly_test_sourced.sh:</p> <pre><code>readonly foo=function typeset -r bar=function typeset baz=function readonly baz qux=function readonly qux quux=function typeset -r quux </code></pre> <p>readonly_test.sh:</p> <pre><code>function f { . ./readonly_test_sourced.sh printf "foo=${foo}\nbar=${bar}\nbaz=${baz}\nqux=${qux}\nquux=${quux}\n" } g() { . ./readonly_test_sourced.sh printf "foo=${foo}\nbar=${bar}\nbaz=${baz}\nqux=${qux}\nquux=${quux}\n" } [ -n "${KSH_VERSION}" ] &amp;&amp; echo "ksh: ${KSH_VERSION}" [ -n "${BASH_VERSION}" ] &amp;&amp; echo "bash: ${BASH_VERSION}" for var in foo bar baz qux quux; do unset "${var}" eval "$var=global" # don't do this at home, there are better ways done func="${1:-f}" echo echo "inside function ${func}" echo '----------------' ${func} echo echo "global scope after calling ${func}" echo '----------------------------' printf "foo=${foo}\nbar=${bar}\nbaz=${baz}\nqux=${qux}\nquux=${quux}\n" </code></pre> <p>This is the output I get with ksh93u+:</p> <pre><code>$ ksh ./readonly_test.sh f ksh: Version JM 93u+ 2012-02-29 inside function f ---------------- foo= bar=function baz=function qux= quux= global scope after calling f ---------------------------- foo=function bar=global baz=global qux=function quux=function $ ksh ./readonly_test.sh g ksh: Version JM 93u+ 2012-02-29 inside function g ---------------- foo=function bar=function baz=function qux=function quux=function global scope after calling g ---------------------------- foo=function bar=function baz=function qux=function quux=function </code></pre> <p>This is what I get with bash 4.2:</p> <pre><code>$ bash ./readonly_test.sh f bash: 4.2.42(1)-release inside function f ---------------- foo=function bar=function baz=function qux=function quux= global scope after calling f ---------------------------- foo=function bar=global baz=global qux=function quux=function $ bash ./readonly_test.sh g bash: 4.2.42(1)-release inside function g ---------------- foo=function bar=function baz=function qux=function quux= global scope after calling g ---------------------------- foo=function bar=global baz=global qux=function quux=function </code></pre> <p>I also ran it through mksh (which implements dynamic scoping according to its manpage, and it does yield the same results as with bash):</p> <pre><code>$ mksh ./readonly_test.sh f ksh: @(#)MIRBSD KSH R40 2012/03/20 inside function f ---------------- foo=function bar=function baz=function qux=function quux= global scope after calling f ---------------------------- foo=function bar=global baz=global qux=function quux=function $ mksh ./readonly_test.sh g ksh: @(#)MIRBSD KSH R40 2012/03/20 inside function g ---------------- foo=function bar=function baz=function qux=function quux= global scope after calling g ---------------------------- foo=function bar=global baz=global qux=function quux=function </code></pre> <p>Observations:</p> <ul> <li><p><code>readonly</code> and <code>typeset -r</code> are sometimes synonymous, sometimes not</p></li> <li><p>bash and mksh</p> <ul> <li><p>there are 3 possible different scenarios (both for f and g):</p> <ul> <li>[1] <code>readonly foo=function</code> assigns <code>'function'</code> to the global variable <code>foo</code> and does not declare a new local variable (identical to [4])</li> <li>[2] <code>typeset -r bar=function</code> assigns <code>'function'</code> to the new local variable <code>bar</code> (identical to [3])</li> <li>[3] <code>typeset baz=function; readonly baz</code> assigns <code>'function'</code> to the new local variable <code>baz</code> (identical to [2])</li> <li>[4] <code>qux=function; readonly qux</code> assigns <code>'function'</code> to the global variable <code>qux</code> and does not declare a new local variable (identical to [1]). <code>qux</code> is readonly in both local as well as global scope, but this is expected because it marks the global variable as readonly and due to dynamic scoping it becomes readonly in the function as well (side note: <a href="https://stackoverflow.com/questions/5005903/scoping-problems-in-different-shell-languages">see also</a>).</li> <li>[5] <code>quux=function; typeset -r quux</code> assigns <code>'function'</code> to the global variable <code>quux</code>, then declares a new local variable <code>quux</code> without value</li> </ul></li> <li><p><code>readonly</code> never declares a new (local) variables (as expected). For [1] and [4] it marks the global variables readonly, for [3] the new local variable. This is totally what I would expect and means the scope that <code>readonly</code> operates in is the same as for the respective variable itself, i.e.</p> <ul> <li><code>x=y</code>; if <code>$x</code> is local then <code>readonly x</code> would mark the local variable <code>x</code> readonly</li> <li><code>x=y</code>; if <code>$x</code> is global then <code>readonly x</code> would mark the global variable <code>x</code> readonly</li> </ul></li> <li><p>consistency between [1] / [2] and [4] / [5], respectively</p></li> </ul></li> <li><p>in ksh (discussing f; g behaves as expected):</p> <ul> <li>there are also 3 possible different scenarios: <ul> <li>[6] <code>readonly foo=function</code>: similar to [5], [8] and [9] but way more confusing as this is a single command (as opposed to: assignment first, <code>readonly</code>/<code>typeset -r</code> later). Apparently <code>readonly</code> declares a new local variable <code>foo</code> without value but sets the global variable <code>foo</code> to <code>'function'</code>. <strong>?!?</strong>. <code>foo</code> becomes readonly in both function as well as global scope.</li> <li>[7] <code>typeset -r bar=function</code> assigns <code>'function'</code> to the new local variable <code>bar</code> (identical to [8])</li> <li>[8] <code>typeset baz=function; readonly baz</code> assigns <code>'function'</code> to the new local variable <code>baz</code> (identical to [7]). <code>baz</code> becomes readonly only in function scope</li> <li>[9] <code>qux=function; readonly qux</code> assigns <code>'function'</code> to the global variable <code>qux</code>, then declares a new local variable <code>qux</code> without value (identical to [5], [6], [10]). <code>qux</code> becomes readonly only in function scope</li> <li>[10] <code>quux=function; typeset -r quux</code> assigns <code>'function'</code> to the global variable <code>quux</code>, then declares a new local variable <code>quux</code> without value (identical to [5], [9], [10]). <code>quux</code> becomes readonly only in function scope.</li> </ul></li> <li><code>readonly</code> does seem to declare new local variables in [6] and [9], but not in [8].</li> </ul></li> </ul> <p>The behaviour of bash is expected. <code>typeset</code> (= <code>declare</code>) creates/modifies in the scope of the function (bash has the <code>-g</code> option to force creation/modification in global scope even when used inside functions), <code>readonly</code> really only "marks" existing variables and never introduces new local variables. [5] puzzled me a little as I never had to declare an unset readonly variable before and I would have assumed that in case a global variable of the same name exists it modifies that, but I can absolutely live with this behaviour as it is consistent with the other scenarios and the existence of <code>-g</code>.</p> <p>But, to my understanding, the ksh man page fails to completely explain all of the above scenarios and the subtle differences between the <code>readonly</code> and <code>typeset -r</code> keywords, unless I missed something while re-reading the respective sections in questions. What is most confusing to me is that nowhere does it mention the keyword <code>readonly</code> near the explanation of the scoping differences between <code>foo()</code> and <code>function bar</code>, and the short explanation of the <code>readonly</code> inbuilt does not mention anything about this either. Based on that I would never assume that <code>readonly</code> introduces new, statically scoped variables like <code>typeset -r</code> does and it seems really unexpected that it does so in some (but not even all) scenarios.</p> <p>The most confusing scenario for me is [6] and I fail to understand what exactly happens there (this is also the specific scenario that broke my code).</p> <p>Are my observations correct and can someone shed light on the behaviour of ksh93? (I hope this question is acceptably scoped (no pun intended))</p>
    singulars
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    plurals
    1. This table or related slice is empty.
    1. This table or related slice is empty.
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload