aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAgando <roduccomm@gmail.com>2026-10-27 16:26:30 +0100
committerAgando <roduccomm@gmail.com>2026-10-27 16:26:30 +0100
commit40c4f5ccd9a54b60cad3a3f29c6c9d32d3fbdac5 (patch)
tree868f4218586bd336c71486b2c990395009751256
parentcd71cc814f0d51c2d71fca4231690228a1e1da83 (diff)
fix(newos4): piped grep searched the working directory instead of its input
The stdin work in 5cf5010 fixed wc/head/tail but left grep alone, and grep's no-operand branch sweeps the cwd. Measured on v4.6.115: `cat /gdir/in.txt | grep zzz` answered `other.txt:1: zzz in a cwd file` — a file that was never piped, and `cat in.txt | grep bbb` reported the match twice (`in.txt` from the cwd sweep plus `__pipebuf__` from the injected file). - module-fs.js: when terminal._pipeInput is set and no file operand was given, grep searches the staged text and prints `<line>: <text>` (stdin has no filename, as in GNU). The cwd sweep stays for a bare interactive `grep x`. - shell.js: _isPipeAware now covers only sort/cut/uniq/expand. grep/head/tail/wc read _pipeInput themselves, so injecting the __pipebuf__ file produced duplicate hits and named stdin results. Verification: - test-shell-stdin.js 20 asserts (+4 for grep: no cwd match, non-match reports none, match prints no filename, -c counts stdin). RED on HEAD: G1/G2/G3 fail with `other.txt:1: zzz in a cwd file`. - Suite 18/18 files; maintain --fix "node tests 18/18 pass". - Live v4.6.116 after `cd /gdir` (where other.txt contains zzz): `cat /gdir/in.txt | grep zzz` -> (no matches) · `| grep bbb` -> `2: bbb` · `| grep -c b` -> 1 · `| head -n 2` -> aaa,bbb · `| tail -n 1` -> ccc · `| wc -l` -> 3. Credit: the false-positive grep was reported by the stdin subagent as an aside; re-derived here against the live terminal before fixing.
-rw-r--r--main.html6
-rw-r--r--modules/core/core-bundle.js16
-rw-r--r--modules/core/module-fs.js16
-rw-r--r--modules/core/module-loader.js2
-rw-r--r--modules/ext/critical-ext-bundle.js5
-rw-r--r--modules/ext/shell.js5
-rw-r--r--modules/test/test-shell-stdin.js27
7 files changed, 71 insertions, 6 deletions
diff --git a/main.html b/main.html
index 9496a15..e32de6e 100644
--- a/main.html
+++ b/main.html
@@ -1349,13 +1349,13 @@
</script>
<!-- v4.5.63: A2A scratchpad fixes + agent-register arg fix -->
-<script src="modules/core/core-bundle.js?v=v4.6.115"></script>
+<script src="modules/core/core-bundle.js?v=v4.6.116"></script>
<!-- Async dependency-ordered module loader now only initializes terminal and loads optional ext modules -->
-<script src="modules/core/module-loader.js?v=v4.6.115"></script>
+<script src="modules/core/module-loader.js?v=v4.6.116"></script>
<!-- Critical ext bundle loads AFTER webx11 exists so registerModule calls work -->
-<script src="modules/ext/critical-ext-bundle.js?v=v4.6.115"></script>
+<script src="modules/ext/critical-ext-bundle.js?v=v4.6.116"></script>
<script>
// ═══ ONE TRUE VERSION SOURCE ═══
diff --git a/modules/core/core-bundle.js b/modules/core/core-bundle.js
index 6f00658..1a92ef9 100644
--- a/modules/core/core-bundle.js
+++ b/modules/core/core-bundle.js
@@ -6828,6 +6828,22 @@ class FileSystemModule {
else this.terminal.print(grand + ' match(es)' + (multi ? ' in ' + readFiles + ' file(s)' : ''), 'text-muted');
}
} else {
+ // Piped input wins over the cwd sweep. `cat f | grep x` used to grep the working
+ // directory, so it could match a file that was never piped — measured 2026-10-27:
+ // `cat /gdir/in.txt | grep zzz` answered "other.txt:1: zzz in a cwd file".
+ const piped = this.terminal._pipeInput;
+ if (piped !== null && piped !== undefined) {
+ let pipedTotal = 0;
+ String(piped).split('\n').forEach((line, i) => {
+ if (!hits(line)) return;
+ pipedTotal++;
+ if (!opt.count && !opt.files) this.terminal.print((i + 1) + ': ' + line);
+ });
+ if (opt.files) { if (pipedTotal) this.terminal.print('(stdin)'); }
+ else if (opt.count) this.terminal.print(String(pipedTotal));
+ else if (pipedTotal === 0) this.terminal.print('(no matches)', 'info');
+ return;
+ }
const dir = this.terminal.fs.getCurrentDir();
if (!dir) return this.terminal.print('grep: cannot search', 'error');
let total = 0;
diff --git a/modules/core/module-fs.js b/modules/core/module-fs.js
index 757d1da..0dea0d5 100644
--- a/modules/core/module-fs.js
+++ b/modules/core/module-fs.js
@@ -305,6 +305,22 @@ class FileSystemModule {
else this.terminal.print(grand + ' match(es)' + (multi ? ' in ' + readFiles + ' file(s)' : ''), 'text-muted');
}
} else {
+ // Piped input wins over the cwd sweep. `cat f | grep x` used to grep the working
+ // directory, so it could match a file that was never piped — measured 2026-10-27:
+ // `cat /gdir/in.txt | grep zzz` answered "other.txt:1: zzz in a cwd file".
+ const piped = this.terminal._pipeInput;
+ if (piped !== null && piped !== undefined) {
+ let pipedTotal = 0;
+ String(piped).split('\n').forEach((line, i) => {
+ if (!hits(line)) return;
+ pipedTotal++;
+ if (!opt.count && !opt.files) this.terminal.print((i + 1) + ': ' + line);
+ });
+ if (opt.files) { if (pipedTotal) this.terminal.print('(stdin)'); }
+ else if (opt.count) this.terminal.print(String(pipedTotal));
+ else if (pipedTotal === 0) this.terminal.print('(no matches)', 'info');
+ return;
+ }
const dir = this.terminal.fs.getCurrentDir();
if (!dir) return this.terminal.print('grep: cannot search', 'error');
let total = 0;
diff --git a/modules/core/module-loader.js b/modules/core/module-loader.js
index 29a1b62..b74987e 100644
--- a/modules/core/module-loader.js
+++ b/modules/core/module-loader.js
@@ -4,7 +4,7 @@
// Core module bundle loaded synchronously in main.html.
// This loader only handles version cache-busting, splash, terminal init, and optional ext modules.
- const LATEST_VER = 'v4.6.115';
+ const LATEST_VER = 'v4.6.116';
const CACHE = '?v=' + LATEST_VER;
// Exposed so ext modules can register their own URL with the same cache-busting version
// instead of an unversioned path the browser may already have cached (shell.js did).
diff --git a/modules/ext/critical-ext-bundle.js b/modules/ext/critical-ext-bundle.js
index fb623fd..f892617 100644
--- a/modules/ext/critical-ext-bundle.js
+++ b/modules/ext/critical-ext-bundle.js
@@ -594,7 +594,10 @@ class ShellModule {
// Commands that read from files and should use the pipe buffer file. wc is NOT here any
// more: it reads terminal._pipeInput natively (v4.6.111), and injecting __pipebuf__ gave
// `3 __pipebuf__` instead of a bare count.
- return ['grep', 'head', 'tail', 'sort', 'cut', 'uniq', 'expand'].includes(cmd);
+ // grep/head/tail/wc read terminal._pipeInput themselves (v4.6.111-116). Injecting the
+ // __pipebuf__ file for them made `cat f | grep x` match twice (the pipe buffer AND the cwd
+ // file of the same name) and printed "3 __pipebuf__" from wc.
+ return ['sort', 'cut', 'uniq', 'expand'].includes(cmd);
}
// ── Variable substitution ──
diff --git a/modules/ext/shell.js b/modules/ext/shell.js
index 07f175b..ad66e63 100644
--- a/modules/ext/shell.js
+++ b/modules/ext/shell.js
@@ -458,7 +458,10 @@ class ShellModule {
// Commands that read from files and should use the pipe buffer file. wc is NOT here any
// more: it reads terminal._pipeInput natively (v4.6.111), and injecting __pipebuf__ gave
// `3 __pipebuf__` instead of a bare count.
- return ['grep', 'head', 'tail', 'sort', 'cut', 'uniq', 'expand'].includes(cmd);
+ // grep/head/tail/wc read terminal._pipeInput themselves (v4.6.111-116). Injecting the
+ // __pipebuf__ file for them made `cat f | grep x` match twice (the pipe buffer AND the cwd
+ // file of the same name) and printed "3 __pipebuf__" from wc.
+ return ['sort', 'cut', 'uniq', 'expand'].includes(cmd);
}
// ── Variable substitution ──
diff --git a/modules/test/test-shell-stdin.js b/modules/test/test-shell-stdin.js
index 10ba190..1b0fbca 100644
--- a/modules/test/test-shell-stdin.js
+++ b/modules/test/test-shell-stdin.js
@@ -29,6 +29,33 @@ const mk = (files, pipeInput) => {
};
const last = (out) => out.length ? out[out.length - 1] : '<nothing printed>';
+// (g) grep over a pipe must search the piped text, never the working directory.
+// Measured 2026-10-27 on v4.6.115: `cat /gdir/in.txt | grep zzz` answered
+// "other.txt:1: zzz in a cwd file" — a file that was never piped.
+{
+ const decoy = {
+ readFile: () => null,
+ getCurrentDir: () => ({ name: 'gdir', content: {
+ 'other.txt': { type: 'file', content: 'zzz in a cwd file' },
+ 'in.txt': { type: 'file', content: 'aaa\nbbb\nccc' },
+ } }),
+ };
+ const out = [];
+ const term = { fs: decoy, _pipeInput: 'aaa\nbbb\nccc', print: (m) => out.push(String(m)) };
+ const m = new FileSystemModule();
+ m.terminal = term; m.registerCommands();
+ m.commands.grep(['zzz']);
+ ok('G1 grep over a pipe does not search the cwd', !out.join('|').includes('other.txt'), JSON.stringify(out));
+ ok('G2 a non-match over a pipe reports no matches', out.join('|').includes('(no matches)'), JSON.stringify(out));
+
+ out.length = 0;
+ m.commands.grep(['bbb']);
+ ok('G3 a match prints the line number without a filename', out.length === 1 && out[0] === '2: bbb', JSON.stringify(out));
+
+ out.length = 0;
+ m.commands.grep(['-c', 'b']);
+ ok('G4 grep -c over a pipe counts stdin lines', out.length === 1 && out[0] === '1', JSON.stringify(out));
+}
// (a) wc -l over 3 lines of stdin
{
const { m, out } = mk({}, 'alpha\nbeta\ngamma');