[comp.unix.shell] How does the ksh ENV "trick" work?

eric@brolga.cc.uq.oz.au (Eric Halil) (05/27/91)

In "The Kornshell Command and Programming Language" by Morris I. Bolsky and
David G. Korn, Prentice-Hall, 1989, ISBN 0-13-516972-0, page 78 they give:

  export FILE=$HOME/.kshrc
  # The subscript below evaluates to 0 when interactive.
  ENV='${FILE[(_$-=0)+(_=1)-_${-%%*i*}]}'

as a easy way to stop your .kshrc being evaluated when it's an not an
interactive shell.  Can someone explain in detail how this works? 

Thanks,
Eric (eric@cc.uq.oz.au)

tim@ksr.com (Tim Peters) (05/27/91)

In article <1991May27.054451.24232@brolga.cc.uq.oz.au> eric@brolga.cc.uq.oz.au (Eric Halil) writes:
> In "The Kornshell Command and Programming Language" by Morris I. Bolsky and
> David G. Korn, Prentice-Hall, 1989, ISBN 0-13-516972-0, page 78 they give:
>
>  export FILE=$HOME/.kshrc
>  # The subscript below evaluates to 0 when interactive.
>  ENV='${FILE[(_$-=0)+(_=1)-_${-%%*i*}]}'
>
>as a easy way to stop your .kshrc being evaluated when it's an not an
>interactive shell.  Can someone explain in detail how this works? 

Patience!  It's a long sequence of obscure tricks, and it helps to start
backwards.

So, to start at the end, if a varible VAR is defined in ksh, then
${VAR[0]} has the same value as $VAR (try it! -- a scalar variable in
ksh is treated much like a 1-element array).

Thus having set FILE to $HOME/.kshrc, ${FILE[0]} is also $HOME/.kshrc,
and ${FILE[1]} is undefined (returns a null string, so long as nounset
(-u) hasn't been enabled).

Next, ENV is evaluated when ksh starts up.  The man page is pretty clear
about this, so suffice it to say that if you can arrange that ENV
evaluate to $HOME/.kshrc if the shell is interactive, and to a null
string if the shell is not interactive, then you're done (if ENV
evaluates to a null string, no file will be read up since no file whose
name is a null string will be found).

So we're all set if ENV evaluates to ${FILE[0]} if the shell is
interactive and to ${FILE[1]} if it's not.  The guts of the long
expression does just that:

	(_$-=0)+(_=1)-_${-%%*i*}

evaluates to 0 if the shell is interactive and to 1 if it's not.

To see how that works, break the expression up so it's clearer:

	(_$-=0) + (_=1) - _${-%%*i*}

The first term is

	_$-=0

and this does two things:

1) As a crucial side-effect, sets to 0 the value of the variable whose
   name is the result of evaluating _$- (more on that below).
2) Evaluates to 0.

Recall that $- by itself evaluates to the set of flags with which the
shell was invoked.  E.g., $- is typically "ismh" for interactive shells
(try doing "print $-" and see what you get).  The precise set of flags
isn't really important except that it will have an "i" in it if & only
if the shell is interactive.

Executing the "_$-=0" part creates a new variable with underscore as the
first letter of its name and the set of flags as the remaining letters
in its name.  So, e.g., if $- is ismh, this part of the expression sets
the variable _ismh to 0.

Next, the term

	(_=1)

is evaluated.  This again does two things:

1) As a crucial side-effect, sets the variable whose name is a single
   underscore to the value 1.
2) Evaluates to 1.

Next the 0 from evaluating the first term is added to the 1 from
evaluating the second term, and 0+1 = 1.  So the long subscript
expression

	(_$-=0) + (_=1) - _${-%%*i*}

has so far been reduced to

	1 - _${-%%*i*}

Courage!  We're almost done <grin>.  The only remaining step is to
puzzle out what

	_${-%%*i*}

does.  First look at the ${-%%*i*} part (i.e., ignore the underscore for
a while).  ${-%%*i*} is just an ordinary instance of the "expand a
variable removing the longest matching suffix" construct:  it expands to
the value of $-, removing the longest suffix matching pattern *i* (note:
you could just as well use "##" instead of "%%" in this context):

  - If $- has an "i" in it, the longest suffix matching *i* is the
    entire string, hence ${-%%*i*} evaluates to a null string.

  - If $- does not have an "i" in it, no suffix matches *i*, hence
    ${-%%*i*} evaluates to $-.

Finally, the value of ${-%%*i*} is attached to _, and the resulting
variable is evaluated:

  - If $- has an "i" in it, _${-%%*i*} first evaluates to plain _, and
    the variable whose name is _ was set to 1 as a side-effect of
    evaluating the second term.  So the whole subscript expression
    evaluates to 1-1 = 0, and ${FILE[0]} = $HOME/.kshrc is read up.

  - If $- does not have in "i" in it _${-%%*i*} first evaluates to
    _$- (where the $- part is whatever set of flags the shell was
    invoked with), and the variable whose name is *that* was set to 0 as
    a side-effect of evaluating the first term.  So the whole subscript
    expression in this case evaluates to 1-0 = 1, and ${FILE[1]}
    evaluates to a null string and so no file is read up.

