fix(read): assign the variables at end of input, and never a readonly one - #156
Merged
Merged
Conversation
… one
while IFS= read -r line || [ -n "$line" ]; do ...; done
is the loop that also reads a last line with no newline. On hellish it
never ended. At EOF with nothing read, builtin_read returned 1 without
assigning anything, so $line still held the last line and the `||`
kept the loop going. bash assigns the variables empty (and empties an
-a array) whenever read returns, EOF included. The plugins' envload and
envdiff had to work around this with an explicit `line=`.
Root cause: read_one_line returns NULL for "EOF, nothing read", and
builtin_read returned on NULL before rd_dispatch. It now dispatches an
empty line, so every variable is assigned, and still returns 1.
Assigning at EOF made a second bug reachable on a new path: read never
checked readonly. `readonly r=keep; echo x | read r` changed r. rd_set_var
now refuses a readonly name with bash's "r: readonly variable" message.
The statuses are bash's:
- a readonly variable before the last one: 2, and the ones after it
are not assigned;
- the last one: 1;
- REPLY: 2; an -a array: 1.
read is not a special builtin, so the shell goes on.
tests/scripts/58_read_eof_readonly.sh, graded against bash --posix:
- EOF with nothing read, for named variables, REPLY, -a, -d, -n and -N;
- a last line without a newline, and the `|| [ -n "$line" ]` loop
(guarded, so a regression fails instead of hanging);
- readonly in each position, REPLY, -a, and the messages.
develop diverges on 16 of its 26 lines and loops without the guard.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RAmeHfJNm7XjYbMNrQqvkG
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What & why
This is the loop that also reads a last line with no newline:
On hellish it never ended. At end of input, with nothing read,
readreturned 1 without assigning anything.$linestill held the last line, so the||kept the loop going. bash assigns the variables empty (and empties an-aarray) wheneverreadreturns, EOF included. The hellishrc_pluginsenvload/envdiffhad to work around this with an explicitline=before each read.Root cause.
read_one_linereturns NULL for "EOF, nothing read", andbuiltin_readreturned on NULL beforerd_dispatch. It now dispatches an empty line, so every variable is assigned. The status is still 1.A second bug this made reachable.
readnever checkedreadonly:readonly r=keep; echo x | read rchangedr.rd_set_varnow refuses a readonly name with bash'sr: readonly variable. The statuses are bash's:REPLY: 2;-aarray: 1.readis not a special builtin, so the shell goes on.How I verified it
Reproduced first.
tests/scripts/58_read_eof_readonly.shis graded againstbash --posix5.3.9. It covers:REPLY,-a,-d,-nand-N;|| [ -n "$line" ]loop (guarded, so a regression fails instead of hanging);REPLY,-a, and the messages.develop diverges on 16 of its 26 lines, and without the guard it loops forever. This branch matches bash byte for byte.
Local gates on this commit (ASan debug build):
tests/tester: 5363/5363;tests/run_scripts.shagainstbash --posix: 125/125;verify_alloc.sh: identical output on both heaps;alloc_stress.sh: all clean;tests/pty_suite.sh: 110 ok, 6 skipped, 3 failed. The three areprompt_compat_matrix,prompt_drift_matrixandprompt_jobs_badge. They expect the non-root%/$prompt and get#, because the container runs as root; they fail the same way on develop, and CI runs them as a normal user.norminetteis OK on every touched file.Notes / trade-offs
🤖 Generated with Claude Code
https://claude.ai/code/session_01RAmeHfJNm7XjYbMNrQqvkG
Generated by Claude Code