Skip to content

Instantly share code, notes, and snippets.

@heroheman
Last active March 18, 2026 09:43
Show Gist options
  • Select an option

  • Save heroheman/d634ebb51c98f3afefcc571802c9a786 to your computer and use it in GitHub Desktop.

Select an option

Save heroheman/d634ebb51c98f3afefcc571802c9a786 to your computer and use it in GitHub Desktop.
FZF Scripts Snippet Collection

Manage and organize

1. Central fzf-scripts directory + loader

Put each group of functions into its own file (e.g. git.sh, dev.sh, ai.sh) under a single directory and load them from your shell config. junegunn.github

# ~/.config/fzf-scripts/*.sh and optional allow-list via FZF_SCRIPTS_ENABLED
FZF_SCRIPTS_DIR="${FZF_SCRIPTS_DIR:-$HOME/.config/fzf-scripts}"
FZF_SCRIPTS_ENABLED="${FZF_SCRIPTS_ENABLED:-}"

load_fzf_scripts() {
  local file name
  [[ -d "$FZF_SCRIPTS_DIR" ]] || return 0

  for file in "$FZF_SCRIPTS_DIR"/*.sh; do
    [[ -f "$file" ]] || continue
    name=$(basename "$file" .sh)

    if [[ -n "$FZF_SCRIPTS_ENABLED" ]]; then
      if ! grep -qw "$name" <<<"$FZF_SCRIPTS_ENABLED"; then
        continue
      fi
    fi

    # shellcheck source=/dev/null
    source "$file"
  done
}
load_fzf_scripts

You can then enable only certain bundles per machine:

export FZF_SCRIPTS_ENABLED="git dev ai"

2. “fzf scripts registry” with metadata

Add a one-line metadata header to each script and use a registry function to browse and open them via fzf. github

Example header at the top of each script:

# FZF-SCRIPT: /home/user/.config/fzf-scripts/git_helpers.sh    git    Git helpers (branches, log, stash)

Registry function:

fzf_scripts_registry() {
  local dir="${FZF_SCRIPTS_DIR:-$HOME/.config/fzf-scripts}"
  local line file
  [[ -d "$dir" ]] || { echo "No dir $dir"; return 1; }

  line=$(
    grep -hR "^# FZF-SCRIPT:" "$dir"/*.sh 2>/dev/null \
    | sed 's|^# FZF-SCRIPT: ||' \
    | fzf --prompt='fzf-scripts> ' \
          --with-nth=2,3 \
          --preview 'echo {} | awk -F"\t" "{print \$3}"'
  ) || return

  file=$(echo "$line" | awk -F'\t' '{print $1}')
  "${EDITOR:-vim}" "$file"
}

3. Use existing fzf-focused Zsh plugins

Several GitHub projects already group and manage lots of fzf-powered functions for you:

Typical Oh-My-Zsh setup:

# .zshrc
plugins=(
  git
  fzf
  fzf-tools      # happycod3r/fzf-tools
  # fzf-zsh-plugin if you install it as a plugin
)

source "$ZSH/oh-my-zsh.sh"

4. Generic “fzf script runner”

Instead of wiring each function into your shell, keep small scripts in a directory and have a single runner to pick and execute them. github

fzf_run_scripts() {
  local root="${1:-$HOME/scripts}"
  shift || true

  local script
  script=$(
    find "$root" -type f \( -name '*.sh' -o -name '*.py' -o -name '*.rb' -o -name '*.js' \) 2>/dev/null \
    | fzf --multi --prompt='scripts> ' \
          --preview 'head -n 60 {}'
  ) || return

  while IFS= read -r s; do
    case "$s" in
      *.sh)  bash "$s" "$@" ;;
      *.zsh) zsh  "$s" "$@" ;;
      *.py)  python "$s" "$@" ;;
      *.rb)  ruby "$s" "$@" ;;
      *.js)  node  "$s" "$@" ;;
      *)     echo "Unknown type: $s" ;;
    esac
  done <<<"$script"
}

5. Separate by concern and source conditionally

Finally, the core fzf repo itself recommends small, focused shell integration files and sourcing them as needed (e.g. completion.bash, key-bindings.zsh). You can mirror that pattern: github

  • fzf-git.sh – git helpers (branches, log, stash, conflicts)
  • fzf-dev.sh – tests, Make targets, npm scripts, etc.
  • fzf-sys.sh – processes, systemd, logs

and load only what you need per host or shell session using the loader in section 1.

Handy fzf Scripts

This collection contains small shell functions to integrate fzf into everyday workflows: file navigation, searching with ripgrep, Git, processes, man pages, and more.

Usage: Put the functions into a file like ~/.fzf_scripts.sh and source it from your .bashrc / .zshrc. Adjust tools like bat, rg, fd, wl-copy as needed.


Global fzf defaults

Sets reasonable default options for fzf and tweaks the standard CTRL‑T / ALT‑C / CTRL‑R bindings for better previews and navigation.

export FZF_DEFAULT_OPTS="
  --height 80%
  --layout=reverse
  --border
  --ansi
  --bind 'tab:toggle-down,btab:toggle-up'
"

export FZF_CTRL_T_OPTS="
  --walker-skip .git,node_modules,target
  --preview 'bat -n --color=always {}'
  --bind 'ctrl-/:change-preview-window(down|hidden|)'
"

export FZF_ALT_C_OPTS="
  --preview 'ls --color=always {}'
"

export FZF_CTRL_R_OPTS="
  --sort
  --preview 'echo {}'
"

Files and directories

ff / ffm let you pick files with fzf and open them in your $EDITOR.
cdd lets you jump to any directory found by fd.
cdbh navigates through your shell directory stack via fzf.

ff() {
  local file
  file=$(fd . --type f 2>/dev/null | fzf --prompt='file> ') || return
  "${EDITOR:-vim}" "$file"
}

ffm() {
  local files
  files=$(fd . --type f 2>/dev/null | fzf --multi --prompt='files> ') || return
  "${EDITOR:-vim}" $files
}

cdd() {
  local dir
  dir=$(fd . --type d 2>/dev/null | fzf --prompt='cd> ') || return
  cd "$dir" || return
}

cdbh() {
  local dir
  dir=$(dirs -v | fzf --tac --prompt='dirs> ' | awk '{print $2}') || return
  cd "$dir" || return
}

ripgrep + fzf

frg combines ripgrep and fzf: search for a pattern, select a match, and open the file at the correct line in your editor.
frgf finds all files containing a pattern and lets you select one or many to edit.

frg() {
  local query file
  query="$1"
  if [[ -z "$query" ]]; then
    echo "Usage: frg <pattern>"
    return 1
  fi

  file=$(
    rg --color=always --line-number --no-heading "$query" \
    | fzf --ansi \
          --prompt="rg: $query > " \
          --delimiter ':' \
          --preview 'bat --color=always --style=numbers --line-range {2}: {1}' \
          --preview-window='up,60%,border-bottom'
  ) || return

  local filename lineno
  filename=$(echo "$file" | cut -d: -f1)
  lineno=$(echo "$file"   | cut -d: -f2)
  "${EDITOR:-vim}" "+${lineno}" "$filename"
}

frgf() {
  local query files
  query="$1"
  if [[ -z "$query" ]]; then
    echo "Usage: frgf <pattern>"
    return 1
  fi

  files=$(
    rg "$query" --files-with-matches 2>/dev/null \
    | fzf --multi --prompt="rg files: $query > "
  ) || return

  "${EDITOR:-vim}" $files
}

Git helpers

fglog shows an interactive Git log with a preview of the selected commit.
fgcopy lets you pick a commit and copies its hash to your clipboard (or prints it).
fgb lists all branches (local and remote) and checks out the selected one.

fglog() {
  git log --graph --color=always \
    --format='%C(auto)%h%d %s %C(black)%C(bold)%cr%Creset %C(green)%an' \
  | fzf --ansi --no-sort --reverse --tiebreak=index \
        --preview 'echo {} | grep -oE "^[^ ]+" | xargs git show --color=always' \
        --preview-window=right:60%
}

fgcopy() {
  local hash
  hash=$(
    git log --color=always --oneline \
    | fzf --ansi --no-sort --reverse \
    | awk '{print $1}'
  ) || return

  if command -v wl-copy >/dev/null 2>&1; then
    printf '%s' "$hash" | wl-copy
  elif command -v xclip >/dev/null 2>&1; then
    printf '%s' "$hash" | xclip -selection clipboard
  elif command -v xsel >/dev/null 2>&1; then
    printf '%s' "$hash" | xsel --clipboard --input
  else
    echo "$hash"
  fi
}

fgb() {
  local branch
  branch=$(
    git branch --all --color=always | sed 's/^..//' \
    | fzf --ansi --prompt='branch> ' \
          --preview 'git log --oneline --graph --decorate --color=always --max-count=20 {1}' \
          --preview-window=right:60%
  ) || return
  git checkout "$(echo "$branch" | sed 's#remotes/[^/]*/##')"
}