Revolting, isn't it <grin>?  And to think I used to enjoy stuff like
this ...

suspecting-obscurity-is-the-secret-end-as-well-as-the-means-ly y'rs  - tim

Tim Peters   Kendall Square Research Corp
tim@ksr.com,         ksr!tim@uunet.uu.net

dickson@escob1.UUCP (Dave Dickson) (05/27/91)

eric@brolga.cc.uq.oz.au (Eric Halil) writes:

>In "The Kornshell Command and Programming Language" by Morris I. Bolsky and
>David G. Korn, Prentice-Hall, 1989, ISBN 0-13-516972-0, page 78 they give:

>  export FILE=$HOME/.kshrc
>  # The subscript below evaluates to 0 when interactive.
>  ENV='${FILE[(_$-=0)+(_=1)-_${-%%*i*}]}'

>as a easy way to stop your .kshrc being evaluated when it's an not an
>interactive shell.  Can someone explain in detail how this works? 

I asked myself the same thing one time and spent an hour or so figuring
it out.  I knew that I would not be able to remember how it worked, so
I wrote up the following. I hope this helps.


EXAMPLE from "The KORNSHELL Command and Programming Language"
	by David G. Korn:

START=~/.alias; export START
ENV='${START[(_$-=0)+(_=1)-_${-%%*i*}]}'


ENV can be set to so that it evaluates to a Null value if you
are in a non-interactive shell, or to a file name if in an
interactive shell.  The procedure is shown in "The KORNSHELL
Command and Programming Language" by David G.  Korn; however,
Korn gives no explanation as to how this works, this is an
attempt to explain it.

The assignment of a variable for the "Alias" file is straight
forward and requires no explanation.  (In the example "FILE" is
used, however, I will use "START," i.e., START=~/.alias; export
START.)  The evaluation of the subscript for "START" is the part
that requires an explanation.

The trick is to get the subscript for START to evaluate to a "0"
for an interactive shell and to a "1" for non-interactive shells,
thus ENV would be set to START[0] (~/.alias) for an interactive
shell, but would be set to "START[1]" (Null) for a
non-interactive shell.


Before we get started a couple of points should be made:

1) Within a subscript, arithmetic evaluation is used, meaning
that variable names (named parameters) and constants are treated
the same as within "((...))"  and hence, don't need to be
preceded by "$."

2) Because of the "'" (single quote),
'${START[(_$-=0)+(_=1)-_${-%%*i*}]}' is not evaluated until "ENV"
is referenced by the shell.

3) The "_" used in the statement is simply an identifier, just
like any other identifier and just as well could have been X, or
whatever.

4) Parameter "$-" holds the value of the Option Flags supplied
to KSH at invocation.  $- will include an "i" if interactive; if
the shell is non-interactive, no "i" will be present in parameter
$-.

5) It is important to understand that the evaluation of "var1"
and "var2", below, is always set to "0" and "1" respectively (the
names are set and the value assigned in one statement).


The statement is easier to understand, if you break the
subscript arithmetic down into 3 parts, "var1", "var2" and
"var3":

	var1)	"_$-=0"
	var2)	"_=1"
	var3)	"_${-%%*i*}"

and look at the evaluation of the subscript as being equal to:

	var1+var2-var3

or, START[var1 + var2 - var3].

EXAMPLE for an interactive shell with Option Flags imsh:

var1:
		"$-" evaluates to "imsh", a "_" is prepended to it and it is
		assigned the value of "0".  So, we now have a variable with
		a name of "_imsh" and it is set to "0":

		_imsh=0

var2:
		"_" is set to "1":

		_=1

var3:
		Var3 is set to "_" with the result of parameter deletion
		( ${-%%*i*} ) appended to it.  Since "$-" has an "i" in it,
		the result of the parameter deletion is Null, meaning that
		var3 is named "_", which has a value of "1":

		_		(this has a value of "1", see var2: above)


Evaluation:
	
	ENV='${START[(_$-=0)+(_=1)-_${-%%*i*}]}' ;
	ENV=${START[(_imsh)+(_)-_]}
	ENV=${START[(0)+(1)-1]}
	ENV=${START[0]}
	ENV=~/.alias


EXAMPLE for an non-interactive shell with Option Flags h:

var1:
		"$-" evaluates to "h", a "_" is prepended to it and it is
		assigned the value of "0".  So, we now have a variable with
		a name of "_h" and it is set to "0":

		_h=0

var2:
		"_" is set to "1":

		_=1

var3:
		Var3 is set to "_" with the result of parameter deletion
		( ${-%%*i*} ) appended to it.  Since "$-" has no "i" in it,
		there is no parameter deletion done meaning "var3" is named
		"_h", which has a value of "0":

		_h		(this has a value of "0", see var1: above)


Evaluation:
	
	ENV='${START[(_$-=0)+(_=1)-_${-%%*i*}]}' ;
	ENV=${START[(_h)+(_)-_h]}
	ENV=${START[(0)+(1)-0]}
	ENV=${START[1]}
	ENV=
-- 
David G. Dickson
Ohio Bell Telephone Co. (614-223-8134)
uunet!escob1!dickson