tech-userlevel archive

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

Coming changes to /bin/sh



I have two changes ready to commit that I thought I would let
people know about, and solicit a few opinions as to a couple of
open issues...

The first is to implement the ";&" alternative to ";;" in case
statements - ";&" is "fallthrough", and exists in many other
shells ... actions that are connected to the following pattern
(if any) are executed after the actions for the current one (without
evaluating the pattern, or attempting to match it).

This was approved for the next (major) version of POSIX (issue 8)
back in 2011, and is (and was) implemented in most other shells
(just not ours.)   (No, I have no idea when issue8 will actually appear.)

As an exmaple of its use, the canonical example (from the forthcoming
POSIX text) is ...

x=0 y=1
case 1 in
  $((y=0)) ) ;;
  $((x=1)) ) ;&
  $((x=2)) ) echo $x.$y ;;
esac

which is required to emit "1.0" (and does with the changes I have planned,
but not yet committed).

The question here, is that bash (at least) also has a third form of
terminator to use instead of ";;" - ";;&".   That one causes fall
through to the next pattern which matches, that is, after the commands
associated with a pattern are executed, if those commands were terminated
with ";;&" then the shell will go on to look at the next pattern, evaluate
it, see if it matches, and if so, execute its commands (and repeat if it
also ends with ";;&") - if that pattern does not match, continue looking for
one that does (ie: after executing the ";;&" commands, everything continues
more or less as if the pattern which matched had not matched after all.)

I do not have an implementation of that, but I should be able to make it
happen if we want it.  The question is do we?   This one is not POSIX.


The second change is a bunch of work on ${LINENO}.   Some of you know,
I know, that the ${LINENO} implementation in our shell is something of
a hack (which nevertheless has some properties that I prefer over the
other implementations.)   But a hack it is.   In particular, to work
we must see $LINENO or ${LINENO} (or the brace form with one of the
conditional or modifier expressions as well).   In particular $((LINENO))
just gives 0 (there is not really a variable called LINENO to reference).

The changes I have ready, make LINENO into a proper variable, and I think
will make out implementation of LINENO the best that there is (all the other
shells I have tested have at least one defect in their implementations.)

The question here is what should we do with the hack?   While I haven't
tested it, I have tested enough to be fairly confident that if we just
delete it, everything will work, more or less like bash or ksh93.
But if we do, we lose one thing...

That is, as we have it now, a command like

echo "${LINENO}
${LINENO}
${LINENO}"

outputs N, N+1 and N+2 for the three instances of LINENO, neither bash
nor ksh93 do that, they either print N, three times, or N+2 three times
(ksh93 and bash resp.)   That is, a line number is associated iwth the
echo command, LINENO is set to that, and then the args are expanded,
replacing ${LINENO} with the same value every time it is encountered.

The same happens in any multi-line "string" which references ${LINENO}
(like here docs, the arg(s) to "eval", ...)

Now if it was just that, I wouldn't bother asking, "our way is better"
(NIH if you like...) but it does give rise to an anomaly, if the command
were instead ...

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

then where you'd kind of expect (perhaps hope) that each line would contain
the same number twice (and does in bash, and ksh93), with the way I have the
changes now, it does not for us - the ${LINENO} variants get expanded as
above, to the actual line number on which they appear (the hack), whereas
the $((LINENO)) forms look at the value of the LINENO variable when the
expansion is done, and so (just like the other shells) we get the same value
for each $((LINENO)) (it happens to be the number of the line containing
"echo" for us, as it is in ksh93).

So, what does everyone think?

Also, these changes will fix a bug with LINENO that I introduced
back when the here doc processing was changed, last year ... the
here doc text (in the -current sh) does not contribute to LINENO,
which continues counting just as if the here doc simply didn't exist.
(The fix could be made independanlly, it has nothing to do with the
rest of the changes - I just noticed the issue when testing all of this.)

For 99% of those of you who have read this far, stop now!

For the odd individual who really cares about the details, I am including
the script I am using to torture test this stuff...

