Supercharging Unix Search Commands with fzf: A Power User’s Guide
In my previous article on Unix search commands, we explored the fundamentals of tools like find, grep, fd, and ripgrep. These tools are already quite powerful on their own, but there’s a way to take your search workflow to the next level: fzf, the command-line fuzzy finder.
As someone who spends most of their day in a terminal, discovering fzf was one of those rare “aha!” moments that fundamentally changed how I interact with my computer. It’s like gaining a superpower that makes everything from finding files to navigating command history delightfully efficient.
What Makes fzf So Special?
fzf is a general-purpose fuzzy finder for the command line. At its core, it takes a list of items, lets you filter them with a simple, fuzzy search interface, and returns your selection. This might sound basic, but it’s the integration with other tools that makes it magical.
Unlike most command-line tools, fzf provides an interactive interface with real-time feedback. Type a few characters, and it instantly narrows down options, showing you matches as you type.
Let’s start with installation and then dive into practical uses.
Getting Started with fzf
Installing fzf is straightforward on most systems:
1# On macOS
2brew install fzf
3
4# On Ubuntu/Debian
5apt install fzf
6
7# On other systems or to get the latest version
8git clone --depth 1 https://github.com/junegunn/fzf.git ~/.fzf
9~/.fzf/install
After installation, run the included install script to set up shell integration. This adds key bindings and shell completions that we’ll explore later.
Basic fzf Usage
At its simplest, fzf reads input from stdin and writes the selected item to stdout:
1# Select a file in the current directory
2find . -type f | fzf
This command lists all files in the current directory and its subdirectories, then lets you filter and select one.
What makes fzf special is its interactive interface:
- Type to filter (fuzzy matching)
- Use arrow keys to navigate
- Press Enter to select
- Use Ctrl+C or Esc to cancel
The selected item is then printed to stdout, making it easy to pipe into other commands.
Supercharging File Searches
1. Find and Edit Files
One of the most common use cases is finding and opening files for editing:
1vim $(find . -type f | fzf)
But we can make this even better with fd:
1vim $(fd --type f | fzf)
Or create a shell function for even quicker access:
1# Add to your .bashrc or .zshrc
2fe() {
3 local file
4 file=$(fd --type f --hidden --exclude .git | fzf --preview 'bat --color=always {}')
5 if [ -n "$file" ]; then
6 ${EDITOR:-vim} "$file"
7 fi
8}
This function uses bat (a cat clone with syntax highlighting) to show a preview of the file contents as you browse.
2. Searching Through File Contents
Combining ripgrep and fzf creates a powerful search-within-files workflow:
1# Search file contents and open selected match in vim
2rg "function" --line-number | fzf | awk -F ':' '{print $1 " +" $2}' | xargs vim
This is a powerful pattern, but let’s make it more elegant with a custom function:
1# Add to your .bashrc or .zshrc
2fif() {
3 if [ ! "$#" -gt 0 ]; then
4 echo "Need a search term"
5 return 1
6 fi
7
8 local match=$(
9 rg --line-number --no-heading "$1" |
10 fzf --preview "
11 FILE=\$(echo {} | cut -d: -f1)
12 LINE=\$(echo {} | cut -d: -f2)
13 bat --color=always \$FILE --highlight-line \$LINE --line-range \$((LINE-10)):\$((LINE+10))
14 "
15 )
16
17 if [ -n "$match" ]; then
18 local file=$(echo "$match" | cut -d: -f1)
19 local line=$(echo "$match" | cut -d: -f2)
20 ${EDITOR:-vim} "$file" +$line
21 fi
22}
Now you can search and navigate to any text in your project with:
1fif "function initApp"
This function even provides context around the matched line in the preview window.
Directory Navigation on Steroids
1. Quick Directory Jumping
fzf can transform how you navigate between directories:
1# Add to your .bashrc or .zshrc
2fd() {
3 local dir
4 dir=$(find ${1:-.} -path '*/\.*' -prune -o -type d -print 2> /dev/null | fzf +m) &&
5 cd "$dir"
6}
But we can make it even better by integrating with your shell history:
1# Add to your .bashrc or .zshrc
2cdf() {
3 local dir
4 dir=$(
5 find ~/code ~/projects -type d -name .git -prune -o -type d -print 2> /dev/null |
6 fzf --preview 'ls -la --color=always {}'
7 ) &&
8 cd "$dir"
9}
This function lets you jump to any directory in your code and projects folders, with a preview of the directory contents.
2. Browsing Command History
fzf comes with Ctrl+R integration that supercharges your history search:
1# This is usually set up by fzf's install script
2# Press Ctrl+R to search through command history
3export FZF_CTRL_R_OPTS="--preview 'echo {}' --preview-window down:3:hidden:wrap --bind '?:toggle-preview'"
This makes history searching infinitely more useful than the default Ctrl+R.
Git Workflow Enhancement
Git and fzf are a match made in heaven:
1. Interactive Branch Switching
1# Add to your .bashrc or .zshrc
2fbr() {
3 local branches branch
4 branches=$(git branch --all | grep -v HEAD) &&
5 branch=$(echo "$branches" |
6 fzf --height 40% --reverse --preview "git log --oneline --graph --date=short --color=always --pretty='format:%C(auto)%h%d %s %C(green)%cr' {1}") &&
7 git checkout $(echo "$branch" | sed "s/.* //" | sed "s#remotes/[^/]*/##")
8}
This function shows branches with a preview of their commit history, making it easy to see what you’re switching to before you commit.
2. Commit Browser
1# Add to your .bashrc or .zshrc
2fco() {
3 local commits commit
4 commits=$(git log --pretty=oneline --abbrev-commit --reverse) &&
5 commit=$(echo "$commits" | fzf --preview "echo {} | cut -d' ' -f1 | xargs -I% git show --color=always % | bat") &&
6 git checkout $(echo "$commit" | sed "s/ .*//")
7}
This lets you browse through all commits and see what changed in each one.
3. Interactive Git Add
1# Add to your .bashrc or .zshrc
2fga() {
3 local files
4 files=$(git status --short |
5 fzf --multi --preview 'git diff --color=always {2}' |
6 cut -c4- |
7 sed 's/.* -> //')
8 if [ -n "$files" ]; then
9 git add $files
10 fi
11}
This shows a list of changed files with previews of the differences, allowing you to selectively stage files.
Advanced Data Processing
fzf can be transformative for working with structured data:
1. Process Selection
1# Add to your .bashrc or .zshrc
2fkill() {
3 local pid
4 pid=$(ps -ef | sed 1d | fzf -m --header="Select process to kill" | awk '{print $2}')
5
6 if [ "x$pid" != "x" ]; then
7 echo $pid | xargs kill -${1:-9}
8 fi
9}
This function lets you interactively select and kill processes.
2. Docker Container Management
1# Add to your .bashrc or .zshrc
2fdc() {
3 local cid
4 cid=$(docker ps | sed 1d | fzf -q "$1" | awk '{print $1}')
5
6 [ -n "$cid" ] && docker exec -it "$cid" bash
7}
This lets you quickly select and enter any running Docker container.
3. Working with JSON Data
1# Add to your .bashrc or .zshrc
2jqf() {
3 local file="$1"
4 local query
5 query=$(jq -r 'paths | map(tostring) | join(".")' "$file" |
6 fzf --preview "jq '{\"\(.)\": .\(.)}' $file")
7
8 if [ -n "$query" ]; then
9 jq ".${query}" "$file"
10 fi
11}
This function helps you interactively explore and extract data from complex JSON files.
Integration with Other Command-Line Tools
The real power of fzf emerges when you combine it with other tools in your workflow:
1. Supercharged Man Pages
1# Add to your .bashrc or .zshrc
2fman() {
3 man -k . | fzf --prompt='Man> ' | awk '{print $1}' | xargs -r man
4}
This lets you search through all available man pages and open the one you select.
2. Enhanced SSH Host Selection
1# Add to your .bashrc or .zshrc
2fssh() {
3 local host
4 host=$(grep "^Host " ~/.ssh/config | grep -v "*" | cut -d " " -f 2 | fzf)
5
6 if [ -n "$host" ]; then
7 ssh "$host"
8 fi
9}
Quickly select and connect to any host in your SSH config.
3. AWS Resources
For cloud engineers, fzf can help navigate the AWS CLI:
1# Add to your .bashrc or .zshrc
2faws() {
3 local instance
4 instance=$(aws ec2 describe-instances --query 'Reservations[].Instances[].[InstanceId,Tags[?Key==`Name`].Value|[0],State.Name,PrivateIpAddress,PublicIpAddress]' --output text |
5 column -t |
6 fzf --header="InstanceID Name Status PrivateIP PublicIP" |
7 awk '{print $1}')
8
9 if [ -n "$instance" ]; then
10 aws ssm start-session --target "$instance"
11 fi
12}
This function lists EC2 instances and lets you start an SSM session with the selected one.
Building Your Custom fzf Workflow
These examples just scratch the surface. The real power comes from tailoring fzf to your specific workflow. Here are some tips for creating your own fzf functions:
1. Consider the Input Source
Start by identifying what list you want to filter:
- Files with specific patterns
- Lines from log files
- Items from APIs or databases
- Command output
2. Add Useful Previews
The --preview option is what makes fzf truly shine:
1fzf --preview 'cat {}' # Preview file contents
2fzf --preview 'jq . {}' # Preview JSON files
3fzf --preview 'git show {}' # Preview git commits
3. Use Multi-selection When Appropriate
The -m or --multi flag allows selecting multiple items with Tab:
1find . -type f | fzf -m | xargs grep "pattern"
4. Customize Key Bindings
Create custom key bindings for common actions:
1fzf --bind 'ctrl-o:execute(vim {})' --bind 'ctrl-y:execute-silent(echo {} | pbcopy)+abort'
This opens the selected file in vim with Ctrl+O or copies the selection to clipboard with Ctrl+Y.
Performance Tips for Large Datasets
When working with large datasets, keep these performance considerations in mind:
1. Use Efficient Input Commands
Instead of find, use fd or ripgrep which are optimized for speed:
1# Faster than find
2fd --type f | fzf
2. Limit Input Size When Needed
For extremely large directories, consider limiting your search:
1fd --type f --max-depth 3 | fzf
3. Leverage fzf’s Built-in Filtering
Use fzf’s query syntax to pre-filter results:
1fzf --query "pdf " --preview 'file {}'
4. Use Async Preview for Large Files
For large files, use async preview to avoid blocking:
1fzf --preview 'head -100 {}' --preview-window '+{2}+3/3'
My Daily fzf Toolkit
After years of refining my workflow, here are the fzf functions I use every day:
1# Quick access to dotfiles
2dots() {
3 local file=$(find ~/.dotfiles -type f | grep -v ".git/" | fzf --preview 'bat --color=always {}')
4 if [ -n "$file" ]; then
5 ${EDITOR:-vim} "$file"
6 fi
7}
8
9# Open recent git files
10frecent() {
11 local file
12 file=$(git log --pretty=format: --name-only --since="30 days ago" | sort | uniq -c | sort -rn | awk '{print $2}' | fzf --preview 'bat --color=always {}')
13 if [ -n "$file" ]; then
14 ${EDITOR:-vim} "$file"
15 fi
16}
17
18# Combined file and content search
19falive() {
20 local file
21 file=$(fd --type f --hidden --exclude .git |
22 fzf --query "$1" --reverse --preview 'bat --color=always {}' --bind "ctrl-g:reload(rg '$1' --files-with-matches)")
23 if [ -n "$file" ]; then
24 ${EDITOR:-vim} "$file"
25 fi
26}
These functions have transformed how I navigate and operate within my projects.
Taking It Further: fzf.vim and Beyond
If you’re a Vim user, fzf.vim integrates fzf into Vim, providing powerful commands like:
:Files- Search for files:Rg- Search for content:BLines- Search current buffer lines:History- Search command history:Commits- Browse git commits
This integration transforms Vim into a powerhouse for navigating and editing code.
Conclusion: Building Your Command-Line Superpowers
fzf is one of those rare tools that fundamentally changes how you interact with your computer. By combining it with the search commands we explored in the previous article, you can create a workflow that feels like having superpowers.
The key is to start small, incorporating fzf into the parts of your workflow where you feel friction, then gradually building up a collection of functions that make your daily tasks effortless.
Remember, the goal isn’t just to save keystrokes—it’s to reduce cognitive load. When finding that specific file or command becomes a fluid, automatic action rather than a mental task, you free up mental resources for the creative and analytical work that really matters.
What fzf integrations have you found most useful in your workflow? Share your favorites in the comments!
If you haven’t read my previous article on Unix search commands, check it out here for a solid foundation on the tools that pair perfectly with fzf.
