Skip to content

Commit 7f62652

Browse files
committed
interp: handle bash >|, >>|, &>|, &>>|, <> redirects
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).
1 parent 9e7dd28 commit 7f62652

2 files changed

Lines changed: 40 additions & 7 deletions

File tree

interp/interp_test.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1469,6 +1469,26 @@ var runTests = []runTest{
14691469
"{ echo a; echo b >&2; } &>/dev/null",
14701470
"",
14711471
},
1472+
{
1473+
// >| force-overwrite; equivalent to > when noclobber is unset.
1474+
"echo foo >| a; cat a",
1475+
"foo\n",
1476+
},
1477+
{
1478+
// >| overwrites an existing file.
1479+
"echo foo >a; echo bar >| a; cat a",
1480+
"bar\n",
1481+
},
1482+
{
1483+
// <> opens for read-write; the file must be readable as stdin.
1484+
"echo foo >a; cat <>a",
1485+
"foo\n",
1486+
},
1487+
{
1488+
// <> creates the target file if it does not exist.
1489+
"cat <>missing; ls missing",
1490+
"missing\n",
1491+
},
14721492
{
14731493
"sed 's/o/a/g' <<EOF\nfoo$foo\nEOF",
14741494
"faa\n",

interp/runner.go

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -998,8 +998,19 @@ func (r *Runner) redir(ctx context.Context, rd *syntax.Redirect) (io.Closer, err
998998
}
999999
return nil, nil
10001000
case syntax.RdrIn, syntax.RdrOut, syntax.AppOut,
1001-
syntax.RdrAll, syntax.AppAll:
1002-
// done further below
1001+
syntax.RdrAll, syntax.AppAll,
1002+
syntax.RdrClob, syntax.AppClob,
1003+
syntax.RdrAllClob, syntax.AppAllClob,
1004+
syntax.RdrInOut:
1005+
// done further below.
1006+
// The "Clob" variants (>|, >>|, &>|, &>>|) bypass the noclobber
1007+
// shell option (set -C). Since this interpreter does not enforce
1008+
// noclobber on file redirects, they are functionally identical to
1009+
// their plain counterparts.
1010+
// RdrInOut (<>) opens the target file for read+write and binds it
1011+
// to the input fd (default 0); we read from the resulting file as
1012+
// stdin. Writes back through fd 0 are not propagated to the file
1013+
// since stdin is plumbed as io.Reader internally.
10031014
case syntax.DplIn:
10041015
switch arg {
10051016
case "-":
@@ -1013,25 +1024,27 @@ func (r *Runner) redir(ctx context.Context, rd *syntax.Redirect) (io.Closer, err
10131024
}
10141025
mode := os.O_RDONLY
10151026
switch rd.Op {
1016-
case syntax.AppOut, syntax.AppAll:
1027+
case syntax.AppOut, syntax.AppAll, syntax.AppClob, syntax.AppAllClob:
10171028
mode = os.O_WRONLY | os.O_CREATE | os.O_APPEND
1018-
case syntax.RdrOut, syntax.RdrAll:
1029+
case syntax.RdrOut, syntax.RdrAll, syntax.RdrClob, syntax.RdrAllClob:
10191030
mode = os.O_WRONLY | os.O_CREATE | os.O_TRUNC
1031+
case syntax.RdrInOut:
1032+
mode = os.O_RDWR | os.O_CREATE
10201033
}
10211034
f, err := r.open(ctx, arg, mode, 0o644, true)
10221035
if err != nil {
10231036
return nil, err
10241037
}
10251038
switch rd.Op {
1026-
case syntax.RdrIn:
1039+
case syntax.RdrIn, syntax.RdrInOut:
10271040
stdin, err := stdinFile(f)
10281041
if err != nil {
10291042
return nil, err
10301043
}
10311044
r.stdin = stdin
1032-
case syntax.RdrOut, syntax.AppOut:
1045+
case syntax.RdrOut, syntax.AppOut, syntax.RdrClob, syntax.AppClob:
10331046
*orig = f
1034-
case syntax.RdrAll, syntax.AppAll:
1047+
case syntax.RdrAll, syntax.AppAll, syntax.RdrAllClob, syntax.AppAllClob:
10351048
r.stdout = f
10361049
r.stderr = f
10371050
default:

0 commit comments

Comments
 (0)