React SME Cookbook
All FAQs

Search Documentation

Search across all documentation pages

linuxbashshellscriptingpipes

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 sudo

When 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 -1

What this demonstrates:

  • $(...) for inline command output
  • Combining find, grep, wc to count things
  • 2>/dev/null to 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
EOF

Shell 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 ~/.bashrc

Shell 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 EXIT

Practical 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
done

Keyboard 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 input

Gotchas

Things that will bite you. Each gotcha includes what goes wrong, why it happens, and the fix.

  • Aliases not loading — Aliases defined in ~/.bashrc don't appear in new terminals. Fix: macOS uses ~/.zshrc by default (zsh). Put aliases there, or add source ~/.bashrc to ~/.zshrc.

  • Script fails silently — A command in the middle fails but the script continues. Fix: Add set -euo pipefail at the top of every script.

  • Spaces in filenames break xargsxargs splits on whitespace by default. Fix: Use find -print0 | xargs -0 or quote variables: "$file".

  • Pipe hides exit codescmd1 | cmd2 returns the exit code of cmd2, hiding failures in cmd1. Fix: set -o pipefail catches errors in any pipe stage.

  • History not saving — Commands from one terminal don't appear in another. Fix: Add to ~/.zshrc: setopt SHARE_HISTORY and setopt INC_APPEND_HISTORY.

Alternatives

Other ways to solve the same problem — and when each is the better choice.

AlternativeUse WhenDon't Use When
zsh + Oh My ZshRich plugin ecosystem, themes, auto-suggestionsYou need maximum POSIX compatibility
fish shellSane defaults, amazing auto-complete out of the boxScripts need to be portable to bash
just (justfile)Task runner replacing Makefile, simpler syntaxnpm scripts are sufficient
MakefileMulti-language projects, CI automationSimple Node.js projects (npm scripts suffice)
Turbo/NxMonorepo task orchestration with cachingSingle-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 -20 shows 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.log
  • 2>&1 redirects stderr (file descriptor 2) to wherever stdout (1) is going
What does set -euo pipefail do at the top of a script?
  • -e exits the script immediately if any command fails
  • -u treats undefined variables as errors
  • -o pipefail catches 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 ~/.zshrc or add source ~/.bashrc inside ~/.zshrc
How do I search my command history for a previous command?
  • Press Ctrl+R for 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?
  • xargs takes stdin lines and passes them as arguments to another command
  • Example: find . -name "*.log" | xargs rm deletes all found .log files
  • Use -P 4 for parallel execution and -0 with find -print0 for 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 pipefail so 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 Button to create src/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!"