Processes and system

fkill lists running processes and lets you kill one or many via fzf.
fenv searches your environment variables and prints the selected value.
fman offers a fuzzy interface over man -k and previews man pages.

fkill() {
  local pid
  pid=$(
    ps -ef | sed 1d \
    | fzf --multi --prompt='kill> ' \
          --preview 'echo {}' \
    | awk '{print $2}'
  ) || return

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

fenv() {
  local out
  out=$(env | fzf --prompt='env> ') || return
  echo "${out#*=}"
}

fman() {
  local MAN_CMD
  MAN_CMD="${MAN:-/usr/bin/man}"

  if [[ -n "$1" ]]; then
    "$MAN_CMD" "$@"
    return
  fi

  local selection page
  selection=$(
    "$MAN_CMD" -k . 2>/dev/null \
    | fzf --prompt='man> ' --reverse \
          --preview="echo {} | awk '{print \$1\" \"\$2}' | sed 's/(/ /;s/)//' | xargs $MAN_CMD" \
          --preview-window=right:60%
  ) || return

  page=$(echo "$selection" | awk '{print $1"."$2}' | tr -d '()')
  "$MAN_CMD" "$page"
}

Miscellaneous helpers

fh provides an interactive fuzzy search over your shell history and executes the chosen command.
fgchanged shows changed Git files and opens the selected ones in your editor.
frm lets you pick files in the current directory to remove, instead of passing names manually to rm.

fh() {
  local cmd
  if [[ -n "$ZSH_NAME" ]]; then
    cmd=$(
      fc -l 1 \
      | fzf --tac --no-sort --prompt='history> ' --preview 'echo {}' \
      | sed 's/^[[:space:]]*[0-9]\+[[:space:]]*//'
    ) || return
  else
    cmd=$(
      history \
      | fzf --tac --no-sort --prompt='history> ' --preview 'echo {}' \
      | sed 's/^[[:space:]]*[0-9]\+[[:space:]]*//'
    ) || return
  fi

  print -s "$cmd" 2>/dev/null || history -s "$cmd" 2>/dev/null
  eval "$cmd"
}

fgchanged() {
  local files
  files=$(
    git diff --name-only --diff-filter=ACMRTUXB HEAD 2>/dev/null \
    | fzf --multi --prompt='changed> ' \
          --preview 'git diff --color=always -- {-1} | sed -n "1,200p"' \
          --preview-window=right:60%
  ) || return

  "${EDITOR:-vim}" $files
}

frm() {
  if [[ "$#" -ne 0 ]]; then
    command rm "$@"
    return
  fi

  local files
  files=$(
    find . -maxdepth 1 -type f 2>/dev/null \
    | sed 's#^\./##' \
    | fzf --multi --prompt='rm> ' \
          --preview 'ls -l --color=always {}'
  ) || return

  echo "$files" | xargs -r rm
}

Recent files (using fd + stat)

Lists files in the current tree, sorted by modification time, and lets you pick one or more to edit.

frecent() {
  local files
  files=$(
    fd . --type f 2>/dev/null \
    | xargs -r stat -c '%Y %n' 2>/dev/null \
    | sort -nr \
    | cut -d' ' -f2- \
    | fzf --multi --prompt='recent> '
  ) || return

  "${EDITOR:-vim}" $files
}

SSH host picker (from ~/.ssh/config and known_hosts)

Collects hostnames from SSH config and known_hosts, lets you pick one, then connects.

fssh() {
  local host
  host=$(
    {
      grep -iE '^Host ' ~/.ssh/config 2>/dev/null | awk '{for (i=2;i<=NF;i++) print $i}'
      [ -f ~/.ssh/known_hosts ] && cut -f1 -d' ' ~/.ssh/known_hosts | sed 's/[,\ ]/\n/g'
    } | sed 's/[*,]//g' | sed '/^$/d' | sort -u \
      | fzf --prompt='ssh> '
  ) || return

  ssh "$host"
}

Kubernetes context and namespace picker (kubectl)

Quickly switch kubectl context and namespace via fzf.

fkc() {
  local ctx
  ctx=$(
    kubectl config get-contexts -o name 2>/dev/null \
    | fzf --prompt='kubectx> '
  ) || return

  kubectl config use-context "$ctx"
}

fkns() {
  local ns
  ns=$(
    kubectl get ns -o jsonpath='{range .items[*]}{.metadata.name}{"\n"}{end}' 2>/dev/null \
    | fzf --prompt='kube-ns> '
  ) || return

  kubectl config set-context --current --namespace="$ns"
}

Docker container and image picker

Start a shell in a running container or run a new container from an image, both via fzf.

fdocker_shell() {
  local id
  id=$(
    docker ps --format '{{.ID}} {{.Image}} {{.Names}}' \
    | fzf --prompt='docker ps> ' --preview 'echo {}'
  ) || return

  id=${id%% *}
  docker exec -it "$id" "${1:-/bin/bash}"
}

fdocker_run() {
  local img
  img=$(
    docker images --format '{{.Repository}}:{{.Tag}} {{.ID}}' \
    | fzf --prompt='docker image> '
  ) || return

  img=${img%% *}
  docker run -it --rm "$img" "${@:-/bin/bash}"
}

Git stash picker

List stashes with description, preview their diff, and apply or pop the selected one.

fgstash() {
  local line stash
  line=$(
    git stash list \
    | fzf --prompt='stash> ' \
          --preview 'echo {} | cut -d: -f1 | xargs git stash show -p --color=always' \
          --preview-window=right:60%
  ) || return

  stash=$(echo "$line" | cut -d: -f1)

  case "${1:-apply}" in
    apply) git stash apply "$stash" ;;
    pop)   git stash pop "$stash" ;;
    show)  git stash show -p "$stash" ;;
    *)     echo "Usage: fgstash [apply|pop|show]" ;;
  esac
}

