Skip to main content

Reusable Shell Functions

This is a production-ready fzf functions library. Add all relevant functions to your ~/.bashrc / ~/.zshrc or source a dedicated ~/.fzf-functions.sh file.

Setup

~/.bashrc or ~/.zshrc
# Source your fzf functions file
[ -f ~/.fzf-functions.sh ] && source ~/.fzf-functions.sh

File Functions

~/.fzf-functions.sh
#!/usr/bin/env bash

# ── fe: fuzzy edit ─────────────────────────────────────────────────
# Open files with preview; multi-select to open all
fe() {
local files
IFS=$'\n' files=($(
fzf --multi \
--preview 'bat --color=always --style=numbers --line-range=:300 {}' \
--preview-window 'right:60%:hidden' \
--bind 'ctrl-/:toggle-preview' \
--query="$1" \
--header 'Tab: mark Enter: open CTRL-/: preview' \
< <(fd --type f --hidden --exclude .git .)
))
[[ -n "${files[@]}" ]] && ${EDITOR:-vim} "${files[@]}"
}

# ── fcd: fuzzy cd ──────────────────────────────────────────────────
fcd() {
local dir
dir=$(fd --type d --hidden --exclude .git . "${1:-.}" | fzf \
--preview 'tree -C -L 2 {} | head -100' \
--preview-window 'right:40%' \
--prompt '📁 cd> ' \
--header 'CTRL-/: toggle tree preview') && cd "$dir" || return
}

