Files
2025-11-30 08:48:40 +08:00

12 KiB

fzf - Interactive Selection Workflows

Overview

fzf is a command-line fuzzy finder that enables interactive selection from lists. It's a powerful tool for building interactive workflows.

Basic Usage

# Pipe any list to fzf
ls | fzf

# Multi-select with Tab
ls | fzf -m

# Use selected result
SELECTED=$(ls | fzf)
echo "You selected: $SELECTED"

Preview Windows

Basic Preview

# Preview with cat
fd | fzf --preview 'cat {}'

# Preview with bat (syntax highlighting)
fd | fzf --preview 'bat --color=always {}'

# Preview with line numbers
fd | fzf --preview 'bat --color=always --line-range :500 {}'

Preview Window Configuration

# Position and size
fzf --preview 'cat {}' --preview-window=right:50%
fzf --preview 'cat {}' --preview-window=up:40%
fzf --preview 'cat {}' --preview-window=down:30%
fzf --preview 'cat {}' --preview-window=left:50%

# Hidden by default (toggle with ctrl-/)
fzf --preview 'cat {}' --preview-window=hidden

# Wrap text
fzf --preview 'cat {}' --preview-window=wrap

# No wrap (default)
fzf --preview 'cat {}' --preview-window=nowrap

Advanced Previews

# Preview with highlighted search term
rg --line-number 'pattern' | \
  fzf --delimiter : \
      --preview 'bat --color=always {1} --highlight-line {2}'

# Preview JSON with jq
fd -e json | fzf --preview 'jq --color-output . {}'

# Preview images (requires chafa/imgcat)
fd -e png -e jpg | fzf --preview 'chafa {}'

# Preview directories
fd -t d | fzf --preview 'ls -la {}'

# Preview with tree
fd -t d | fzf --preview 'tree -L 2 {}'

# Multi-level preview (file type detection)
fd | fzf --preview '[[ -f {} ]] && bat --color=always {} || tree -L 1 {}'

Keybindings

Default Keybindings

ctrl-j / ctrl-n / down    : Move down
ctrl-k / ctrl-p / up      : Move up
enter                     : Select and exit
tab                       : Mark item (multi-select)
shift-tab                 : Unmark item
ctrl-c / esc              : Exit without selection
ctrl-/                    : Toggle preview window

Custom Keybindings

# Copy to clipboard (macOS)
fzf --bind 'ctrl-y:execute-silent(echo {} | pbcopy)'

# Copy to clipboard (Linux)
fzf --bind 'ctrl-y:execute-silent(echo {} | xclip -selection clipboard)'

# Open in editor
fzf --bind 'ctrl-e:execute($EDITOR {})'

# Open in editor and exit
fzf --bind 'ctrl-e:execute($EDITOR {})+abort'

# Delete file with confirmation
fzf --bind 'ctrl-d:execute(rm -i {})'

# Multiple bindings
fzf --bind 'ctrl-e:execute($EDITOR {})' \
    --bind 'ctrl-y:execute-silent(echo {} | pbcopy)' \
    --bind 'ctrl-d:execute(rm -i {})'

Advanced Keybinding Actions

# Reload results
fzf --bind 'ctrl-r:reload(fd)'

# Change preview
fzf --preview 'bat {}' \
    --bind 'ctrl-/:change-preview-window(hidden|)'

# Toggle between preview styles
fzf --preview 'bat --color=always {}' \
    --bind 'ctrl-t:change-preview(tree -L 2 {})'

# Accept and execute
fzf --bind 'enter:execute(echo Selected: {})+accept'

Search Syntax

Fuzzy Matching

# Basic fuzzy search
abc       # Matches files containing 'a', 'b', 'c' in order
          # Examples: abc, aXXbXXc, axbxc

# Exact match (single quote)
'abc      # Matches files containing exact string "abc"

# Prefix match (caret)
^abc      # Matches files starting with "abc"

# Suffix match (dollar)
abc$      # Matches files ending with "abc"

# Exact match (both)
^abc$     # Matches exactly "abc"

Logical Operators

# AND (space)
abc def   # Contains both "abc" AND "def"

# OR (pipe)
abc|def   # Contains "abc" OR "def"

# NOT (exclamation)
abc !def  # Contains "abc" but NOT "def"

# Combination
abc def|ghi !jkl
# Contains "abc" AND ("def" OR "ghi") but NOT "jkl"

Field Selection

# Search specific field (delimiter-separated)
# -d : Set delimiter
# -n : Select field number (1-indexed)
# --with-nth : Display specific fields

# Example: search only filenames in paths
fd | fzf -d / --with-nth -1

# Example: search only line numbers
rg --line-number 'pattern' | fzf -d : --with-nth 2

Common Workflows

File Navigation