Open URL from browser history (example: sqlite + fzf)

Example for Chromium‑based browsers; adjust DB path and query for your setup.

furl_chromium() {
  local db url
  db="$HOME/.config/chromium/Default/History"
  [ -f "$db" ] || { echo "Chromium history DB not found"; return 1; }

  url=$(
    sqlite3 "$db" \
      "select url, title, last_visit_time from urls order by last_visit_time desc" 2>/dev/null \
    | awk -F'|' '{printf "%s\t%s\n", $1, $2}' \
    | fzf --prompt='url> ' --with-nth=2 --preview 'echo {1}' \
    | cut -f1
  ) || return

  [ -n "$url" ] && xdg-open "$url" >/dev/null 2>&1 &
}

Test file runner (Go/Python/Jest)

Finds test files and runs the selected one(s) with appropriate runner.

ftest() {
  local test runner
  test=$(
    fd --extension _test.go,test.py,spec.js,test.js . \
    | fzf --multi --prompt='test> '
  ) || return
  
  case "$test" in
    *.go)  runner='go test' ;;
    *.py)  runner='pytest' ;;
    *.js)  runner='npm test --' ;;
    *)     return 1 ;;
  esac
  
  echo "$test" | xargs -d '\n' $runner
}

Cargo crate dependency picker

