2026-08-26
You have a stream of lines. You want to transform one column, one regex match, or one byte range per line with some existing command — base64, dig, jq, whatever — and leave the rest untouched. The classical answers are all bad:
system() forks a subprocess per line. Fine for 20 lines, horror at 200,000.teip (by Yasuhiro Yamada, MIT-licensed Rust binary, in nixpkgs / brew / AUR / cargo install teip) is the tool for exactly this shape of problem. It masks the parts of stdin you don't want touched, streams only the masked substrings to a subcommand that runs once, then splices the transformed output back into the original positions verbatim.
# Uppercase only the 3rd whitespace field
$ echo "alpha bravo charlie delta" | teip -f 3 -- tr a-z A-Z
alpha bravo CHARLIE delta
# Reverse-lookup only the IPs in access-log lines
$ cat access.log | teip -og '\d+\.\d+\.\d+\.\d+' -- dig +short -x
router.lan. - - [26/Aug/2026:14:03:11] "GET / HTTP/1.1"
# jq only the JSON blob embedded in each syslog line
$ journalctl -u myapp | teip -og '\{.*\}' -- jq -c '.request_id'
# Base64-decode column 4 of a CSV (with proper quoting)
$ teip --csv -f 4 -- base64 -d < users.csv
# Redact credit-card-shaped numbers, everything else untouched
$ teip -og '\b\d{4}[- ]?\d{4}[- ]?\d{4}[- ]?\d{4}\b' -- \
sed 's/./*/g' < support-tickets.txt
# Parse only the timestamp column with dateutils
$ teip -D '\t' -f 2 -- dateutils.dconv -f '%s' < events.tsv
The flag zoo is small and orthogonal:
-f N — whitespace field N (or use -d , / -D REGEX for other delimiters)-c 5-12 — byte/character range, cut-style-l 3,5-8 — specific line numbers-g REGEX — transform whole regex match; -og matches "only" (grep-style)--csv — real CSV parsing, honours quoted commas-s — "solid" mode: pass the concatenated selection as one blob to the subcommand instead of line-by-line (needed when the transform collapses or multiplies lines)-I TAG — inline mode: put the selection where TAG appears in the command, so you can build up shell pipelines around itThe safety property is what makes teip trustworthy in production: unless you pass -s, teip refuses to splice back a subcommand output whose line count differs from what it sent. If your transform silently ate a line you get an error, not garbled output — the exact failure mode that eventually bites every hand-rolled awk-plus-system() pipeline.
Performance-wise, the subcommand runs once as a coprocess and streams. On a million-line log, replacing awk '{ "cmd " $3 | getline x; ... }' with teip commonly moves you from minutes to a couple of seconds because you stopped forking a million times.
Fun corner: teip -s -f 1- is a legitimate way to slurp all fields into a single subprocess call, which turns teip into a general "wrap this command around the whole stream, but I still want its output on the same lines" utility.
