Shell Productivity
Pipes, redirection, shell scripting, aliases, and workflow tips that make you faster in the terminal — tailored for web developers.
Recipe
Quick-reference recipe card — copy-paste ready.
# Pipes — chain commands
cat package.json | jq '.dependencies | keys[]'
git log --oneline | head -20
npm outdated | grep -v "Package"
# Redirection
npm run build > build.log 2>&1 # stdout + stderr to file
npm test 2>/dev/null # suppress errors
# Command substitution
echo "Node $(node -v) on $(uname -s)"
# History
history | grep "docker" # search past commands
Ctrl+R # reverse search (interactive)
!! # repeat last command
sudo !! # repeat last command with sudoWhen to reach for this: When you need to combine commands, automate repetitive tasks, or speed up your terminal workflow.
Working Example
Quick Project Health Check Script
#!/bin/bash
# save as: scripts/health-check.sh
# run with: bash scripts/health-check.sh
echo "=== Project Health Check ==="
echo ""
echo "Node: $(node -v) | npm: $(npm -v)"
echo "Branch: $(git branch --show-current)"
echo "Uncommitted changes: $(git status --porcelain | wc -l | tr -d ' ')"
echo ""
echo "--- Dependencies ---"
echo "Total packages: $(ls node_modules | wc -l | tr -d ' ')"
echo "Outdated:"
npm outdated 2>/dev/null || echo " All up to date"
echo ""
echo "--- Code Stats ---"
echo "TypeScript files: $(find src -name '*.ts' -o -name '*.tsx' 2>/dev/null | wc -l | tr -d ' ')"
echo "Test files: $(find src -name '*.test.*' -o -name '*.spec.*' 2>/dev/null | wc -l | tr -d ' ')"
echo "TODO count: $(grep -r 'TODO\|FIXME' src/ --include='*.ts' --include='*.tsx' 2>/dev/null | wc -l | tr -d ' ')"
echo ""
echo "--- Type Check ---"
npx tsc --noEmit 2>&1 | tail -1What this demonstrates:
$(...)for inline command output- Combining
find,grep,wcto count things 2>/dev/nullto suppress noise- A practical script you can actually use in your projects
Deep Dive
Pipes and Composition
Pipes (|) pass the output of one command as input to the next.
# Count lines of code by file type
find src -name "*.tsx" | xargs wc -l | sort -n | tail -20
# Find the largest 10 dependencies
du -sh node_modules/* | sort -rh | head -10
# List all unique import sources
grep -rh "from ['\"]" src/ | sed "s/.*from ['\"]//;s/['\"].*//" | sort -u
# Find components that use useState but not useEffect
grep -rl "useState" src/components/ | xargs grep -L "useEffect"
# Count commits per author
git shortlog -sn --all | head -10
# Find files changed in the last commit that contain TODO
git diff --name-only HEAD~1 | xargs grep -l "TODO" 2>/dev/null
# JSON processing with jq
cat package.json | jq '.scripts'
cat package.json | jq '.dependencies | keys | length'
curl -s https://api.example.com/data | jq '.items[] | {id, name}'Redirection
# Redirect stdout to file
npm run build > build.log
# Redirect stderr to file
npm run build 2> errors.log
# Redirect both to same file
npm run build > build.log 2>&1
# Modern syntax (bash 4+):
npm run build &> build.log
# Append instead of overwrite
echo "new entry" >> log.txt
# Discard output
npm install > /dev/null 2>&1
# Redirect stdin from file
node script.js < input.txt
# Here document (multiline input)
cat << 'EOF' > .env.local
DATABASE_URL=postgresql://localhost:5432/mydb
NEXT_PUBLIC_API_URL=http://localhost:3000/api
EOFShell Aliases and Functions
Add to ~/.bashrc or ~/.zshrc:
# Navigation
alias ..="cd .."
alias ...="cd ../.."
alias proj="cd ~/projects"
# Git shortcuts
alias gs="git status -sb"
alias gl="git log --oneline --graph -20"
alias gd="git diff"
alias gc="git commit -m"
alias gp="git push"
alias gpu="git pull"
alias gco="git checkout"
alias gcb="git checkout -b"
# npm shortcuts
alias ni="npm install"
alias nd="npm run dev"
alias nb="npm run build"
alias nt="npm test"
alias nrd="npm run dev"
# Quick servers
alias serve="npx serve ."
alias py-serve="python3 -m http.server 8080"
# Utility
alias ll="ls -la"
alias cls="clear"
alias ports="lsof -i -P | grep LISTEN"
alias myip="curl -s ifconfig.me"
# Functions
mkcd() { mkdir -p "$1" && cd "$1"; }
# Kill process on a port
killport() { lsof -ti :"$1" | xargs kill -9 2>/dev/null && echo "Killed port $1" || echo "Nothing on port $1"; }
# Create a new component file
newcomp() {
local name="$1"
local dir="src/components"
mkdir -p "$dir"
cat > "$dir/$name.tsx" << EOF
interface ${name}Props {
// props
}
export function ${name}({}: ${name}Props) {
return (
<div>
<h2>${name}</h2>
</div>
);
}
EOF
echo "Created $dir/$name.tsx"
}# Reload after editing
source ~/.zshrc # or ~/.bashrcShell Scripting Basics
#!/bin/bash
# Variables
NAME="my-app"
PORT=${PORT:-3000} # default value if not set
TIMESTAMP=$(date +%Y%m%d)
# Conditionals
if [ -f "package.json" ]; then
echo "Node project found"
elif [ -f "Cargo.toml" ]; then
echo "Rust project found"
else
echo "Unknown project type"
fi
# Check if command exists
if command -v pnpm &> /dev/null; then
pnpm install
else
npm install
fi
# Loops
for file in src/components/*.tsx; do
echo "Component: $(basename "$file" .tsx)"
done
# Loop over lines
git diff --name-only | while read -r file; do
echo "Changed: $file"
done
# Exit on error (always use in scripts)
set -euo pipefail
# -e: exit on error
# -u: error on undefined variables
# -o pipefail: catch errors in pipes
# Trap for cleanup
cleanup() { rm -f /tmp/myapp-*.log; }
trap cleanup EXITPractical Developer Scripts
# Quick deploy check: type-check, lint, test, build
#!/bin/bash
set -euo pipefail
echo "1/4 Type checking..."
npx tsc --noEmit
echo "2/4 Linting..."
npx next lint
echo "3/4 Testing..."
npx vitest run
echo "4/4 Building..."
npm run build
echo "All checks passed!"# Watch a file and auto-run a command
# Install: brew install entr (macOS) or apt install entr (Linux)
find src -name "*.test.ts" | entr -c npx vitest run
# Or watch with a simple loop
while true; do
inotifywait -r -e modify src/ 2>/dev/null || fswatch -1 src/
npx tsc --noEmit
doneKeyboard Shortcuts (Bash/Zsh)
Ctrl+R Reverse search history
Ctrl+A Move cursor to beginning of line
Ctrl+E Move cursor to end of line
Ctrl+W Delete word before cursor
Ctrl+K Delete from cursor to end of line
Ctrl+U Delete entire line (bash) / to beginning (zsh)
Ctrl+L Clear screen (same as `clear`)
Ctrl+C Cancel current command
Ctrl+Z Suspend current process (resume with `fg`)
Alt+B Move back one word
Alt+F Move forward one word
!! Repeat last command
!$ Last argument of previous command
xargs — Build Commands from Input
# Delete all .log files found
find . -name "*.log" | xargs rm
# Run prettier on all changed TypeScript files
git diff --name-only --diff-filter=M | grep '\.tsx\?$' | xargs npx prettier --write
# Open all files containing a pattern in VS Code
rg -l "deprecated" src/ | xargs code
# Parallel execution
find tests -name "*.test.ts" | xargs -P 4 -I {} npx vitest run {}
# -P 4 = 4 parallel processes
# -I {} = placeholder for the inputGotchas
Things that will bite you. Each gotcha includes what goes wrong, why it happens, and the fix.
-
Aliases not loading — Aliases defined in
~/.bashrcdon't appear in new terminals. Fix: macOS uses~/.zshrcby default (zsh). Put aliases there, or addsource ~/.bashrcto~/.zshrc. -
Script fails silently — A command in the middle fails but the script continues. Fix: Add
set -euo pipefailat the top of every script. -
Spaces in filenames break xargs —
xargssplits on whitespace by default. Fix: Usefind -print0 | xargs -0or quote variables:"$file". -
Pipe hides exit codes —
cmd1 | cmd2returns the exit code ofcmd2, hiding failures incmd1. Fix:set -o pipefailcatches errors in any pipe stage. -
History not saving — Commands from one terminal don't appear in another. Fix: Add to
~/.zshrc:setopt SHARE_HISTORYandsetopt INC_APPEND_HISTORY.
Alternatives
Other ways to solve the same problem — and when each is the better choice.
| Alternative | Use When | Don't Use When |
|---|---|---|
zsh + Oh My Zsh | Rich plugin ecosystem, themes, auto-suggestions | You need maximum POSIX compatibility |
fish shell | Sane defaults, amazing auto-complete out of the box | Scripts need to be portable to bash |
just (justfile) | Task runner replacing Makefile, simpler syntax | npm scripts are sufficient |
Makefile | Multi-language projects, CI automation | Simple Node.js projects (npm scripts suffice) |
| Turbo/Nx | Monorepo task orchestration with caching | Single-package projects |
FAQs
What does the pipe operator (|) do?
- It passes the stdout of one command as stdin to the next command
- Example:
git log --oneline | head -20shows only the first 20 log entries - You can chain as many pipes as needed
What is the difference between > and >> for redirection?
>overwrites the file with the new output>>appends to the file without erasing existing content- Use
>>for log files; use>when you want a fresh file
How do I redirect both stdout and stderr to the same file?
npm run build > build.log 2>&1
# or (bash 4+):
npm run build &> build.log2>&1redirects stderr (file descriptor 2) to wherever stdout (1) is going
What does set -euo pipefail do at the top of a script?
-eexits the script immediately if any command fails-utreats undefined variables as errors-o pipefailcatches failures in any stage of a pipe, not just the last- Always include this in production shell scripts
How do I create a shell alias for a frequently used command?
Add to ~/.zshrc (or ~/.bashrc):
alias gp="git push"Then reload: source ~/.zshrc
Gotcha: My aliases defined in ~/.bashrc do not work in new terminals on macOS. Why?
- macOS defaults to
zsh, which reads~/.zshrc, not~/.bashrc - Move your aliases to
~/.zshrcor addsource ~/.bashrcinside~/.zshrc
How do I search my command history for a previous command?
- Press
Ctrl+Rfor interactive reverse search - Or:
history | grep "docker"to search non-interactively !!repeats the last command;sudo !!reruns it with sudo
How does xargs work and when should I use it?
xargstakes stdin lines and passes them as arguments to another command- Example:
find . -name "*.log" | xargs rmdeletes all found.logfiles - Use
-P 4for parallel execution and-0withfind -print0for filenames with spaces
Gotcha: My script silently continues after a command in a pipe fails. How do I catch it?
- By default, only the exit code of the last command in a pipe is checked
- Add
set -o pipefailso any failure in the pipe causes the script to fail
How do I write a shell function that creates a TypeScript component file?
newcomp() {
local name="$1"
cat > "src/components/$name.tsx" << EOF
interface ${name}Props {}
export function ${name}({}: ${name}Props) {
return <div>${name}</div>;
}
EOF
}- Call with
newcomp Buttonto createsrc/components/Button.tsx
What is the difference between $() and backticks for command substitution?
$(command)and`command`both capture a command's output$()is preferred because it nests cleanly:$(echo $(date))- Backticks cannot be nested without awkward escaping
How do I run a quick deploy check script for a TypeScript project?
#!/bin/bash
set -euo pipefail
echo "Type check..." && npx tsc --noEmit
echo "Lint..." && npx next lint
echo "Test..." && npx vitest run
echo "Build..." && npm run build
echo "All checks passed!"Related
- Sysadmin Essentials — Process, disk, network, and service management
- Search & Regex — Find files, search content, and use regex patterns
- Node.js Developer Commands — npm, environment, and build commands