Fuzzy search through Cargo.toml dependencies and opens docs or repo.

fcargo_dep() {
  local dep
  dep=$(
    grep -E '^[^#]*=' Cargo.toml \
    | sed 's/.*= "\([^"]*\)".*/\1/' \
    | fzf --prompt='crate> '
  ) || return
  
  echo "https://docs.rs/$dep" | xdg-open -
}

Docker compose service manager

Lists docker-compose services and runs up/down/logs on selected.

fdcompose() {
  local svc action
  svc=$(
    docker compose ps --services \
    | fzf --prompt='dc svc> '
  ) || return
  
  action="${1:-logs}"
  docker compose "$action" "$svc"
}

LSP language server status

Shows running LSP servers (via lsof) and restarts selected one.

flsp() {
  local server
  server=$(
    lsof -i -P -n | grep -i lsp \
    | awk '{print $1" "$2" "$NF}' \
    | fzf --prompt='lsp> '
  ) || return
  
  pid=$(echo "$server" | awk '{print $2}')
  kill -9 "$pid" && echo "Restart $server LSP"
}

Makefile target runner

Fuzzy pick from Makefile targets and runs make <target>.

fmake() {
  local target
  target=$(
    make -qp | awk -F':' '/^[a-zA-Z0-9][^\$#\/:=()]{1,}/ {print $1}' \
    | grep -v '^:' | sort | uniq \
    | fzf --prompt='make> '
  ) || return
  
  make "$target"
}