First, here it is with line numbers (so you can see which line numbers make
sense in the output) - I will include it again, in raw form, right at the
end of the message for any of you who want to try running it on whatever
(Bourne style) shells you have available.

     1	echo "Line ${LINENO} at start"
     2	
     3	X=${LINENO}; echo "Multi-line string starts @${LINENO} ($((LINENO)))
     4	continues with line ${LINENO} ($((LINENO))) and
     5	also ${LINENO} ($((LINENO))) in a
     6	multi-line string ending at ${LINENO} ($((LINENO))) started @${X}"
     7	
     8	X=${LINENO}; echo "At start ${LINENO} ($((LINENO))) then "\
     9	\
    10	${LINENO} "($((LINENO)))" during and \
    11	\
    12	"${LINENO} ($((LINENO))) after continuation lines @ $X"
    13	
    14	X=${LINENO}; echo "Wacky weird case @$X" $\
    15	{\
    16	L\
    17	I\
    18	N\
    19	E\
    20	N\
    21	O\
    22	}
    23	
    24	X=${LINENO}; echo "Anded ${LINENO} ($((LINENO)))" &&
    25		echo "${LINENO} ($((LINENO)))" &&
    26		echo "${LINENO} ($((LINENO))) started at $X"
    27	
    28	X=${LINENO}; if echo "first in if cond @$X - ${LINENO} ($((LINENO)))"
    29		echo "and second in if test  - ${LINENO} ($((LINENO)))"
    30	then
    31		echo "${LINENO} ($((LINENO))) in then clause of if @ $X"
    32	fi
    33	
    34	X=${LINENO}; {
    35		echo "In a compound command @$X - ${LINENO} ($((LINENO)))"
    36	}
    37	
    38	X=${LINENO}; (
    39		echo "In a subshell @$X - ${LINENO} ($((LINENO)))"
    40	)
    41	
    42	X=${LINENO}; echo $(
    43		echo "in a cmdsub @$X - ${LINENO} ($((LINENO)))"
    44	)
    45	X=${LINENO}; echo `
    46		echo "in a backtick cmdsub @$X - ${LINENO} ($((LINENO)))"
    47	`
    48	
    49	X=${LINENO}; echo "In arithmetic @$X $(( LINENO )) or $(( $LINENO ))
    50	in multi-line arith $((
    51	 $LINENO +
    52	 $LINENO )) and unquoted multi-line arith:" $((
    53	 $LINENO
    54	+
    55	 $LINENO
    56	))
    57	
    58	X=${LINENO}; cat <<!
    59	In a heredoc started at $X on first line ${LINENO} ($((LINENO)))
    60	and 2nd line ${LINENO} ($((LINENO)))
    61	and last, and least: ${LINENO} ($((LINENO)))
    62	!
    63	
    64	X=${LINENO}; eval 'echo "An eval @$X line 1: ${LINENO} ($((LINENO)))"
    65				echo "Line 2: ${LINENO} ($((LINENO)))"'
    66	
    67	X=${LINENO}; . /tmp/D
    68	
    69	export LINENO; X=${LINENO}; printf 'exported LINENO @%d +1 = ' $X
    70		env | grep '^LINENO='; printf 'And from the line affer: '
    71		env | grep '^LINENO='

The file "/tmp/D" which is run as ". /tmp/D" near the end contains
just two lines ...

echo "First line in a dotfile @$X: ${LINENO}"
echo "Second and last line in dotfile: ${LINENO}"

but you could easily just delete the "." test, every shell (I tested)
handles ${LINENO} correctly in a "." file (that and the first simple
command are the only two that work the same everywhere.).  Of
course, all the other (troublesome) forms could occur in a '.' script,
but doing so would illustrate nothing extra that is useful.

The output my working-sources shell generate from this is ...

Line 1 at start
Multi-line string starts @3 (3)
continues with line 4 (3) and
also 5 (3) in a
multi-line string ending at 6 (3) started @3
At start 8 (8) then 10 (8) during and 12 (8) after continuation lines @ 8
Wacky weird case @14 22
Anded 24 (24)
25 (25)
26 (26) started at 24
first in if cond @28 - 28 (28)
and second in if test  - 29 (29)
31 (31) in then clause of if @ 28
In a compound command @34 - 35 (35)
In a subshell @38 - 39 (39)
in a cmdsub @42 - 43 (43)
in a backtick cmdsub @45 - 46 (46)
In arithmetic @49 49 or 49
in multi-line arith 103 and unquoted multi-line arith: 108
In a heredoc started at 58 on first line 59 (58)
and 2nd line 60 (58)
and last, and least: 61 (58)
An eval @64 line 1: 64 (64)
Line 2: 65 (65)
First line in a dotfile @67: 1
Second and last line in dotfile: 2
exported LINENO @69 +1 = LINENO=70
And from the line affer: LINENO=71

The values in parentheses (mostly) - that is, from $((LINENO)) are
what we would get for ${LINENO} if we remove the LINENO hack.

The output from bash (I am testing 4.3.30(1)-release -- I probably should
update it, but I doubt there have been many changes to this stuff).  It
makes no difference whether bash is running in posix mode or not.

Line 1 at start
Multi-line string starts @6 (6)
continues with line 6 (6) and
also 6 (6) in a
multi-line string ending at 6 (6) started @3
At start 10 (10) then 10 (10) during and 10 (10) after continuation lines @ 8
Wacky weird case @14 14
Anded 24 (24)
25 (25)
26 (26) started at 24
first in if cond @28 - 28 (28)
and second in if test  - 29 (29)
31 (31) in then clause of if @ 28
In a compound command @34 - 35 (35)
In a subshell @38 - 39 (39)
in a cmdsub @42 - 45 (45)
in a backtick cmdsub @45 - 48 (48)
In arithmetic @49 52 or 52
in multi-line arith 104 and unquoted multi-line arith: 104
In a heredoc started at 58 on first line 58 (58)
and 2nd line 58 (58)
and last, and least: 58 (58)
An eval @64 line 1: 65 (65)
Line 2: 66 (66)
First line in a dotfile @67: 1
Second and last line in dotfile: 2
exported LINENO @69 +1 = LINENO=69
And from the line affer: LINENO=69

