Learning Platform
Глоссарий Troubleshooting
Урок 20.02 · 20 мин
Средний
fzfFuzzy searchProductivityShell

fzf: fuzzy finder для shell

Терминал по умолчанию — это ввод команд по памяти и Tab-completion. Когда нужен файл в большом дереве, или один из 200 коммитов, или процесс из 50 в ps aux — Tab не помогает, ты помнишь только кусок названия. Стандартный workflow: набираешь find / -name 'partial*' 2>/dev/null или ps aux | grep partial — медленно, требует точного pattern.

fzf — fuzzy finder. Получает на stdin список строк, показывает интерактивный UI с fuzzy search, возвращает выбранную строку в stdout. На любую задачу «найди в списке» fzf даёт O(1) UX вместо O(N) ручного парсинга.

Установив fzf, ты получаешь: Ctrl-R с fuzzy search через всю historу, cd **<TAB> для fuzzy-перехода в директорию, fuzzy git branch checkout, fuzzy kill процесса, fuzzy file open в editor. Junior DE экономит десятки часов в год.


Установка

# Debian/Ubuntu:
sudo apt install fzf

# macOS:
brew install fzf
$(brew --prefix)/opt/fzf/install   # установить shell integrations

# Из исходников (рекомендуется для последней версии):
git clone --depth 1 https://github.com/junegunn/fzf.git ~/.fzf
~/.fzf/install

При установке через install скрипт автоматически добавит в ~/.bashrc / ~/.zshrc source-команды для интеграций:

# ~/.bashrc или ~/.zshrc:
[ -f ~/.fzf.bash ] && source ~/.fzf.bash

Перезайди в терминал или сделай source ~/.bashrc.


Базовое использование

fzf — это pipe | fzf. Получает список строк, показывает интерфейс, возвращает выбранную.

# Найти файл в текущем дереве:
find . -type f | fzf
# (UI открывается, набираешь куски имени, Enter выбирает, путь печатается в stdout)

# Открыть выбранный файл в редакторе:
nano "$(find . -type f | fzf)"

# cd в выбранную директорию:
cd "$(find . -type d | fzf)"
fzf flow
stdin linesЛюбая команда, выдающая список строк: find, ls, history, kubectl get pods. Каждая строка — кандидат для выбора
|
fzf UIИнтерактивный fullscreen UI с fuzzy search. Набираешь куски — фильтр обновляется в realtime. Enter выбирает
stdoutВыбранная строка попадает в stdout. Можно использовать в $(...) для подстановки в другую команду

Fuzzy matching syntax

fzf поддерживает паттерны:

  • foo bar — содержит и foo, и bar (fuzzy)
  • ^foo — начинается с foo
  • foo$ — заканчивается foo
  • 'foo — exact match foo (single quote prefix)
  • !foo — не содержит foo
  • ^foo$ — точное равенство foo

Это покрывает 99% потребностей поиска.


Key bindings из коробки

После установки с интеграциями в bash/zsh — три hotkeys:

Самая жизнеменяющая фича. По умолчанию Ctrl-R в bash — это поиск по истории точной подстроки. С fzf — fuzzy + полноэкранный preview:

> docker run                                <- ты набрал
docker run -it --rm ubuntu:24.04 bash       <- match 1
docker run -d -p 80:80 nginx                <- match 2
docker run --network=host alpine            <- match 3
...

Стрелки выбирают, Enter подставляет в командную строку. Если случайно нажал — Escape отменяет.

TIP

Это самая важная фича fzf. Установи только ради Ctrl-R, остальное — приятный бонус. После недели использования вернуться к стандартному поиску по истории невозможно.

Ctrl-T: fuzzy find file

Подставляет выбранный файл в текущую командную строку. Полезно когда не помнишь точный путь:

$ cat <Ctrl-T>
# fzf открывается, ищет файл, выбираешь
$ cat ./src/config/database.yml

Alt-C: fuzzy cd to subdirectory

Жмёшь Alt-C — fzf показывает все subdirectories под текущей, выбираешь, cd выполняется.

Tab-completion: ** trigger

$ vim **<TAB>
# fzf открывается с файлами

$ cd **<TAB>
# fzf открывается с директориями

$ kill **<TAB>
# fzf открывается с процессами

$ ssh **<TAB>
# fzf открывается с known hosts

**<TAB> — universal trigger. fzf понимает контекст команды и выбирает подходящий source.


DE use case 1: git branch switching

# Функция в ~/.bashrc:
gcb() {
    local branch
    branch=$(git branch --all | grep -v HEAD | sed 's/^[* ]*//' | fzf) || return
    git checkout "${branch#remotes/origin/}"
}