# Find and edit file
fe() {
  local file
  file=$(fd --type f | fzf --preview 'bat --color=always {}')
  [[ -n "$file" ]] && $EDITOR "$file"
}

# Find and cd to directory
fcd() {
  local dir
  dir=$(fd --type d | fzf --preview 'tree -L 1 {}')
  [[ -n "$dir" ]] && cd "$dir"
}

# Find file with content preview
ff() {
  local file
  file=$(rg --files-with-matches "${1:-.}" | \
    fzf --preview "rg --color=always --context 5 '$1' {}")
  [[ -n "$file" ]] && $EDITOR "$file"
}
# Search content and edit
fzf_grep() {
  local line
  line=$(rg --line-number "$1" | \
    fzf --delimiter : \
        --preview 'bat --color=always {1} --highlight-line {2}' \
        --preview-window +{2}-/2)

  if [[ -n "$line" ]]; then
    local file=$(echo "$line" | cut -d: -f1)
    local lineno=$(echo "$line" | cut -d: -f2)
    $EDITOR "+$lineno" "$file"
  fi
}

# Find function definition and edit
find_function() {
  local result
  result=$(ast-grep --pattern "def $1(\$\$\$):" | \
    fzf --preview 'bat --color=always {1}')

  if [[ -n "$result" ]]; then
    local file=$(echo "$result" | cut -d: -f1)
    $EDITOR "$file"
  fi
}

Git Integration

# Interactive git add
fga() {
  git status --short | \
    fzf -m --preview 'git diff --color=always {2}' | \
    awk '{print $2}' | \
    xargs git add
}

# Interactive git checkout
fgco() {
  git branch --all | \
    grep -v HEAD | \
    sed 's/^[* ]*//' | \
    sed 's#remotes/origin/##' | \
    sort -u | \
    fzf --preview 'git log --oneline --color=always {}' | \
    xargs git checkout
}

# Interactive git log
fgl() {
  git log --oneline --color=always | \
    fzf --ansi --preview 'git show --color=always {1}' | \
    awk '{print $1}' | \
    xargs git show
}

# Interactive git diff
fgd() {
  git diff --name-only | \
    fzf --preview 'git diff --color=always {}' | \
    xargs git diff
}

Process Management

# Interactive kill
fkill() {
  local pid
  pid=$(ps aux | \
    sed 1d | \
    fzf -m --header='Select process to kill' | \
    awk '{print $2}')

  if [[ -n "$pid" ]]; then
    echo "$pid" | xargs kill -${1:-9}
  fi
}

# Interactive lsof (find what's using a port)
fport() {
  local port
  port=$(lsof -i -P -n | \
    sed 1d | \
    fzf --header='Select connection' | \
    awk '{print $2}')

  [[ -n "$port" ]] && echo "PID: $port"
}

Docker Integration

# Interactive container selection
fdocker() {
  docker ps -a | \
    sed 1d | \
    fzf --header='Select container' | \
    awk '{print $1}'
}

# Interactive container logs
fdlogs() {
  local container
  container=$(fdocker)
  [[ -n "$container" ]] && docker logs -f "$container"
}

# Interactive container exec
fdexec() {
  local container
  container=$(fdocker)
  [[ -n "$container" ]] && docker exec -it "$container" /bin/bash
}

Package Management

# Interactive npm script runner
fnpm() {
  local script
  script=$(cat package.json | \
    jq -r '.scripts | keys[]' | \
    fzf --preview 'jq -r ".scripts.{}" package.json')

  [[ -n "$script" ]] && npm run "$script"
}

# Interactive pip package info
fpip() {
  pip list | \
    fzf --preview 'pip show {1}'
}

Configuration Files

# Edit config file
fconf() {
  local file
  file=$(fd -H -t f \
    -e conf -e config -e json -e yaml -e yml -e toml -e ini \
    . "$HOME/.config" | \
    fzf --preview 'bat --color=always {}')

  [[ -n "$file" ]] && $EDITOR "$file"
}

# Edit dotfile
fdot() {
  local file
  file=$(fd -H -t f '^\..*' "$HOME" --max-depth 1 | \
    fzf --preview 'bat --color=always {}')

  [[ -n "$file" ]] && $EDITOR "$file"
}

Advanced Configurations

Color Schemes

# Monokai
export FZF_DEFAULT_OPTS='
  --color=fg:#f8f8f2,bg:#272822,hl:#66d9ef
  --color=fg+:#f8f8f2,bg+:#44475a,hl+:#66d9ef
  --color=info:#a6e22e,prompt:#f92672,pointer:#f92672
  --color=marker:#f92672,spinner:#a6e22e,header:#6272a4
'

