CLI Pipelines: Keeping Data in the Stream
How to work with command output without reaching for the mouse. The core idea: output is input to the next command, not text to be read and retyped.
The fundamental building block. | sends stdout of one command to stdin of the
next.
# Count Go filesfind . -name "*.go" | wc -l
# Find the 5 largest filesdu -sh * | sort -rh | head -5
# Unique sorted IPs from a logawk '{print $1}' access.log | sort -u
# Chain as many as you needcat /etc/passwd | cut -d: -f1 | sort | head -20Pipe to clipboard
Section titled “Pipe to clipboard”Skip the select-and-copy step entirely.
# macOSecho "something useful" | pbcopypbpaste # retrieve it
# Linux (X11)echo "something useful" | xclip -selection clipboardxclip -selection clipboard -o
# Linux (Wayland)echo "something useful" | wl-copywl-pasteCommand Substitution
Section titled “Command Substitution”Capture output inline with $(). The shell runs the inner command first and
substitutes the result.
# Open all files containing TODOnvim $(rg -l "TODO")
# cd into a found directorycd $(fd -t d "components" | head -1)
# Use a timestamp in a filenametar czf "backup-$(date +%Y%m%d).tar.gz" ./src
# Assign to a variablecurrent_branch=$(git branch --show-current)echo "On branch: $current_branch"Nesting
Section titled “Nesting”# Count lines in the most recently modified filewc -l $(ls -t *.log | head -1)History Expansion
Section titled “History Expansion”Reuse pieces of previous commands without retyping.
| Expansion | Meaning |
|---|---|
!! | Last command, entire line |
!$ | Last argument of previous command |
!^ | First argument of previous command |
!* | All arguments of previous command |
!:2 | Second argument of previous command |
$_ | Last argument (works in scripts) |
!-2 | Command two entries back |
# Rerun with sudoapt install nginxsudo !! # becomes: sudo apt install nginx
# Reuse the path you just typedls /var/log/nginx/error.logless !$ # becomes: less /var/log/nginx/error.logvim !$ # open the same file
# Reuse all argumentscp file1.txt file2.txt /backup/ls !* # becomes: ls file1.txt file2.txt /backup/Quick substitution
Section titled “Quick substitution”# Fix a typo in the last commandgit stats^stats^status^ # reruns as: git statusConvert stdin lines into arguments for another command.
# Delete all .tmp filesfd '\.tmp$' | xargs rm
# Open matching files in your editorrg -l "deprecated" | xargs nvim
# Run in parallel (-P)fd '\.png$' | xargs -P 4 optipng
# Handle filenames with spaces (-0 with -print0 or fd -0)fd -0 '\.log$' | xargs -0 rm
# Limit arguments per invocation (-n)echo "a b c d" | xargs -n 2 echo # "a b" then "c d"
# Substitute placement (-I)cat urls.txt | xargs -I {} curl -O {}Process Substitution
Section titled “Process Substitution”<() creates a temporary file-like object from command output. Use when a
command expects a filename, not stdin.
# Diff two commands without temp filesdiff <(curl -s https://api.example.com/v1) <(curl -s https://api.example.com/v2)
# Diff sorted outputsdiff <(sort file1.txt) <(sort file2.txt)
# Compare directory listingsdiff <(ls dir1/) <(ls dir2/)
# Source output of a commandsource <(kubectl completion bash)Fuzzy Selection with fzf
Section titled “Fuzzy Selection with fzf”fzf turns any list into an interactive picker. No mouse needed — type to filter, arrow keys to select.
# Pick a file to editnvim $(fzf)
# Pick a branch to checkoutgit checkout $(git branch --all | fzf)
# Pick a process to killkill $(ps aux | fzf | awk '{print $2}')
# Pick a docker container to exec intodocker exec -it $(docker ps --format '{{.Names}}' | fzf) bash
# Pick from command history$(history | fzf | sed 's/^ *[0-9]* *//')fzf with preview
Section titled “fzf with preview”# File picker with previewnvim $(fzf --preview 'bat --color=always {}')
# Git log picker with diff previewgit show $(git log --oneline | fzf --preview 'git show {1}' | awk '{print $1}')fzf key bindings (defaults after install)
Section titled “fzf key bindings (defaults after install)”| Binding | Action |
|---|---|
Ctrl+T | Paste selected file path into prompt |
Ctrl+R | Search command history |
Alt+C | cd into selected directory |
Here Documents and Here Strings
Section titled “Here Documents and Here Strings”See Shell Scripting for heredoc syntax, quoting variants, and here strings.
Send output to both a file and the next command in the pipeline.
# Save and displaycurl -s https://api.example.com | tee response.json | jq '.status'
# Save intermediate pipeline resultsps aux | tee processes.txt | grep nginx
# Append instead of overwriteecho "log entry" | tee -a debug.logVariable Capture Patterns
Section titled “Variable Capture Patterns”See Shell Scripting for variable assignment, exit status (
$?), and output capture. Thewhile readloop fed by process substitution< <(cmd)combines a shell loop with process substitution.
Combining Patterns
Section titled “Combining Patterns”Real workflows chain these together.
# Find the function that changed most across git historygit log --oneline --all --follow -p -- '*.py' \ | grep "^+.*def " \ | sed 's/^+//' \ | sort \ | uniq -c \ | sort -rn \ | head -10
# Deploy: build, tag, push — stopping on any failureversion=$(git describe --tags) \ && docker build -t "app:$version" . \ && docker push "app:$version" \ && echo "Deployed $version"
# Interactive git stash applygit stash apply $(git stash list | fzf | cut -d: -f1)
# Find large files not tracked by gitcomm -23 \ <(find . -type f -size +1M | sort) \ <(git ls-files | sort) \ | head -20Quick Reference
Section titled “Quick Reference”| Goal | Pattern |
|---|---|
| Use output as arguments | cmd $(other_cmd) |
| Use output as stdin | cmd1 | cmd2 |
| Use output as a file | cmd <(other_cmd) |
| Save output and pass it along | cmd1 | tee file | cmd2 |
| Send output to clipboard | cmd | pbcopy |
| Pick from output interactively | cmd | fzf |
| Reuse last argument | !$ or $_ |
| Rerun last command | !! |
| Convert stdin to arguments | cmd1 | xargs cmd2 |
| Fix typo in last command | ^old^new^ |
See Also
Section titled “See Also”- Shell Scripting — Variables, heredocs, functions, loops, and error handling for scripts that use these pipeline patterns
- Unix CLI — Core commands for file ops and text processing
- jq — JSON processing in pipelines
- Python CLI
- Regex
- CLI-First