Documentation
¶
Overview ¶
Package tee implements the tee builtin command.
tee — read from standard input and write to standard output and files
Usage: tee [OPTION]... [FILE]...
Copy standard input to standard output, making a copy in each FILE operand. A FILE operand of "-" is opened as a literal file named "-" through the sandbox like any other operand — GNU tee treated "-" as an alias for stdout in coreutils 5.3.0 through 8.23, but that behavior was removed in 8.24 as mandated by POSIX, and this implementation matches current GNU tee rather than the historical behavior.
tee mutates file content, so — like truncate, logrotate, rm, and systemctl — it is only available in remediation mode. This is enforced both at interpreter dispatch (RemediationOnly) and again inside the handler as defence in depth, per docs/RULES.md.
Accepted flags:
-a, --append
Append to the given FILEs, rather than overwriting them.
-h, --help
Print usage to stdout and exit 0.
Out of scope (not implemented; rejected as unknown flags):
-i, --ignore-interrupts ignore SIGINT — rshell builtins do not
receive process signals the way a forked
tee(1) would, so this flag has no
meaningful effect to implement.
-p, --output-error[=MODE] behavior on write error / FIFO-aware
diagnostics — every FIFO write target is
already rejected before open (see "File
access" below), so the "pipe error" modes
GNU tee -p exists for cannot occur here.
File access:
Every FILE operand is opened via callCtx.OpenFile with O_WRONLY | O_CREATE, plus O_APPEND (-a) or O_TRUNC (default). This routes through the same AllowedPaths sandbox and remediation-mode gate that backs the shell's own >/>> redirects and the truncate builtin: writes are confined to :rw roots and a multiply linked (hard-linked) regular file is rejected as a write target. A non-regular target (FIFO, socket, device) is rejected by a non-blocking pre-open StatFile check (rejectNonRegularTarget) so the shell does not block waiting for a FIFO reader; see that function's doc comment for the narrow TOCTOU window this leaves and why it is an accepted, low-severity gap shared with the interpreter's own >/>> redirects. See the hard link entry in AGENTS.md/docs/RULES.md for the shared hard-link mechanism.
Exit codes:
0 Standard input was fully copied to standard output and to every FILE operand without error. 1 Standard input could not be fully read, or at least one FILE operand could not be opened or written. tee continues copying to the remaining destinations after a single non-pipe destination fails (open/write/close error), matching GNU tee's default "diagnose errors writing to non pipe outputs" behavior. A broken pipe (EPIPE) on any destination — in practice this is standard output when a downstream reader like `head` exits early — is handled differently: matching GNU tee's other documented default, "exit immediately on error writing to a pipe", the whole copy stops there rather than continuing to write to the remaining destinations. This case exits 0, matching cat's handling of the same condition: a downstream reader exiting early is normal, expected pipeline behavior, not a reportable error.
Memory safety:
Input is streamed from stdin in fixed-size chunks (teeBufSize, 32 KiB) and fanned out to standard output and every open FILE in the same iteration — no buffering proportional to total input size. The read loop checks ctx.Err() before every Read to honour the shell's execution timeout and support graceful cancellation, and also returns as soon as every destination (including standard output) has stopped accepting writes, rather than draining a long-lived or infinite stdin source to no purpose. The number of concurrently open file descriptors is bounded by MaxFileOperands, checked before any FILE is opened, so a glob or repeated operand cannot exhaust the embedding process's descriptor table; a later duplicate name reuses its own file handle rather than reopening (matching GNU tee's write-order and open-once semantics).
Index ¶
Constants ¶
const MaxFileOperands = 64
MaxFileOperands is the maximum number of FILE operands accepted by a single tee invocation. Every accepted operand is opened and held open concurrently for the whole duration of the copy (see the package doc comment) — unlike jq/sha256sum, which open, process, and close one file at a time, tee cannot release a descriptor until every destination has been written to and closed. An unbounded operand count — easily reached through shell glob expansion — would therefore open and hold an unbounded number of file descriptors, exhausting the embedding process's descriptor table (starving unrelated concurrent work in that process) well before any generous-looking cap like 1,024 is reached: macOS's default soft RLIMIT_NOFILE is 256, and a cap needs headroom below the lowest common default for stdin/stdout/stderr and any sandbox-internal descriptors already open in the embedding process. 64 matches jq's existing MaxOperands precedent for simultaneously-relevant file operands, with a wide margin below 256. Exceeding the limit rejects the entire command before any destination is opened.
Variables ¶
var Cmd = builtins.Command{ Name: "tee", Description: "read from standard input and write to standard output and files", MakeFlags: registerFlags, RemediationOnly: true, RemediationDeniedMessage: readOnlyMessage, }
Cmd is the tee builtin command descriptor.
Functions ¶
This section is empty.
Types ¶
This section is empty.