# Nord
export FZF_DEFAULT_OPTS='
  --color=fg:#e5e9f0,bg:#3b4252,hl:#81a1c1
  --color=fg+:#e5e9f0,bg+:#434c5e,hl+:#81a1c1
  --color=info:#eacb8a,prompt:#bf616a,pointer:#b48ead
  --color=marker:#a3be8b,spinner:#b48ead,header:#a3be8b
'

# Tokyo Night
export FZF_DEFAULT_OPTS='
  --color=fg:#c0caf5,bg:#1a1b26,hl:#bb9af7
  --color=fg+:#c0caf5,bg+:#292e42,hl+:#7dcfff
  --color=info:#7aa2f7,prompt:#7dcfff,pointer:#7dcfff
  --color=marker:#9ece6a,spinner:#9ece6a,header:#9ece6a
'

Layout Options

# Reverse layout (prompt at top)
fzf --reverse

# Full screen
fzf --height=100%

# 40% of screen
fzf --height=40%

# Border
fzf --border
fzf --border=rounded
fzf --border=sharp

# Inline info
fzf --inline-info

# No info
fzf --no-info

Performance Options

# Limit results
fzf --select-1      # Auto-select if only one match
fzf --exit-0        # Exit if no match

# Algorithm
fzf --algo=v1       # Faster, less accurate
fzf --algo=v2       # Default, balanced

# History
fzf --history=/tmp/fzf-history

Environment Variables

# Default command
export FZF_DEFAULT_COMMAND='fd --type f --hidden --exclude .git'

# Default options
export FZF_DEFAULT_OPTS='
  --height 40%
  --layout=reverse
  --border
  --inline-info
  --preview "bat --color=always --line-range :500 {}"
  --bind ctrl-/:toggle-preview
'

# ctrl-t command (file widget)
export FZF_CTRL_T_COMMAND="$FZF_DEFAULT_COMMAND"
export FZF_CTRL_T_OPTS="
  --preview 'bat --color=always --line-range :500 {}'
  --bind 'ctrl-/:toggle-preview'
"

# alt-c command (directory widget)
export FZF_ALT_C_COMMAND='fd --type d --hidden --exclude .git'
export FZF_ALT_C_OPTS="
  --preview 'tree -L 1 {}'
"

Shell Integration

Bash/Zsh Key Bindings

# Add to ~/.bashrc or ~/.zshrc
source /usr/share/doc/fzf/examples/key-bindings.bash  # Bash
source /usr/share/doc/fzf/examples/key-bindings.zsh   # Zsh

# Or via package manager
eval "$(fzf --bash)"  # Bash
eval "$(fzf --zsh)"   # Zsh

Default bindings:

  • ctrl-t: Paste selected files
  • ctrl-r: Paste from command history
  • alt-c: cd into selected directory

Custom Shell Functions

# Add to ~/.bashrc or ~/.zshrc

# fh - repeat history
fh() {
  print -z $( ([ -n "$ZSH_NAME" ] && fc -l 1 || history) | \
    fzf +s --tac | \
    sed -E 's/ *[0-9]*\*? *//' | \
    sed -E 's/\\/\\\\/g')
}

# fbr - checkout git branch
fbr() {
  local branches branch
  branches=$(git branch -vv) &&
  branch=$(echo "$branches" | fzf +m) &&
  git checkout $(echo "$branch" | awk '{print $1}' | sed "s/.* //")
}

# fshow - git commit browser
fshow() {
  git log --graph --color=always \
      --format="%C(auto)%h%d %s %C(black)%C(bold)%cr" "$@" |
  fzf --ansi --no-sort --reverse --tiebreak=index --bind=ctrl-s:toggle-sort \
      --bind "ctrl-m:execute:
                (grep -o '[a-f0-9]\{7\}' | head -1 |
                xargs -I % sh -c 'git show --color=always % | less -R') << 'FZF-EOF'
                {}
FZF-EOF"
}

Tips and Tricks

Performance

  1. Use fd instead of find for FZF_DEFAULT_COMMAND
  2. Limit preview size with --line-range
  3. Use --select-1 and --exit-0 for automation
  4. Cache results for repeated searches

User Experience

  1. Always provide preview when possible
  2. Use semantic keybindings (ctrl-e for edit)
  3. Provide helpful --header messages
  4. Use --multi for batch operations
  5. Toggle preview with ctrl-/ for large files

Integration

# Combine with xargs
fd -e py | fzf -m | xargs wc -l

# Combine with while read
fd | fzf -m | while read file; do
  echo "Processing: $file"
  # Process file
done

# Combine with command substitution
vim $(fzf)
cd $(fd -t d | fzf)

Scripting

# Exit code check
if result=$(fd | fzf); then
  echo "Selected: $result"
else
  echo "No selection or cancelled"
  exit 1
fi

# Multi-select results
selected=$(fd | fzf -m)
if [[ -n "$selected" ]]; then
  echo "$selected" | while read file; do
    process "$file"
  done
fi