Skip to content

interp: handle bash >|, >>|, &>|, &>>|, <> redirects#1333

Open
qiangli wants to merge 1 commit into
mvdan:masterfrom
qiangli:interp-bash-redirects
Open

interp: handle bash >|, >>|, &>|, &>>|, <> redirects#1333
qiangli wants to merge 1 commit into
mvdan:masterfrom
qiangli:interp-bash-redirects

Conversation

@qiangli
Copy link
Copy Markdown

@qiangli qiangli commented May 9, 2026

Summary

(*Runner).redir in interp/runner.go only threaded RdrIn, RdrOut, AppOut, RdrAll, and AppAll through its three dispatch switches. The remaining file-redirect operators that the parser already recognises — RdrClob (>|), AppClob (>>|), RdrAllClob (&>|), AppAllClob (&>>|), and RdrInOut (<>) — fell to the default: unhandled redirect op: %v branch and exited with an error, so any script using them failed even though plain >/>> worked.

This shows up most often in templates that wrap a user command in a fixed >| to force-overwrite a known temp file. With those failing, an embedded interpreter looks broken from the caller's perspective even when the user's command itself succeeded.

Approach

  • Treat the four *Clob variants as identical to their plain counterparts. They only differ from >/>>/&>/&>> in that they bypass the noclobber shell option (set -C), and the interpreter does not enforce noclobber on file redirects today, so the runtime behaviour is the same.
  • For <> (RdrInOut), open the target with O_RDWR|O_CREATE and bind it through stdinFile so reads work. Writes back through fd 0 are not propagated to the file because stdin is exposed as io.Reader internally — sufficient for the read-side use that accounts for nearly all real <> usage.

Test plan

  • Four entries added to the existing redirect table in interp/interp_test.go covering >| (new file, overwrite-existing) and <> (read-existing, create-if-missing).
  • go test -short ./interp/ ./syntax/ passes.
  • Manual: the new redirect entries match bash 5.3 behaviour.

The parser already recognises these tokens (RdrClob, AppClob,
RdrAllClob, AppAllClob, RdrInOut), but the runtime in
interp/runner.go only threaded RdrIn/RdrOut/AppOut/RdrAll/AppAll
through its three dispatch switches inside (*Runner).redir.
The remaining operators fell to the default branch and exited
with "unhandled redirect op", so any script using them failed
even though plain `>`/`>>` worked.

This shows up most often through templates that wrap user
commands in a `>|` to force-overwrite a known temp file (for
example, capturing $PWD across a persistent-shell invocation),
where the embedded interpreter's exit then propagates back to
the caller as a generic failure.

Treat the four "Clob" variants as identical to their plain
counterparts — they only differ from `>`/`>>`/`&>`/`&>>` in
that they bypass the noclobber shell option (set -C), and this
interpreter does not enforce noclobber on file redirects today.
RdrInOut (<>) opens the target with O_RDWR|O_CREATE and binds
it to the input fd; we plumb the result through stdinFile so
reads work. Writes back through fd 0 are not propagated since
stdin is exposed as io.Reader internally — sufficient for the
read-side use that accounts for nearly all real <> usage.

Adds four entries in the existing redirect table in
interp/interp_test.go covering >| (new file, overwrite) and
<> (read existing, create-if-missing).
@qiangli qiangli changed the title interp: handle bash >|, >>|, &>|, &>>|, <> redirects interp: handle bash >|, >>|, &>|, &>>|, <> redirects + dup pipe fds for EOF/SIGPIPE May 9, 2026
@qiangli qiangli force-pushed the interp-bash-redirects branch from 6df2b90 to 7f62652 Compare May 9, 2026 20:33
@qiangli qiangli changed the title interp: handle bash >|, >>|, &>|, &>>|, <> redirects + dup pipe fds for EOF/SIGPIPE interp: handle bash >|, >>|, &>|, &>>|, <> redirects May 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant