Skip to content
On this page

    The Fish Shell Terminal Stack: From Slow to Fast

    After years of using default terminal tools, I rebuilt my workflow with modern alternatives. The result? 100x faster file searches, 80% fewer keystrokes, and workflows that don't require a mouse. Here's what actually matters.

    9 min read

    I spent years using the default terminal tools. find for searching files. grep for text search. cd for navigation. It worked, but it was slow.

    I’d wait 30 seconds for a search to finish. I’d type out full directory paths. I’d scroll through command history looking for that one command I ran yesterday.

    Then I discovered the modern terminal stack-tools that replace those defaults with specialized, performant alternatives (written using Rust/ Go). The difference isn’t incremental. It’s transformative.

    I’m primarily a Fish shell user, and when you pair these tools with Fish’s 50-100ms startup time and native integrations, you get something special. Also, when you work with Claude Code, you would see significant turnaround.

    After switching, I’m saving 20-30 minutes per day. That’s an extra hour of deep work every 2-3 days. Here’s what actually matters.

    File System and Navigation: The Core Workflow

    fd: Modern find with Gitignore Awareness

    fd reimplements Unix find with three critical improvements:

    1. Gitignore Respect: By default, fd excludes files matching .gitignore, .git/, node_modules/, and hidden directories-no complex -not -path excludes needed
    2. Intuitive Syntax: fd pattern directory vs. find directory -name pattern reverses the cognitive load
    3. Parallel Searching: On multi-core systems, fd parallelizes directory traversal

    Benchmark: Searching 500K files (typical monorepo) takes 0.3s with fd vs. 3-30s with find depending on exclude rules. See fd benchmarks for detailed performance comparisons.

    Fish shell integration:

    # Alias (lazy-loaded, zero startup cost)
    alias --save find "fd"
    
    # Or as abbreviation (expands in-place)
    abbr -a ff "fd --hidden --exclude=.git"

    Bash/Zsh integration:

    alias find='fd'

    zoxide: Frecency-Based Navigation

    zoxide learns your most-accessed directories and enables jumps via partial matching:

    # Fish shell
    z dev        # Jump to ~/projects/dev (if recently accessed)
    z project    # Jump to ~/workspace/project (even if not in $PWD)
    z -          # Jump to previous directory
    zi           # Interactive selection with preview (if fzf installed)

    Fish shell setup (~/.config/fish/config.fish):

    zoxide init fish | source

    Bash/Zsh setup (~/.bashrc or ~/.zshrc):

    eval "$(zoxide init bash)"  # or zsh

    Internally, zoxide maintains a frecency database (~/.local/share/zoxide/db.zo) that scores directories by frequency × (now - last_accessed_time).

    Frequently visited directories can be reached in one command instead of 4-5 cd commands.

    Keystroke impact: In a typical 8-hour workday with 50 directory changes, zoxide saves 200-300 keystrokes through intelligent autocomplete and frecency weighting.

    ripgrep outperforms grep by orders of magnitude through:

    1. Regex Compilation: Rust’s regex library uses NFA/DFA optimization, not backtracking
    2. Gitignore Respect: Skips excluded directories without system calls
    3. Parallelization: Searches multiple files simultaneously
    4. Memory Efficiency: Memory-mapped file I/O for large files

    Benchmark: Searching “useState” across a 200K-file JavaScript monorepo takes 0.05s with rg vs. 8-15s with grep -r-a 200x improvement. See ripgrep benchmarks for more detailed performance comparisons.

    Configuration (.ripgreprc):

    --max-columns=200      # Prevent runaway output
    --smart-case            # Case-sensitive only if uppercase in query
    --type-add text:*.mdx   # Support MDX files

    Fish shell abbreviation:

    # Abbreviation expands in-place, allowing argument modification
    abbr -a rg 'rg --max-columns=200 --smart-case'
    abbr -a rgr 'rg --hidden'  # Include hidden directories

    Bash/Zsh alias:

    alias rg='rg --max-columns=200 --smart-case'

    Interactive Selection: The fzf Ecosystem

    fzf: Core Fuzzy Finding

    fzf implements a modal search interface enabling three critical workflows:

    BindingFunctionUse Case
    CTL-RFuzzy history searchFind previous command without scrolling
    CTL-TFuzzy file selectionvim $(fzf) pattern for editor integration
    ALT-CFuzzy directory navigationcd to nested directories without knowing exact path

    The algorithm: fzf uses a state machine for incremental search. As you type characters, the matching set shrinks in real-time, typically achieving single-keystroke response times even on 100K+ item lists.

    Fish shell integration (recommended: modern plugin):

    # Install Fisher package manager
    curl -sL https://raw.githubusercontent.com/jorgebucaran/fisher/main/functions/fisher.fish | source && fisher install jorgebucaran/fisher
    
    # Install modern fzf.fish plugin
    fisher install PatrickF1/fzf.fish
    
    # Configure in ~/.config/fish/config.fish
    fzf_configure_bindings

    The modern fzf.fish plugin provides customizable keybindings and integrates seamlessly with Fish’s native key binding system. Fisher is Fish’s package manager for managing plugins.

    Bash/Zsh integration:

    # Add to ~/.bashrc or ~/.zshrc
    eval "$(fzf --bash)"  # or --zsh
    . "$(brew --prefix)/opt/fzf/shell/key-bindings.bash"  # or .zsh

    Integration with preview (using bat):

    # Fish shell: Set environment variables
    set -gx FZF_CTRL_T_OPTS "--preview 'bat --color=always --line-range :500 {}' --preview-window=right,50%"
    set -gx FZF_DEFAULT_COMMAND 'fd --hidden --exclude=.git'
    # Bash/Zsh: Export environment variables
    export FZF_CTRL_T_OPTS="--preview 'bat --color=always --line-range :500 {}' --preview-window=right,50%"
    export FZF_DEFAULT_COMMAND='fd --hidden --exclude=.git'

    This displays a syntax-highlighted preview of the selected file, reducing incorrect selections by ~95% compared to blind selection.

    Advanced fzf Patterns

    Combining fzf with other tools creates powerful workflows:

    Fish shell functions:

    # Find and edit a file
    function fe --description "Fuzzy find and edit file"
        set -l file (fzf --preview 'bat --color=always {}')
        [ -n "$file" ] && $EDITOR "$file"
    end
    
    # Git add with preview
    function git_add_preview --description "Interactively add files with preview"
        git diff --name-only | fzf --preview 'git diff {}' | xargs git add
    end

    Bash/Zsh functions:

    # Find and edit a file
    fzf_edit() {
      local file=$(fzf --preview 'bat --color=always {}')
      [ -n "$file" ] && $EDITOR "$file"
    }
    
    # Git add with preview
    fzf_git_add() {
      git ls-files -m -o --exclude-standard | fzf \
        --preview 'git diff --color=always {}' | xargs git add
    }

    Fish advantage: Native array handling ($argv), direct string interpolation, and cleaner function syntax eliminate the complexity of Bash parameter expansion.

    Code Review and Git Workflow

    delta: Syntax-Aware Diff Rendering

    Git diffs in standard terminals show only line-level changes. delta adds three layers of clarity:

    1. Syntax Highlighting: Language-aware coloring for code blocks
    2. Word-Level Diffs: Highlights character-level changes within lines (shows oldvarnewvar in red/green)
    3. Side-by-Side Layout: Optional parallel view with line wrapping

    Impact on code review speed: Research shows side-by-side diffs with syntax highlighting reduce review comprehension time by 30-40% compared to unified diffs. See delta documentation for all available features and themes.

    Configuration (.gitconfig):

    [core]
      pager = delta
    
    [delta]
      syntax-theme = Dracula
      side-by-side = true
      line-numbers = true

    lazygit: Interactive Git UI

    lazygit provides a keyboard-driven TUI for git operations, dramatically accelerating complex workflows:

    Core capabilities:

    • Interactive Staging: Select individual hunks (5-line chunks) from files to stage, rather than staging entire files
    • Interactive Rebase: Visually reorder, squash, reword, and fixup commits without git rebase -i editor gymnastics
    • Merge Conflict Resolution: Visual diff of conflicts with staged/unstaged pane management
    • Branch Management: Create, delete, merge, rebase branches with keyboard shortcuts

    Productivity gain: What requires 10+ CLI commands (e.g., git rebase -i, then editor interactions, then git add --patch) becomes 3-4 keypresses.

    Fish shell key binding (~/.config/fish/functions/fish_user_key_bindings.fish):

    function fish_user_key_bindings
        # Bind Ctrl-G to lazygit
        bind \cg 'lazygit; commandline -f repaint'
        
        # Bind Ctrl-D to git diff with delta
        bind \cd 'git diff | delta | less -R; commandline -f repaint'
    end

    Bash/Zsh key binding (~/.bashrc or ~/.zshrc):

    # Add to your config file
    bind '"\C-g":"lazygit\n"'
    bind '"\C-d":"git diff | delta | less -R\n"'

    Critical Fish syntax notes:

    • \c prefix = Control key (Ctrl-X → \cx) - see Fish key bindings documentation
    • commandline -f repaint = Redraw prompt after external command execution
    • Fish’s bind command integrates seamlessly with external TUI applications

    Example workflow:

    1. Launch lazygit (or press Ctrl-G if bound)
    2. Press I (interactive rebase) on commits pane
    3. Visually drag/drop commits to reorder
    4. Press s to squash into previous commit
    5. Save automatically

    System Monitoring

    btop: Real-Time Resource Visualization

    Traditional system monitors (top, htop) display data in ASCII tables. btop++ adds graphical elements (ASCII-based line graphs) and interactive process management:

    Featurehtopbtop++
    CPU/Memory/Disk graphsBar indicatorsLine graphs over time
    Process tree viewYesYes, with detailed stats
    Disk I/O per processNoYes, with speed metrics
    GPU monitoringNoPartial (vendor-dependent)
    Custom themesLimitedExtensive (themes in config)
    Performance overhead~2-3% CPU~1-2% CPU (C++ vs. Python)

    Use case: During a long-running deployment, btop reveals whether slowness comes from disk I/O (saturated NVMe), network (bandwidth exhaustion), or CPU contention-actionable diagnostics in seconds.

    Supporting Utilities

    jq: JSON Processing Pipeline

    jq treats JSON as a first-class stream type, not unstructured text:

    # Fish shell: Direct pipe composition without escaping
    curl https://api.github.com/users/octocat | jq '.repos[] | {name, stars: .stargazers_count}'
    
    # Variable interpolation without $()
    set repos (curl https://api.github.com/users/octocat | jq '.public_repos')
    echo "Total repos: $repos"
    # Bash/Zsh: Same syntax works identically
    curl https://api.github.com/users/octocat | jq '.repos[] | {name, stars: .stargazers_count}'

    jq’s filter language (similar to functional programming) enables complex data transformations without writing loops, reducing JSON-to-CSV/summary operations from 20 lines of code to single-line expressions.

    tldr: Practical Command Examples

    While man pages provide authoritative documentation, they bury practical examples in 100+ lines of prose. tldr provides community-curated examples for 5,000+ commands in 10-15 lines:

    $ tldr tar
    tar
    Pack or unpack files to/from an archive
    
    $ tldr -l | fzf --preview 'tldr {1}'  # Interactive browsing

    Combined with fzf, you can fuzzy-search for commands and preview examples before running them.

    trash-cli: Safe Deletion

    rm -rf permanently deletes files-a disaster waiting to happen in scripts. trash-cli moves files to the system trash instead:

    # Fish shell: Alias replaces rm
    alias --save rm "trash"
    # Bash/Zsh: Alias replaces rm
    alias rm='trash'

    Usage:

    trash file.txt      # Moves to ~/.local/share/Trash
    trash 'project/**'  # Pattern-based deletion

    Recovery is straightforward: ls ~/.local/share/Trash/files/ to verify, then restore via file manager or mv from trash.

    macOS-Specific Tools

    Raycast: Application Launcher

    Raycast replaces macOS Spotlight with a searchable launcher featuring 300+ extensions:

    Built-in commands:

    • Clipboard history (searchable, timestamped)
    • Window management (snap to halves, thirds, grid)
    • Focus sessions (blocks distracting apps)
    • Calendar integration (one-key meeting join with camera check)
    • Quick links (custom shortcuts to frequently used URLs/tools)

    Extension examples: GitHub repo search, Spotify control, language translation, system information.

    Keyboard shortcuts: You can assign single hotkeys to frequently-used commands (e.g., Cmd-Shift-C for clipboard history), eliminating the need to open Raycast’s search bar.

    Rectangle: Keyboard-Driven Window Management

    Rectangle removes the mouse from window resizing on macOS:

    Cmd-Option-Left      → Snap to left half
    Cmd-Option-Right     → Snap to right half
    Cmd-Option-Up        → Maximize (not fullscreen)
    Cmd-Option-1,2,3,4   → Snap to quadrants

    Without Rectangle, macOS’s native window snapping leaves a gap under the menu bar and requires dragging. Keyboard shortcuts enable power users to manage 4 windows in <1 second.

    Integration Patterns: The Full-Stack Development Loop

    A developer using this toolkit completes: find file → edit → test → stage → commit → review in a single terminal session without touching the mouse:

    Fish shell workflow:

    # 1. Find changed files (using git + fzf)
    git diff --name-only | fzf --preview 'bat {}'
    
    # 2. Edit selected file (Fish uses parentheses for command substitution)
    $EDITOR (git diff --name-only | fzf)
    
    # 3. View diffs with syntax highlighting
    delta                    # Automatically integrated into git
    
    # 4. Interactive staging (Ctrl-G if bound)
    lazygit                  # Ctrl-A to stage all, 's' to squash commits
    
    # 5. Monitoring (Ctrl-S if bound)
    btop                     # Check if tests are CPU-bound or I/O-bound

    Bash/Zsh workflow:

    # 1. Find changed files (using git + fzf)
    git diff --name-only | fzf --preview 'bat {}'
    
    # 2. Edit selected file
    vim $(git diff --name-only | fzf)
    
    # 3. View diffs with syntax highlighting
    delta                    # Automatically integrated into git
    
    # 4. Interactive staging
    lazygit                  # Ctrl-A to stage all, 's' to squash commits
    
    # 5. Monitoring
    btop                     # Check if tests are CPU-bound or I/O-bound

    The entire workflow is keyboard-driven, requires zero mouse interaction, and provides visual feedback at each step.

    Cross-Tool Composition

    These tools follow Unix philosophy-each solves one problem well, and they compose via stdin/stdout:

    Fish shell:

    # Find all *.ts files, preview with bat, select with fzf, open in editor
    fd '\.ts$' | fzf --preview 'bat {}' | xargs $EDITOR
    
    # Interactively add git changes with visual diff preview
    git diff | delta | fzf --preview-window=right,50%
    
    # Search code, preview matches, fuzzy select, navigate
    rg 'useState' --type javascript | fzf --preview 'bat {1} --highlight-line {2}'

    Bash/Zsh:

    # Find all *.ts files, preview with bat, select with fzf, open in editor
    fd '\.ts$' | fzf --preview 'bat {}' | xargs vim
    
    # Interactively add git changes with visual diff preview
    git diff | delta | fzf --preview-window=right,50%
    
    # Search code, preview matches, fuzzy select, navigate
    rg 'useState' --type javascript | fzf --preview 'bat {1} --highlight-line {2}'

    Performance Benchmarks

    OperationTraditionalModern StackImprovement
    Search 500K files for pattern15-30s (find + grep)0.15s (fd + rg)100-200x
    Navigate to nested directory5-8 keystrokes (cd + cd + cd)1-2 keystrokes (zoxide)75-80% reduction
    Review code diff45-60s (read unified diff)15-20s (delta side-by-side)60% faster
    Stage changes for commit30-45s (git add -p, editor loops)5-10s (lazygit visual UI)70% faster
    Terminal prompt render100-500ms (bash/Oh My Zsh)<5ms (Starship)20-100x faster
    Shell startup time300-600ms (Zsh/Oh My Zsh)50-100ms (Fish)3-6x faster
    System monitoring insight1-2 minutes (reading htop)10-15s (btop graphs)80% faster

    Daily impact: A developer performing 100 file searches, 20 directory navigations, 10 diffs, and 8 commits saves 20-30 minutes per day-equivalent to an extra hour of deep work every 2-3 days.

    Fish Shell: Aliases, Abbreviations, and Functions

    Fish has three distinct constructs for command shortcuts. Choosing correctly improves performance and usability:

    PatternUse CaseStartup ImpactExample
    aliasSimple command wrappingNone (lazy-loaded)alias --save gst "git status"
    abbrFrequently-typed commands1-2ms each (loads at init)abbr -a gco "git checkout"
    functionMulti-line logic, conditional behaviorNone (lazy-loaded)Complex git workflows

    Best practices:

    Aliases - Use for simple replacements (zero startup cost):

    alias --save ll "ls -lah"
    alias --save cat "bat"
    alias --save find "fd"
    alias --save grep "rg"

    Abbreviations - Use for frequently-typed commands (expands in-place):

    abbr -a gpl "git pull"
    abbr -a gps "git push"
    abbr -a gac "git add .; git commit -m"

    Functions - Use for complex logic or multi-command operations:

    function git_clean_branches --description "Remove deleted remote branches"
        git branch -vv | grep '\[.*gone\]' | awk '{print $1}' | xargs git branch -d
    end
    
    function git_add_preview --description "Preview files before staging"
        git diff --name-only | fzf --preview 'git diff {}' | xargs git add
    end

    Performance notes:

    Configuration and Maintenance

    Most tools require minimal configuration. Common patterns:

    1. Install via Homebrew (macOS) or distro package manager (Linux)
    2. Create config files in ~/.config/toolname/ (follows XDG standard)
    3. Add shell integrations to your shell config file
    4. Create aliases/abbreviations/functions for frequently-combined commands

    Fish Shell Configuration

    Complete ~/.config/fish/config.fish template:

    # Initialize core tools
    zoxide init fish | source
    
    # Aliases (lazy-loaded, zero startup cost)
    alias --save ll "ls -lah"
    alias --save cat "bat"
    alias --save find "fd"
    alias --save grep "rg"
    alias --save diff "delta"
    alias --save rm "trash"
    alias --save gst "git status"
    
    # Abbreviations (in-place expansion, use sparingly)
    abbr -a -- gpl "git pull"
    abbr -a -- gps "git push"
    abbr -a -- gac "git add .; git commit -m"
    
    # Environment variables
    set -gx EDITOR nvim
    set -gx FZF_DEFAULT_COMMAND 'fd --hidden --exclude=.git'
    set -gx FZF_CTRL_T_OPTS "--preview 'bat --color=always --line-range :500 {}' --preview-window=right,50%"
    
    # Functions (lazy-loaded)
    function git_add_preview --description "Interactively add files with preview"
        git diff --name-only | fzf --preview 'git diff {}' | xargs git add
    end
    
    function fe --description "Fuzzy find and edit file"
        set -l file (fzf --preview 'bat --color=always {}')
        [ -n "$file" ] && $EDITOR "$file"
    end

    Key bindings (~/.config/fish/functions/fish_user_key_bindings.fish):

    function fish_user_key_bindings
        bind \cg 'lazygit; commandline -f repaint'
        bind \cd 'git diff | delta | less -R; commandline -f repaint'
        bind \cs 'btop; commandline -f repaint'
    end

    Fish performance advantages:

    • Aliases: Lazy-loaded functions; zero startup overhead
    • Abbreviations: Load as universal variables; 1-2ms each (limit to 15-20 total)
    • Functions: Stored in ~/.config/fish/functions/; lazy-loaded
    • Startup time: 50-100ms vs. 300-600ms for Zsh with Oh My Zsh

    Bash/Zsh Configuration

    Example .bashrc or .zshrc additions:

    # Initialize core tools
    eval "$(zoxide init bash)"    # or zsh
    eval "$(fzf --bash)"          # or --zsh
    . "$(brew --prefix)/opt/fzf/shell/key-bindings.bash"  # or .zsh
    
    # Aliases
    alias ll='ls -lah'
    alias cat='bat'
    alias find='fd'
    alias grep='rg'
    alias diff='delta'
    alias rm='trash'
    
    # Functions
    fzf_edit() {
      local file=$(fzf --preview 'bat --color=always {}')
      [ -n "$file" ] && $EDITOR "$file"
    }
    
    # Environment variables
    export EDITOR=nvim
    export FZF_DEFAULT_COMMAND='fd --hidden --exclude=.git'
    export FZF_CTRL_T_OPTS="--preview 'bat --color=always --line-range :500 {}' --preview-window=right,50%"

    The Bottom Line

    The modern terminal stack replaces historical Unix utilities with specialized, performant tools optimized for developer ergonomics.

    Rather than a single “best” terminal, this ecosystem emphasizes composability-each tool solves one problem exceptionally well, enabling powerful workflows through piping and integration.

    Why Fish shell fits this stack:

    • Lazy-loaded aliases: Zero shell startup overhead
    • Superior key binding UX: bind syntax is cleaner and more discoverable (see Fish key bindings)
    • Universal variables: Persistent configuration without sourcing scripts (see Fish universal variables)
    • Performance: 50-100ms startup vs. 300-600ms for Zsh with Oh My Zsh
    • Modern syntax: end keyword, 1-based array indexing, direct string interpolation (see Fish syntax documentation)

    For developers using this setup daily, the cumulative time savings (20-30 minutes per day) and ergonomic improvements (zero mouse usage, keyboard-driven workflows) represent a 10-15% productivity boost, equivalent to 4-6 weeks of additional focus time per year.

    The tools profiled here aren’t experimental-they’re actively maintained, widely adopted across the developer community, and available across macOS, Linux, and Windows platforms. The learning curve is front-loaded (1-2 weeks to internalize keybindings), but the payoff compounds daily.

    Quick reference links:

    • fd - Modern find replacement
    • zoxide - Smart directory navigation
    • ripgrep - Fast text search
    • fzf - Fuzzy finder
    • bat - Syntax-highlighted cat
    • delta - Git diff viewer
    • lazygit - Interactive git UI
    • btop - System monitor
    • jq - JSON processor
    • tldr - Command examples
    • trash-cli - Safe deletion
    • Fish shell - Modern shell
    • Fisher - Fish package manager

    Rebuilding your terminal workflow? I’d love to hear what tools you’re using. Reach out on LinkedIn.