tech-userlevel archive

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

${LINENO} implementation change for /bin/sh



I am about ready to commit the changes (additions really) to the
way that ${LINENO} works, which will have little effect on
${LINENO} iself, but which makes $((LINENO)) and export LINENO
work.

In fact, I am going to commit 2 versions, one right after the other.
(both so that we have a record of both ways this can be done, just
imagine that the first was done, then several months later a better
version came along...)

The first is the one I had done last time I sent a message about
this topic.   The one pretty much literally implemented the POSIX
spec...

LINENO  Set by the shell to a decimal number representing the current
        sequential line number (numbered starting with 1) within a script
        or function before it executes each command.

That helps, but actually isn't perfect - but is perhaps a little expensive.

The new (2nd) implementation defers setting LINENO until just before it is
referenced, so the shell only does the work when it is needed (it still
exports LINENO is requested, and if that is done, will need to set it
before every external command is run, but there's no significant extra
overhead if LINENO is not exported, unless you have a script that
references it every line...)  [The overhead compared with the rest of
what the shell is doing is very small in either case, but every little bit
helps.]

Further, in both versions the old LINENO hack is still there, so
${LINENO} doesn't really count as a reference to LINENO for this
purpose.   I suspect now (with the second implementation) that we
can probably do without the hack (just remove it completely) and
barely notice the difference - but I haven't tested that yet, and
removing it can certainly wait for later (maybe much later.)

With the second implementation, but not the first, even this bizarre
case works...

	case ${LINENO} in			# or $((LINENO)) would work as well
	( $((LINENO - 1)) )	echo "good";;
	(        *        ) echo "bad";;
	esac

which I have not been able to find another shell which produces
anything other than "bad".

The answer should be "good" (and will be for us) as, assumimg the "case"
line is line 100, then the next line is 101, so LINENO there is 101, and
101 - 1 == 100. which is a match for ${LINENO} on line 100.

The reason everyone else fails (including implementation #1 for us), is
that LINENO is set before the "case" command is executed, and then not
set again until the next command is ready (one of the two echo commands.)
So when $((LINENO - 1)) is evaluated, LINENO is still 100, and 100-1 is 99,
which is not 100, so ...

Note it is probable that POSIX does not actually require this to work,
and there's no practical use for it I can think of right now, but it
is kind of nice, and demonstrates that LINENO really is doing what it
should with the deferred evaluation.

The are two questions I still would appreciate answers to:

1) from the quote above:

	(numbered starting with 1) within a script or function

we have traditionally caused LINENO within a function to count from
one, so...

	func() { echo "${LINENO}"; }

is just a fancy way of doing "echo 1".   Most other (major) shells
either ignored that (forever) (or don't believe that the words in the
standard actually mean how we read them as) or started the same as us,
but then changed from that to simply keeping on counting lines from the
enclosing script, so that function would tell us, in those shells, which
line the function was defined on.

Both variants have advantages - the ksh93/bash/... (keep counting)
style means that if you have a long script, with functions, and you
get an error message from the script (from the script code, not from
the interpreter, the shell) something like

	detected failure at line 666

then you can just go to line 666 and see what is happening, if the
error happened to be within a function, it would work just the same.

With the other (NetBSD sh, and some others) interpretation, you would
get
	detected failure at line 13
which would then also need to have appended something like
	in function foobar
and the user would first need to find foobar, and then count down 13
lines to find the location of the problem.

On the other hand, I use lots of functions in my normal interactive
environment.  They come from a bunch of different init files (each
file has functions for a set of related purposes.)   Those files
keep getting changed (new functions added, implementations changed
or enhanced, sometimes old unused functions deleted).   But I only
tend to re-read the init file (after it has changed) into one of
my (many) interactive shells, if I need the changes in that shell,
which usually means 1 or 2 of the shells, and the others just keep using
things as they were before.

If in one of "the others", using a shell which keeps couning line
numbers, I run a function (which has not changed, or if I am using it
in this shell I would have re-read the init file) and it tells me
"error at line 123".   Now I go look at the init file that contains the
definition of that function, go to line 123, and find myself no-where
near the function in question.   And when I find the function, the line
number doesn't even give me a clue where in there I should be looking.
[This is just like trying to use gdb to debug a program, where the sources
have changed, a lot, since the program was compiled ... pretty much
impossible.]

And on the other hand again, if I have function relative line numbers,
I get told "error at line 27" (and probably also told in what function,
as above ... but here I already know that) so I read the init file,
find the function definition, go down 27 lines, and I am there.

So, both variants are useful, depending upon circumstances.   That's
the obvious place where we add an option, so it can work either way.
(It happens to be really cheap and easy to do this).   So I have done
that.  For now the option is called "funclinereset" (I always accept
suggestions for better names!) and has no short form.

[ For reference for the future, the option needs to be set, before the
shell starts reading the code that contains the function definition.
That means, the option setting, and the function definition,
cannot both be within the same compound statement - if they are the
shell will have read, and parsed (though not yet defined) the function
before the option setting takes effect.    I doubt that restriction
will make much difference to anyone though ...  it is just the way
the parser works. ]

Now to the question...  what should the default for this be?   Set,
so we retain compat (for scripts that do not set the option) with
the old NetBSD shell, and other ash derived shells (like the FreeBSD
shell, zsh, and dash) or reset, so we become compatible (by default) with
bash, ksh93 (and other ksh variants, including our /bin/ksh), yash, and
others ??


2) - this is a technical question rather than philosophical, and for
someone who knows (not me) there will probably be a simple answer...

As part of implementing the 2nd LINENO implementation variant, I need
to initialise an array of structs where the structs contain a union
(of two members.)   Until now, only the first member of the union
existed (there was no union...) and the init of the array (which wants
to be const, so it gets into text, rather than data) just happens as
normal.

Everything still works when the union is introduced, until I try and add
my new element to the array, in which I need to init the 2nd element of
the union, rather than the first.   gcc complains that the data type is
incorrect, and the compilation fails.

Now I suspect that there is some syntax trick that will let me do what
needs to be done - but I don't know what it is (I learned C way before
anything like this was conceivable .. or perhaps, back when the compiler
wouldn't care if the types didn't match, it just did what it was told,
and as long as the programmer made no mistakes, it all just worked...)

So, can someone give me an example of how to do this?

For now I have it working my moving the array into data (removing "const")
leaving out the init of the union in the static init, and doing the init
of that one field at run time during the shell startup (I know how to
write:
	array[N].union_name.element = value;
!!!)

While that lets me verify that the method works, it isn't ideal, and I'd
like to go back to the "const" version with static init everywhere.

So, please help!

kre



Home | Main Index | Thread Index | Old Index