On 17/09/2018 23:46, Max Rees wrote:
Hello,
While running the shunit2 test suite[1][2] against dash, the following
problem presented itself: when multiple layers of indirection are used,
$LINENO yields a negative number. It appears that the root of the
problem is that lineno is decremented in multiple places in src/eval.c
with no lower bound, but I wasn't sure how to fix that. A minimal test
case follows below.
Hi,
A more minimal test case is
:
:
f() { eval echo \$LINENO; }
f
When this script is parsed, the function definition gets an associated
line number 3, and the eval command invocation also gets an associated
line number 3. POSIX says
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. If the user unsets or resets LINENO, the variable may lose its special meaning for the life of the shell. If the shell is not currently executing a script or function, the value of LINENO is unspecified. This volume of POSIX.1-2017 specifies the effects of the variable only for systems supporting the User Portability Utilities option.
I have interpreted this as saying that when a script defines a function,
inside that function, lines are numbered starting from 1 again. So when
the eval command is executed, LINENO should be set to 1. This is
achieved by subtracting the function definition's line number (less one).
This is also how zsh has interpreted the spec. However, most other
shells agree with bash, which says:
x. The expansion of $LINENO inside a shell function is only relative to the
function start if the shell is interactive -- if the shell is running a
script, $LINENO expands to the line number in the script. This is as
POSIX-2001 requires.
Continuing with the original interpretation:
When the eval command executes, the echo command is parsed in isolation.
It gets an associated line number 1. But it is executed in the context
of the function f, so f's associated line number is taken off again. The
result is 1 - (3 - 1) == -1.
There are multiple possible approaches to change this behaviour,
depending on the intended behaviour.
1: Function definitions could be ignored for LINENO purposes if the
function definitions are in a script. Since that takes out the
subtraction, it would at least get rid of the negative numbers.
2: Function definitions' commands could be ignored for LINENO purposes.
This preserves the outer-most command's line number. Since that also
takes out the subtraction, it would at least get rid of the negative
numbers.
3: eval's commands could be executed as if they are not in the context
of a function. This would mean that eval 'echo $LINENO' always prints 1.
4: eval's commands could be executed as if they are in the context of a
function with a faked associated line number, so that echo $LINENO and
eval 'echo $LINENO' always print the same number, but eval 'echo $LINENO
echo $LINENO' prints two different line numbers.
5: The commands eval sees after parsing could get a fixed associated
line number, so that eval 'echo $LINENO
echo $LINENO' always prints '0 0' or '1 1'.
6: The commands eval sees after parsing could get eval's associated line
number, so that echo $LINENO and eval 'echo $LINENO' always print the
same number, and eval 'echo $LINENO
echo $LINENO' prints the same line number twice. As long as the user
never modifies LINENO, this can also be equivalently stated as: the
commands eval sees after parsing could avoid setting LINENO.
Testing the behaviour of various shells shows, taking dash as the baseline:
bash | 1 4
bosh | 2 5
ksh | 1 3
mksh | 1 6
pdksh | 1 5
posh | 1 5
yash | 1 3
zsh | 6
Personally, I am in favour of 1. It is more sensible behaviour and I
only implemented it the way it is now because of what POSIX said. If a
different, equally reasonable interpretation can get a more desirable
outcome, I would suggest going with that interpretation. It involves a
bit more than just taking out the subtractions: it needs a bit of
thinking to figure out how to ensure LINENO does get set properly in
functions in interactive shells.
As for the rest, unless there are strong reasons to go after a
particular result, since POSIX says the results are unspecified, I would
suggest going with whatever is the simplest to implement.
Cheers,
Harald van Dijk
Please CC me on replies - I'm not subscribed.
Max