Skip to content

Pipeline Tools

tee and xargs for pipeline manipulation.


Write pipeline input to one or more files while passing the original typed objects through to the next command.

tee [OPTIONS] [FILE...]
FlagDescriptionExample
-aAppend to files instead of overwritingls | tee -a log.txt

tee is a pipeline bridge command. It converts each input object to its BashText representation for file output, but passes the original typed objects through the pipeline unchanged.

This means downstream commands receive the same rich objects they would without tee in the pipeline. The file gets plain text; the pipeline gets types.

Terminal window
ls -la | tee listing.txt | grep '.ps1'

In this pipeline:

  • listing.txt receives the text rendering of every LsEntry
  • grep receives LsEntry[] objects and matches against their BashText
  • The final output is still typed LsEntry objects, not strings
Terminal window
$results = ls -la | tee listing.txt | grep '.ps1'
$results[0].GetType().Name # PSCustomObject (LsEntry type)
$results[0].Name # "PsBash.psm1"
$results[0].SizeBytes # 141234

Pass multiple file paths to write the same content to several files at once:

Terminal window
echo 'build started' | tee build.log /tmp/build-backup.log

Both build.log and /tmp/build-backup.log receive the text. The TextOutput object passes through.

Save directory listing while continuing the pipeline:

Terminal window
ls -la | tee listing.txt | grep '.ps1'
-rw-r--r-- 1 you you 141234 Apr 2 10:30 PsBash.psm1
-rw-r--r-- 1 you you 28400 Apr 2 10:28 PsBash.Tests.ps1

The file listing.txt contains the full ls -la output. The grep results are still LsEntry objects.

Append to a log file:

Terminal window
echo 'deploy started' | tee -a deploy.log
echo 'step 1 complete' | tee -a deploy.log
cat deploy.log
deploy started
step 1 complete

Each echo appends a line rather than overwriting.

Capture intermediate pipeline state for debugging:

Terminal window
cat data.csv | grep -v '^#' | tee filtered.csv | wc -l
42

The file filtered.csv captures what the pipeline looked like after grep -v removed comments. wc still counts the original objects.

Write to file and inspect object properties:

Terminal window
$entries = ls -la /tmp | tee /tmp/snapshot.txt
$entries[0].Permissions # "-rw-r--r--"
$entries[0].SizeBytes # 4096

If a target directory does not exist, tee reports an error for that file and continues writing to the remaining files:

Terminal window
echo 'hello' | tee /nonexistent/dir/file.txt output.txt
# tee: /nonexistent/dir/file.txt: No such file or directory

output.txt is still written. The object still passes through.

Terminal window
ls -la | tee listing.txt | grep '.ps1'

Bash tee writes raw text to files and passes the same text through stdout. Everything is unstructured text throughout.


Build and execute commands from standard input. Each input line becomes an argument to the specified command.

xargs [OPTIONS] COMMAND [INITIAL-ARGS...]
FlagDescriptionExample
-I REPLACEReplace occurrences of REPLACE in the command with each input line. Runs the command once per line.xargs -I {} echo 'Processing {}'
-n NUMPass at most NUM arguments per command invocationxargs -n 2 echo

xargs operates in one of three modes depending on the flags:

All input lines are collected and passed as arguments to a single invocation of the command.

Terminal window
echo -e 'one\ntwo\nthree' | xargs echo 'items:'
items: one two three

One command runs: echo 'items:' one two three.

xargs converts each piped object to its BashText representation, splits on newlines, and discards empty lines. Each non-empty line becomes one argument.

Terminal window
ls | xargs echo 'files:'

Each filename from ls becomes an argument to echo.

Delete matching files:

Terminal window
find . -name '*.tmp' | xargs rm -f

All .tmp files found by find are passed as arguments to a single rm -f invocation.

Process each line individually with a placeholder:

Terminal window
cat urls.txt | xargs -I {} echo 'Downloading {}'
Downloading https://example.com/file1.tar.gz
Downloading https://example.com/file2.tar.gz

Each URL is substituted into the echo command one at a time.

Batch arguments in groups:

Terminal window
echo -e 'a\nb\nc\nd\ne\nf' | xargs -n 3 echo 'batch:'
batch: a b c
batch: d e f

Chain with find to process discovered files:

Terminal window
find src -name '*.ps1' | xargs grep 'function'

Searches all .ps1 files under src/ for lines containing function.

Build commands from a file list:

Terminal window
cat manifest.txt | xargs -I {} cp {} /backup/

Copies each file listed in manifest.txt to the backup directory, one at a time.

Calling xargs with no command produces an error:

Terminal window
echo 'hello' | xargs
# xargs: no command specified
Terminal window
find . -name '*.tmp' | xargs rm -f

Bash xargs supports many additional flags (-0 for null-delimited input, -P for parallel execution, -t for tracing). It reads raw text from stdin.