# ── frm: fuzzy rm ─────────────────────────────────────────────────
frm() {
local files
IFS=$'\n' files=($(
fd --type f . "${1:-.}" | fzf \
--multi \
--preview 'bat --color=always {}' \
--preview-window 'right:60%:hidden' \
--bind 'ctrl-/:toggle-preview' \
--header 'Tab: mark Enter: DELETE CTRL-/: preview' \
--prompt '🗑 Delete> '
))
if [[ ${#files[@]} -gt 0 ]]; then
echo "Deleting ${#files[@]} file(s):"
printf ' %s\n' "${files[@]}"
read -rp "Confirm? [y/N] " confirm
[[ "$confirm" == "y" ]] && rm -v "${files[@]}"
fi
}

# ── fgrep: interactive ripgrep ────────────────────────────────────
fgrep() {
local RG="rg --column --line-number --no-heading --color=always --smart-case"
local result
result=$(
fzf --ansi \
--disabled \
--query="${1:-}" \
--bind "start:reload:$RG {q}" \
--bind "change:reload:sleep 0.1; $RG {q} || true" \
--bind 'ctrl-/:toggle-preview' \
--delimiter=: \
--preview 'bat --color=always --style=numbers --highlight-line={2} {1}' \
--preview-window 'right:60%:hidden:+{2}/2' \
--prompt '🔍 Grep> ' \
--header 'Type to grep CTRL-/: preview'
)
[[ -z "$result" ]] && return
local file line
file=$(echo "$result" | cut -d: -f1)
line=$(echo "$result" | cut -d: -f2)
${EDITOR:-vim} "+$line" "$file"
}

Git Functions

~/.fzf-functions.sh (continued)
# ── fgb: fuzzy git branch checkout ───────────────────────────────
fgb() {
local branch
branch=$(git branch --all | grep -v HEAD | sed 's/^[* ]*//' | sed 's#remotes/origin/##' | sort -u | fzf \
--preview 'git log --oneline --graph --color=always $(echo {} | sed "s#remotes/origin/##") | head -30' \
--preview-window 'right:50%' \
--prompt '🌿 Branch> ' \
--header 'Checkout a git branch')
[[ -n "$branch" ]] && git checkout "$branch"
}

# ── fgco: fuzzy git commit checkout ─────────────────────────────
fgco() {
local commit
commit=$(git log --oneline --color=always | fzf \
--ansi \
--preview 'git show --color=always {1}' \
--preview-window 'right:60%' \
--prompt '📋 Commit> ' \
--header 'Select commit to checkout' | awk '{print $1}')
[[ -n "$commit" ]] && git checkout "$commit"
}

# ── fgd: fuzzy git diff ──────────────────────────────────────────
fgd() {
local file
file=$(git diff --name-only | fzf \
--preview 'git diff --color=always {}' \
--preview-window 'right:70%' \
--prompt '📝 Diff> ' \
--header 'Changed files')
[[ -n "$file" ]] && git diff "$file"
}

# ── fgst: fuzzy git stash apply ──────────────────────────────────
fgst() {
local stash
stash=$(git stash list | fzf \
--preview 'git stash show -p $(echo {} | grep -o "stash@{[0-9]*}")' \
--preview-window 'right:60%' \
--prompt 'Stash> ' \
--header 'Enter: apply (stash stays)' | grep -o 'stash@{[0-9]*}')
[[ -n "$stash" ]] && git stash apply "$stash"
}

System Functions

~/.fzf-functions.sh (continued)
# ── fkill: fuzzy process kill ─────────────────────────────────────
fkill() {
local pid signal="${1:-TERM}"
pid=$(ps aux | fzf \
--header-lines=1 \
--preview 'ps --pid=$(echo {} | awk "{print \$2}") -o pid,ppid,cmd,start,etime,pcpu,pmem 2>/dev/null' \
--preview-window 'down:5' \
--prompt '⚡ Kill> ' \
--header "Select process to kill with SIG$signal" \
| awk '{print $2}')
[[ -n "$pid" ]] && kill -"$signal" "$pid" && echo "Sent SIG$signal to PID $pid"
}

# ── fenv: fuzzy environment variable view/copy ────────────────────
fenv() {
local var
var=$(env | sort | fzf \
--preview 'echo {}' \
--preview-window 'down:3:wrap' \
--prompt '🌍 Env> ' \
--header 'Browse environment variables')
[[ -n "$var" ]] && echo "$var"
}

# ── fman: fuzzy man page browser ─────────────────────────────────
fman() {
local page
page=$(man -k . | fzf \
--preview 'man $(echo {} | awk "{print \$1}")' \
--preview-window 'right:60%:wrap' \
--prompt '📖 Man> ' \
--header 'Select man page') && man "$(echo "$page" | awk '{print $1}')"
}

# ── fssh: fuzzy SSH ───────────────────────────────────────────────
fssh() {
local host
host=$({
grep -E "^Host " ~/.ssh/config 2>/dev/null | awk '{print $2}' | grep -v '\*'
cat ~/.ssh/known_hosts 2>/dev/null | awk '{print $1}' | tr ',' '\n' | grep -v '^\[' | sort -u
} | sort -u | fzf \
--prompt '🔐 SSH> ' \
--header 'Select SSH host' \
--preview 'grep -A5 "^Host {}" ~/.ssh/config 2>/dev/null || echo "{}"\n(no config found)')
[[ -n "$host" ]] && ssh "$host"
}

Docker Functions

~/.fzf-functions.sh (continued)
# ── fdps: select running container ────────────────────────────────
fdps() {
local container
container=$(docker ps --format "table {{.Names}}\t{{.Image}}\t{{.Status}}\t{{.Ports}}" | fzf \
--header-lines=1 \
--preview 'docker inspect $(echo {} | awk "{print \$1}") | jq ".[0] | {Name:.Name,Image:.Config.Image,Env:.Config.Env,Ports:.NetworkSettings.Ports}"' \
--preview-window 'right:50%' \
--prompt '🐳 Container> ' \
| awk '{print $1}')
echo "$container"
}

# ── fdexec: exec into container shell ────────────────────────────
fdexec() {
local container shell
container=$(fdps)
[[ -z "$container" ]] && return
shell=$(echo -e "bash\nsh\nzsh" | fzf --prompt "Shell> " --height=5)
[[ -n "$shell" ]] && docker exec -it "$container" "$shell"
}

# ── fdlogs: tail container logs ───────────────────────────────────
fdlogs() {
local container
container=$(docker ps --format "{{.Names}}" | fzf --prompt '📋 Logs> ')
[[ -n "$container" ]] && docker logs -f --tail=100 "$container"
}

# ── fdimg: manage docker images ───────────────────────────────────
fdimg() {
local image
image=$(docker images --format "table {{.Repository}}\t{{.Tag}}\t{{.Size}}\t{{.CreatedSince}}" | fzf \
--header-lines=1 \
--prompt '🖼 Image> ' \
--header 'Enter: inspect Ctrl+D: delete' \
--expect=ctrl-d \
| tail -1 | awk '{print $1":"$2}')

[[ -z "$image" ]] && return
docker inspect "$image" | jq '.[0] | {Name:.RepoTags,Cmd:.Config.Cmd,Env:.Config.Env}'
}

What's Next