Теперь gcb показывает fuzzy-список веток, ты выбираешь — git checkout. Особенно полезно в репозиториях с 50+ ветками.

Аналог через git extras или forgit — там уже встроено.


DE use case 2: kill процесса fuzzy

fkill() {
    local pid
    pid=$(ps -ef | sed 1d | fzf -m | awk '{print $2}')
    [ -n "$pid" ] && echo "$pid" | xargs kill -"${1:-TERM}"
}

fkill — fuzzy-выбор процесса, kill с TERM (или другим сигналом).

-m (multi-select) — Tab помечает несколько строк, можно убить пачкой.


В Airflow часто 100+ DAGs. ls /opt/airflow/dags + grep — медленно.

# Функция:
adag() {
    local dag
    dag=$(find /opt/airflow/dags -name '*.py' | fzf --preview 'head -100 {}')
    [ -n "$dag" ] && nano "$dag"
}

--preview — окно справа показывает первые 100 строк выбранного файла. Можно листать стрелками без открытия.

—preview: powerful UI

# С bat (syntax highlighting):
find . -name '*.py' | fzf --preview 'bat --color=always {}'

# С jq для JSON:
ls *.json | fzf --preview 'jq . {}'

# Git log preview:
git log --oneline | fzf --preview 'git show {1}'

{} подставляется выбранной строкой. {1}, {2} — отдельные слова из неё (split по whitespace).


DE use case 4: kubernetes context/pod switching

kubectl — полное управление кластером из командной строки
# Switch kube context:
kctx() {
    local ctx
    ctx=$(kubectl config get-contexts -o name | fzf) && kubectl config use-context "$ctx"
}

# Open shell in pod:
ksh() {
    local pod
    pod=$(kubectl get pods -o name | fzf) && kubectl exec -it "$pod" -- bash
}

# Tail logs:
klog() {
    local pod
    pod=$(kubectl get pods -o name | fzf) && kubectl logs -f "$pod"
}

Это стандартный DE-toolbelt. Каждый день экономит минуты на навигации.


# Открыть лог-файл за вчера:
fls() {
    find /var/log -name '*.log' -mtime -2 2>/dev/null \
        | fzf --preview 'tail -50 {}' \
        | xargs -I{} less {}
}

fls — fuzzy log search, preview tail, less для просмотра.

Или с ripgrep (см. урок 03) для поиска по содержимому:

# Найти лог-файлы с ERROR:
rg -l ERROR /var/log | fzf --preview 'rg --color=always ERROR {}' | xargs less

Customization через FZF_DEFAULT_OPTS

# ~/.bashrc:
export FZF_DEFAULT_OPTS="
  --height=50%
  --layout=reverse
  --border
  --info=inline
  --preview-window=right:60%:wrap
  --bind='ctrl-d:preview-half-page-down,ctrl-u:preview-half-page-up'
"
  • --height=50% — окно занимает половину экрана, не fullscreen.
  • --layout=reverse — prompt сверху, results снизу (привычнее для tail).
  • --border — рамка вокруг fzf.
  • --preview-window=right:60% — preview справа, 60% ширины.

Также можно настроить command для дефолтного поиска файлов:

# Использовать fd вместо find (быстрее, см. урок 03):
export FZF_DEFAULT_COMMAND='fd --type f --hidden --exclude .git'
export FZF_CTRL_T_COMMAND="$FZF_DEFAULT_COMMAND"
export FZF_ALT_C_COMMAND='fd --type d --hidden --exclude .git'

С fd дефолтный поиск становится в 5-10 раз быстрее (особенно на больших монорепозиториях).


fzf в скриптах: —select-1, —exit-0

Для использования в production-скриптах:

# Если матчей 0 — exit 0 без UI:
ls *.csv | fzf --exit-0

# Если матч 1 — автоселект без UI:
ls *.csv | fzf --select-1

# Часто вместе: подходит для CI/non-interactive:
ls *.csv | fzf --select-1 --exit-0 --filter "alice"

--filter — non-interactive фильтрация (используется как лучший grep с fuzzy логикой).


fzf + multi-select для batch operations

# Удалить несколько файлов (Tab помечает):
find /tmp -name '*.tmp' | fzf -m | xargs rm

# Или add to git multiple files:
git status --short | fzf -m | awk '{print $2}' | xargs git add

-m или --multi — multi-select. Tab помечает, Shift-Tab снимает, Enter завершает выбор.


Альтернативы и комплементы

fzf и его друзья
fzfГлавный fuzzy finder. Universal — работает с любым stdin. Junegunn (Korean dev), maintained активно
ripgrepЗамена grep для рекурсивного поиска по содержимому. Часто комбинируется с fzf: rg -l ERROR | fzf
fdЗамена find. Быстрее, понимает .gitignore. fzf по умолчанию использует find, можно переключить на fd через FZF_DEFAULT_COMMAND
batcat с syntax highlighting + line numbers. Идеален для --preview в fzf
zoxideSmart cd (z proj переходит в часто посещаемую директорию). Урок 05