Node modules inspector

Searches through node_modules and shows package.json + version.

fnodemod() {
  local pkg
  pkg=$(
    fd package.json node_modules \
    | sed 's|.*/node_modules/||; s|/package.json||' \
    | fzf --prompt='npm pkg> ' \
          --preview 'jq .name,jq .version {}/package.json'
  ) || return
  
  cd "node_modules/$pkg" && npm list .
}

Git worktree manager

Lists git worktrees and switches to selected one.

fgworktree() {
  local wt
  wt=$(
    git worktree list \
    | fzf --prompt='worktree> '
  ) || return
  
  cd "$(echo "$wt" | awk '{print $1}')"
}

TypeScript declaration picker

Finds .d.ts files and opens them for type inspection.

ftsd() {
  local decl
  decl=$(
    fd --extension d.ts . \
    | fzf --prompt='d.ts> ' \
          --preview 'bat --language=typescript {}'
  ) || return
  
  "${EDITOR:-vim}" "$decl"
}

Pre-commit hook inspector

Shows git pre-commit hooks and their contents.

fprecommit() {
  local hook
  hook=$(
    ls -la .git/hooks/pre-commit* 2>/dev/null \
    | fzf --prompt='hook> ' \
          --preview 'cat {}'
  ) || return
  
  "${EDITOR:-vim}" "$hook"
}

Coverage report navigator

Fuzzy search through coverage reports (lcov/simplecov) and jumps to uncovered lines.

fcoverage() {
  local file
  file=$(
    rg -g '*.lcov,*.json' 'UNCOVERED\|Missed' coverage/ \
    | fzf --prompt='coverage> '
  ) || return
  
  "${EDITOR:-vim}" "$(echo "$file" | cut -d: -f1)"
}

Open recent project directories

Keep a simple text file with one project path per line (e.g. ~/.projects) and jump into one via fzf.

fproj() {
  local projfile="${PROJECT_LIST:-$HOME/.projects}"
  [[ -f "$projfile" ]] || { echo "No project list at $projfile"; return 1; }

  local dir
  dir=$(
    cat "$projfile" \
    | fzf --prompt='project> ' --preview 'ls -la {} 2>/dev/null'
  ) || return

  cd "$dir" || return
}

Jump to git repositories under a root

Scan a development root (e.g. ~/dev) for git repos and cd into the selected one.

frepo() {
  local root="${DEV_ROOT:-$HOME/dev}"
  local dir
  dir=$(
    fd . "$root" --type d --hidden --exclude '.git' -d 4 2>/dev/null \
    | while read -r d; do
        [[ -d "$d/.git" ]] && echo "$d"
      done \
    | fzf --prompt='repo> ' --preview 'git -C {} status -sb 2>/dev/null'
  ) || return

  cd "$dir" || return
}

Search TODO / FIXME comments across code

Search for TODO/FIXME markers, preview their context, and jump to the file/line in your editor.

ftodo_code() {
  local match file line
  match=$(
    rg --no-heading --line-number 'TODO|FIXME' . 2>/dev/null \
    | fzf --ansi --prompt='todo/fixme> ' \
          --preview 'bat --style=numbers --color=always {1} --line-range {2}:+20'
  ) || return

  file=$(echo "$match" | cut -d: -f1)
  line=$(echo "$match" | cut -d: -f2)
  "${EDITOR:-vim}" "+${line}" "$file"
}

Git file picker: tracked, untracked, ignored

Pick files from git (tracked, modified, untracked) and open in your editor.

