The shell that pointed at the wrong line
- #bash-compat
- #diagnostics
- #syntax-errors
- #set -x
A syntax error has two jobs. Say what is wrong, and say where. huck was doing the first one well and the second one badly — every fix in this run is about the second half of the message: the line number, the echoed source line, or which delimiter it names.
The line a mistake was reported on
Put a stray ;; inside an if block and huck told you the truth about the
token and lied about where it was:
# before
echo hello
if true
then
;;
fi
hello
f.sh: line 2: syntax error near unexpected token `;;'
f.sh: line 2: `if true'Line 2 is where the if started. The mistake is on line 4, and the echoed
source line — the thing you actually scan for the problem — showed you a line
that is perfectly fine. In a long script, that sends you to the wrong place
twice.
# after
hello
f.sh: line 4: syntax error near unexpected token `;;'
f.sh: line 4: `;;'It was filed as a bug about case. It turned out every multi-line compound had
it: if, while, until, for, case, brace groups, subshells and function
bodies all named their own first line.
The delimiter that ran out
When input ends in the middle of something, bash tells you which delimiter it was still looking for. huck named the wrong one whenever arithmetic was involved:
# before
echo hello
total=$((1 +
2 +
"3
hello
b.sh: line 5: unexpected EOF while looking for matching `)'There are two errors in one line of output. The unclosed thing is the quote, not the arithmetic — and it opened on line 4, not line 5. Both halves now match bash:
# after
hello
b.sh: line 4: unexpected EOF while looking for matching `"'The same family of fixes taught huck that $[ 1+2 — bash's deprecated spelling
of $(( )) — is closed by ] rather than ), and that an unterminated C-style
for (( )) header should name its delimiter at the header's line instead of
falling back to a generic "unexpected end of file" pointing at the bottom of the
file.
Reading a script from a pipe had a wording problem too. huck used a phrase bash never says:
# before
$ printf 'echo hello\necho "oops\n' | huck
hello
huck: line 2: syntax error: unexpected end of input
# after
hello
huck: line 2: unexpected EOF while looking for matching `"'Quotes nobody typed
Away from syntax errors, set -x had its own version of the same problem —
showing you something other than what you wrote. An empty assignment came back
wearing quotes:
# before
set -x
x=
y=""
+ x=''
+ y=''# after
+ x=
+ y=bash prints the bare x= for both. When you are reading a trace to find out
what a script actually did, an invented '' is a detail you have to stop and
distrust.
Trap actions had a related problem. The + marks in a trace count nesting
depth, so a handler's commands should stand out from the code they interrupted —
but huck traced them flat, at the depth of whatever was running:
# before
set -x
trap "echo a; echo b" USR1
kill -USR1 $$
+ trap 'echo a; echo b' USR1
+ kill -USR1 12345
+ echo a
+ echo b# after
+ trap 'echo a; echo b' USR1
+ kill -USR1 12345
++ echo a
++ echo bNothing in the "before" trace tells you that echo a came from a signal
handler rather than from the next line of your script.
What is still open
Measuring these turned up six more. One — the for (( )) header above — was
fixed in the same run; the other five are filed and open, and they are all the
same question: when input runs out inside something nested, which delimiter gets
named?
An unterminated ${…} inside
arithmetic names } where bash names the arithmetic delimiter. A
single quote inside a ${…}
in a double-quoted context is skipped for the same reason it is skipped when
expanding — except bash still names it. A
backslash-escaped quote is
treated as a real one, which also changes the expression bash would have
evaluated. A $(( that turns out to be
a command substitution loses
the line it opened on. And a for (( … ) header closed by
a single paren still gets the
generic message.
They interlock — fixing the ${…} one moves the answer for the single-quote one
without making it right — so the next pass here is probably one deliberate model
of what names what, rather than five more small fixes.