Подводные камни

1. Multi-line input

fzf обрабатывает построчно. Если строка содержит newline (CSV с многострочными значениями) — fzf увидит несколько кандидатов. Решение: pre-process input.

2. Slow on huge inputs

Default find может быть медленным на больших file trees (миллионы файлов). fd или ripgrep с правильными flags решают. Также --no-sort ускоряет, если не нужна сортировка.

3. Кавычки и spaces в filenames

fzf сам корректно возвращает строку с пробелами. Но обязательно quote её в bash:

# OK:
file=$(find . | fzf)
cat "$file"

# СЛОМАЕТСЯ:
cat $(find . | fzf)   # word splitting на пробелы

4. Скрипт vs interactive

Helpers (gcb, fkill) должны жить в ~/.bashrc/~/.zshrc или подключаемых файлах. В production-скриптах fzf редко нужен (там автоматизация без UI). Не путай interactive productivity и production.


  • Урок 01 (jq) — fzf + jq: cat data.json | jq -c '.[]' | fzf --preview 'echo {} | jq .'.
  • Урок 03 (ripgrep/fd) — основные источники для fzf.
  • Урок 04 (tmux) — fzf для switching windows/panes.
  • Модуль 02 (terminal config) — где конфигурировать fzf в shell.

Попробуй сам

  1. Установи fzf и shell integrations. Перезайди в терминал.

  2. Жми Ctrl-R, начинай печатать кусок недавней команды. Сравни с дефолтным Ctrl-R.

  3. Попробуй vim **<TAB> — fuzzy file picker.

  4. Создай функцию в ~/.bashrc:

fcd() {
    local dir
    dir=$(find . -type d 2>/dev/null | fzf --preview 'ls -la {}') && cd "$dir"
}

После source — fcd для fuzzy navigation.

  1. Git branch checkout:
gcb() {
    local branch
    branch=$(git branch --all | grep -v HEAD | sed 's/^[* ]*//' | fzf) || return
    git checkout "${branch#remotes/origin/}"
}
  1. fzf + ripgrep — найти файлы с ERROR:
# Установи ripgrep сначала: apt install ripgrep / brew install ripgrep
rg -l ERROR /var/log/ 2>/dev/null | fzf --preview 'rg ERROR {}'

Проверка знанийKnowledge check
Junior работает с monorepo в 50 микросервисами и 200 веток в Git. Ему часто нужно: 1) найти файл с partial-знанием имени, 2) переключиться между ветками, 3) убить orphan-процесс из ps. Как fzf решает все три задачи и почему он сильно эффективнее, чем `find | grep`, `git branch | grep`, `ps | grep`?
ОтветAnswer
Все три задачи решаются одним инструментом + bash-функциями. 1) **Поиск файла**: Ctrl-T или vim **<TAB> — fzf запускается с fuzzy-фильтром. Набираешь куски (порядок не важен!), стрелки/Enter — путь подставляется в команду. С find | grep 'partial' нужно помнить точный pattern и есть только один проход. 2) **Git branches**: функция gcb() { git branch --all | sed 's/^[* ]*//' | fzf | xargs git checkout; }. Видишь все ветки одновременно, fuzzy фильтр, preview через --preview 'git log {}'. С git branch | grep теряешь контекст и не видишь last commits. 3) **Kill процесса**: fkill() { ps -ef | fzf -m | awk '{print \$2}' | xargs kill; }. -m позволяет выбрать несколько процессов Tab'ом, убить одной командой. Преимущества над grep-pipeline: a) **Interactive realtime** — фильтр обновляется на каждый keypress, b) **Fuzzy matching** — печатаешь только то что помнишь, в любом порядке, c) **Preview window** — --preview 'cmd {}' показывает контекст без открытия (head, git show, jq, bat), d) **Multi-select** — операции над пачкой, e) **Universal** — работает с любым stdin, не только файлами. Установка brew install fzf && \$(brew --prefix)/opt/fzf/install + Ctrl-R заменяет дефолтный history search на полноэкранный fuzzy — главная фича, которую junior сразу полюбит. Один час настройки экономит десятки часов в год.

Проверьте понимание

Результат: 0 из 0
Концептуальный
Вопрос 1 из 5. Что делает fzf с потоком строк, которые приходят на stdin?

Закончили урок?

Отметьте его как пройденный, чтобы отслеживать свой прогресс

Войдите чтобы оценить урок

Прогресс модуля
0 из 5