fgit_files() {
  local file
  file=$(
    git status --short \
    | fzf --ansi --prompt='git-files> ' \
          --preview 'git diff --color=always -- {2} || bat --style=numbers --color=always {2}' \
          --preview-window=right:60% \
    | awk '{print $2}'
  ) || return

  "${EDITOR:-vim}" "$file"
}

Git conflict resolver

List files with merge conflicts and open the selected one.

fconflict() {
  local file
  file=$(
    git diff --name-only --diff-filter=U 2>/dev/null \
    | fzf --prompt='conflicts> ' \
          --preview 'bat --style=numbers --color=always {}'
  ) || return

  "${EDITOR:-vim}" "$file"
}

Interactive stash browser

Browse git stashes with preview and apply/pop/show the selected one.

fgstash() {
  local line stash
  line=$(
    git stash list \
    | fzf --prompt='stash> ' \
          --preview 'echo {} | cut -d: -f1 | xargs git stash show -p --color=always' \
          --preview-window=right:60%
  ) || return

  stash=$(echo "$line" | cut -d: -f1)

  case "${1:-apply}" in
    apply) git stash apply "$stash" ;;
    pop)   git stash pop "$stash" ;;
    show)  git stash show -p "$stash" ;;
    *)     echo "Usage: fgstash [apply|pop|show]" ;;
  esac
}

Git worktree picker

List git worktrees and change directory to the selected one.

fgworktree() {
  local wt
  wt=$(
    git worktree list 2>/dev/null \
    | fzf --prompt='worktree> '
  ) || return

  cd "$(echo "$wt" | awk '{print $1}')" || return
}

Run Makefile targets

List Makefile targets and run the selected one via make.

fmake() {
  local target
  target=$(
    make -qp 2>/dev/null \
    | awk -F':' '/^[a-zA-Z0-9][^$#\/:=()]*:/{print $1}' \
    | sort -u \
    | fzf --prompt='make> '
  ) || return

  make "$target"
}

Run package.json scripts (npm / pnpm / yarn)

Pick a script from package.json and run it with your preferred package manager.

fpkg_script() {
  local mgr script
  mgr="${PKG_MGR:-npm}"

  script=$(
    jq -r '.scripts | keys[]' package.json 2>/dev/null \
    | fzf --prompt='pkg script> ' \
          --preview 'jq -r ".scripts[\"{}\"]" package.json'
  ) || return

  case "$mgr" in
    npm)  npm run "$script" ;;
    pnpm) pnpm run "$script" ;;
    yarn) yarn "$script" ;;
    *)    "$mgr" "$script" ;;
  esac
}

System‑wide ripgrep search with file picker

Search for a pattern across the repo, pick a match, and open it at the matching line.

frg_open() {
  local query match file line
  query="$1"
  if [[ -z "$query" ]]; then
    read -rp "Search pattern: " query
  fi

  match=$(
    rg --color=always --line-number --no-heading "$query" . 2>/dev/null \
    | fzf --ansi --prompt="rg: $query > " \
          --preview 'bat --style=numbers --color=always {1} --line-range {2}:+20'
  ) || return

  file=$(echo "$match" | cut -d: -f1)
  line=$(echo "$match" | cut -d: -f2)
  "${EDITOR:-vim}" "+${line}" "$file"
}

Switch tmux sessions via fzf

Pick a tmux session and attach to it (or create one if none exists).

ftmux() {
  if ! command -v tmux >/dev/null 2>&1; then
    echo "tmux not installed"
    return 1
  fi

  local session
  session=$(
    tmux list-sessions -F '#S' 2>/dev/null \
    | fzf --prompt='tmux> '
  )

  if [[ -z "$session" ]]; then
    tmux new-session
  else
    tmux attach-session -t "$session"
  fi
}

Browse and open dotfiles

Keep your dotfiles in ~/.dotfiles (or similar) and use fzf to open them quickly.

fdot() {
  local root="${DOTFILES_ROOT:-$HOME/.dotfiles}"
  local file
  file=$(
    fd . "$root" --type f 2>/dev/null \
    | fzf --prompt='dotfile> ' \
          --preview 'bat --style=numbers --color=always {}'
  ) || return

  "${EDITOR:-vim}" "$file"
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment