tech-userlevel archive

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

Re: Add static local vars to sh(1) ?



  From: uwe%stderr.spb.ru@localhost (Valery Ushakov)
  Subject: Re: Add static local vars to sh(1) ?
  Date: Mon, 29 Jan 2018 13:38:25 +0000 (UTC)

  | This doesn't seem to mention what happens when the
  | function is called recursively.

Apologies.

I think I have said before that I much prefer writing C over
English...   Assistance to make the man page better will
be much appreciated.  Either now, or perhaps better, later
if/when we are closer to actually doing something with this,
beyond just considering whether it is a reasonable thing to do.

  | I assume it does the right thing,

Well, that depends upon what "the right thing" is in this
context.  But you can be sure that I won't commit something
that I don't believe is working correctly.  If we decide to
incorporate this, then when the ATF tests appear (which
will probably be just before the code, so I can verify that the
tests correctly skip themselves when static locals are not
supported) there will certainly be recursive functions included,
and tested, to verify that the results are at least as I expect.

Further, a little later (once I am using something better than
"Mail" (on my MTA) again) I can certainly send a "cvs diff" patch
to anyone who would like to review the code, and test it.

In considering what the right thing is, please remember that sh is
not C, its language is not C, and most especially, its variables
are nothing like what exists in C (or other declarative programming
languages).

In particular, a static variable in sh will not (and cannot)
be private to a function, the way it would be in C.

So, for eaxmple

	VAR="hello"
	f() { echo $VAR; }
	f

prints "hello" no surprise there.   Then add

	g()  { local VAR=goodbye; f; }
	g

prints "goodbye" (from the "echo" command in f, called from g) - that's
the way "local" has always worked, and needs to work (otherwise all
kinds of useful stuff becomes either impossible, or unexplainable.)

Making that instead be

	h() { local -S VAR="goodbye"; f; }
	h

changes nothing (will not change anything) about the way that works.
when f is run, it accesses the local static VAR from h (or g in the
earlier example).  Technically, all references to VAR access the
global (the one and only) var of that name, it is just that when g (or h)
is running, its value "hello" has been preserved in some anonymous place,
and replaced by "goodbye", with the "hello" value being restored
when g (or h) returns.

So if we run
	f; g; h; f
we get (in order, on a line each) hello goodbye goodbye hello

This is why the doc is all about saving and restoring, as that's
what local actually does - it does not create a new name space,
just preserves and restores the values.  That doesn't mean that
the doc is currently good however, it can certainly be made better.

That is

	fn() { local X; X=3; ... ; }

is more or less the same as

	fn() { saveX=$X; X=3; ...; X=$saveX; }

except that the script writer doesn't need to do all the
bookkeeping.   That is making sure to restore the external value
before each and every return from the function - and remember sh
has no "goto" so the C trick of "goto out;" isn't possible.  It is
possible to write a "restore" func and call it though, but that
still needs be be inserted at every return point (care also needs
to taken with the value of $? if the "return" has no operand).

And second, local doesn't have the problem that by attempting
(successfuly if the function not using "local" is written correctly)
of avoiding damaging an external var X (it needs to be more clever
than the above to cope with the possibility that X was unset before
fn was called) but in order to do so, it ends up clobbering the external
variable saveX instead (and that would make writing recursive funcs
with pseudo-local variables without the "local" command, very hard.).

So, the issue is when values get saved (which can only be when
the local command is executed, or when the function exits) and
when they get restored (same two possibilities).  So that is what
I attempted to convey.  Probably not well...

And while I am here, to answer another (private e-mail) query
about the doc, in case anyone else has the same question - static
vars will belong to a function definition.  One particular fn
definition.  (Nb: not one function name - if you redefine the
function all the old statics vanish, and a new set are created
when "local -S" runs in that redefined func.)  Other functions can
have their own locals (static or not) (with the same names, if they
like) - just the same as "local" (without -S) works.

All that's really intended to change is what value the variable
gets given when it is made local.

local -N makes it unset (the way bash, and some otehr shells do).

local -I (or just plain local in the NetBSD sh) makes it be inherited,
(ie: local does not change it) the way that ash based shells (including
NetBSD's) have always done.

Both of those are overridden by an init on the local cmd (local X=1).

Then local -S causes any of that init to only happen when the func
is executed the first time (really, when the "local -S" is executed in
this func for the very first time).  After that you get whatever value
the variable (the version of it available in this function after the
local command) last had.  It is essentially inherited from itself.
Changing the var init on local that way, and arranging to preserve
the return time value of a static local, so it is not lost like a
normal local var's value would be, is really all that is happening here.

kre

ps: given everything in sh is dynamic, defining functions, and
the local command included, and you can achieve some truly
bizarre effects by exploiting that if so inclined.  Eg: by
only sometimes making a var static in the function...

And lastly, apologies for being too classroomish, expecially
given I know that most of you know all of this already.


Home | Main Index | Thread Index | Old Index