Note in particular the very last test (exporting LINENO and seeing what
happens to its value, in the environment, doesn't work at all, the
exported value is just whatever LINENO was when it was exported.)

From ksh93 (the current version of ast-ksh in pkgsrc), we get

Line 1 at start
Multi-line string starts @3 (3)
continues with line 3 (3) and
also 3 (3) in a
multi-line string ending at 3 (3) started @3
At start 8 (8) then 8 (8) during and 8 (8) after continuation lines @ 8
Wacky weird case @14 14
Anded 24 (24)
25 (25)
26 (26) started at 24
first in if cond @28 - 28 (28)
and second in if test  - 29 (29)
31 (31) in then clause of if @ 28
In a compound command @34 - 35 (35)
In a subshell @38 - 39 (39)
in a cmdsub @42 - 43 (43)
in a backtick cmdsub @45 - 46 (46)
In arithmetic @49 49 or 49
in multi-line arith 98 and unquoted multi-line arith: 98
In a heredoc started at 58 on first line 58 (58)
and 2nd line 58 (58)
and last, and least: 58 (58)
An eval @64 line 1: 1 (1)
Line 2: 2 (2)
First line in a dotfile @67: 1
Second and last line in dotfile: 2
exported LINENO @69 +1 = LINENO=70
And from the line affer: LINENO=71

Here the exported LINNO works fine, but eval "string" is treated as a
script, and LINENO restarts from 1 in that string, which I don't think
is sane (that is supposed to happen in . scripts, and in functions, but
an eval'd string is neither of those.)   Bash doesn't get the line numbers
for the eval quite right (I am not sure what is happening there) but at
least they are close (off by one.)

I have tested most of the other Bourne like shells in pkgsrc, and a few
more I have access to, and all of those are even worse (and of course, the
shell distributed with NetBSD currently - all versions - is one of those.)

[You can run the script yourself with NetBSD-current's sh, don't bother
even trying using anything older.   Aside: the changes that have been made
in the past day or so are not critical to this, so a -current shell from
anytime relatively recently, since the new $(()) code was introduced, should
be OK, just the test with the var name split over multiple lines won't work
unless you have a -current shell from today (or later).]

kre

Here is the raw script...  To run it, create /tmp/D (content above) (or delete
that part of the test below), put this in a file ('x' permission not required),
and run it as "sh file" ... that's all it takes.  The first "echo" line
just below should be the first line of the file (to generate comparable results
with those above.)

echo "Line ${LINENO} at start"

X=${LINENO}; echo "Multi-line string starts @${LINENO} ($((LINENO)))
continues with line ${LINENO} ($((LINENO))) and
also ${LINENO} ($((LINENO))) in a
multi-line string ending at ${LINENO} ($((LINENO))) started @${X}"

X=${LINENO}; echo "At start ${LINENO} ($((LINENO))) then "\
\
${LINENO} "($((LINENO)))" during and \
\
"${LINENO} ($((LINENO))) after continuation lines @ $X"

X=${LINENO}; echo "Wacky weird case @$X" $\
{\
L\
I\
N\
E\
N\
O\
}

X=${LINENO}; echo "Anded ${LINENO} ($((LINENO)))" &&
	echo "${LINENO} ($((LINENO)))" &&
	echo "${LINENO} ($((LINENO))) started at $X"

X=${LINENO}; if echo "first in if cond @$X - ${LINENO} ($((LINENO)))"
	echo "and second in if test  - ${LINENO} ($((LINENO)))"
then
	echo "${LINENO} ($((LINENO))) in then clause of if @ $X"
fi

X=${LINENO}; {
	echo "In a compound command @$X - ${LINENO} ($((LINENO)))"
}

X=${LINENO}; (
	echo "In a subshell @$X - ${LINENO} ($((LINENO)))"
)

X=${LINENO}; echo $(
	echo "in a cmdsub @$X - ${LINENO} ($((LINENO)))"
)
X=${LINENO}; echo `
	echo "in a backtick cmdsub @$X - ${LINENO} ($((LINENO)))"
`

X=${LINENO}; echo "In arithmetic @$X $(( LINENO )) or $(( $LINENO ))
in multi-line arith $((
 $LINENO +
 $LINENO )) and unquoted multi-line arith:" $((
 $LINENO
+
 $LINENO
))

X=${LINENO}; cat <<!
In a heredoc started at $X on first line ${LINENO} ($((LINENO)))
and 2nd line ${LINENO} ($((LINENO)))
and last, and least: ${LINENO} ($((LINENO)))
!

X=${LINENO}; eval 'echo "An eval @$X line 1: ${LINENO} ($((LINENO)))"
			echo "Line 2: ${LINENO} ($((LINENO)))"'

X=${LINENO}; . /tmp/D

export LINENO; X=${LINENO}; printf 'exported LINENO @%d +1 = ' $X
	env | grep '^LINENO='; printf 'And from the line affer: '
	env | grep '^LINENO='




Home | Main Index | Thread Index | Old Index