🛠️Herramientas

ripgrep, fd y fzf: tres herramientas que pueden transformar tu forma de trabajar en la terminal

Felipe••15 min de lectura
Busca texto con ripgrep, encuentra archivos con fd y selecciona resultados con fzf. Incluye un laboratorio TypeScript e integración con VS Code y Neovim.

Recuerdas que una función calcula el total de una compra, pero no en qué paquete vive. Buscas su nombre y aparecen la implementación, los tests, una copia compilada y varias dependencias. Después encuentras el archivo correcto y todavía tienes que copiar su ruta para abrirlo.

En ese recorrido hay tres tareas diferentes: buscar dentro del código, encontrar archivos y elegir un resultado. ripgrep, fd y fzf permiten resolverlas por separado y combinarlas mediante las tuberías de la terminal.

No necesitas reemplazar todos tus comandos ni abandonar el buscador del editor. Vamos a construir un repositorio pequeño en TypeScript para comprobar dónde ayudan estas herramientas y dónde siguen siendo necesarios grep, find o las funciones del IDE.

Qué aporta cada herramienta

HerramientaPregunta que resuelveEjemplo
ripgrep, ejecutable rg¿Qué archivos contienen este texto?rg -n 'calculateTotal' src
fd¿Qué archivos o directorios tienen este nombre?fd -t f -e ts calculateTotal
fzf¿Cuál de estos resultados quiero seleccionar?fd -t f -e ts | fzf

Una tubería, |, conecta la salida de un programa con la entrada del siguiente. En el último ejemplo, fd produce las rutas y fzf permite elegir una. No se está buscando el contenido de los archivos.

ripgrep frente a grep

ripgrep busca patrones de texto. Al recibir un directorio, lo recorre recursivamente; si no indicas una ruta y no recibe datos por una tubería, busca desde el directorio actual.

Estas dos órdenes sirven para investigar una función dentro de src:

grep -R -n 'calculateTotal' src
rg -n 'calculateTotal' src

-n pide los números de línea. La diferencia práctica no está solo en escribir menos: durante el recorrido, rg aplica reglas de exclusión pensadas para repositorios. Por defecto omite archivos ignorados, ocultos y binarios, y no sigue enlaces simbólicos. grep -R no aplica automáticamente .gitignore; además, las opciones y el comportamiento recursivo pueden variar entre implementaciones de grep.

grep sigue siendo útil en scripts portables y al procesar flujos de texto. ripgrep no es un reemplazo idéntico: sus opciones, expresiones regulares y archivos examinados pueden producir resultados distintos.

fd frente a find

fd busca entradas del sistema de archivos. Su patrón se interpreta como expresión regular y, normalmente, se compara con el nombre de la entrada. Puedes restringir por extensión y tipo:

fd -t f -e ts calculateTotal
fd -t d billing

La primera orden busca archivos TypeScript; la segunda, directorios. Para comparar contra la ruta completa existe --full-path. Para patrones de shell como *.test.ts, usa --glob y conserva las comillas.

Con find, una búsqueda por nombre se expresa así:

find src tests -type f -name '*Total*.ts'

No son órdenes equivalentes para cualquier árbol: fd omite normalmente entradas ocultas e ignoradas. También utiliza smart case: un patrón sin mayúsculas busca sin distinguirlas; al introducir una mayúscula, pasa a distinguirlas.

find sigue siendo una herramienta más amplia para combinar predicados sobre archivos y ejecutar acciones. No conviene sustituirlo mecánicamente por fd en scripts existentes. Los detalles de filtros y opciones están en el manual oficial de fd.

fzf: selección interactiva mediante búsqueda difusa

fzf recibe candidatos y reduce la lista mientras escribes. La búsqueda difusa permite omitir caracteres intermedios: calctot puede encontrar calculateTotal.ts porque conserva el orden de los caracteres buscados. No es un corrector ortográfico ni interpreta el significado del código.

Con las flechas cambias la selección, con Enter la confirmas y con Esc cancelas. La selección se escribe en la salida estándar, por lo que otro programa puede recibirla. También puede recorrer archivos cuando se ejecuta solo; aquí le pasaremos candidatos explícitos para controlar qué se busca.

Instalación en macOS y Linux

En macOS, con Homebrew instalado:

brew install ripgrep fd fzf

En Debian o Ubuntu, con los paquetes disponibles en los repositorios de tu distribución:

sudo apt update
sudo apt install ripgrep fd-find fzf

En Debian y Ubuntu el ejecutable de fd-find suele llamarse fdfind. Para los ejemplos puedes sustituir fd por fdfind. Si quieres usar fd también desde programas que invocan un shell nuevo, el proyecto recomienda un enlace en un directorio incluido en PATH:

mkdir -p "$HOME/.local/bin"
ln -s "$(command -v fdfind)" "$HOME/.local/bin/fd"
export PATH="$HOME/.local/bin:$PATH"

Hazlo únicamente si fdfind existe y esa ruta de destino está libre. Un alias interactivo alias fd=fdfind es más simple, pero no necesariamente estará disponible en los comandos que ejecute fzf internamente.

Estas vías aparecen en las instrucciones oficiales de ripgrep, fd y fzf. Las versiones de los paquetes dependen de la distribución. Comprueba las tuyas antes de copiar una configuración:

rg --version
fd --version
fzf --version

El laboratorio de este artículo se ejecutó en macOS ARM64 con ripgrep 15.2.0, fd 10.5.1 y fzf 0.74.5. La instalación de Linux se contrastó con las instrucciones oficiales; no se ejecutó en una distribución Linux. No hicimos mediciones comparativas de rendimiento.

Laboratorio: un repositorio TypeScript pequeño y reproducible

Necesitas Git y las tres herramientas. Los archivos TypeScript son el material de búsqueda: no necesitas instalar Node.js, paquetes npm ni un framework para completar los ejercicios.

Ejecuta este bloque en Bash o Zsh, desde una carpeta de trabajo donde no exista terminal-search-lab. git init hace que el ejemplo sea un repositorio real a efectos de las reglas de .gitignore; no crea commits ni conecta con GitHub.

mkdir terminal-search-lab
cd terminal-search-lab
git init -q
mkdir -p src/{billing,config} tests docs dist node_modules/demo

cat > .gitignore <<'EOF'
node_modules/
dist/
.env
EOF

cat > src/billing/calculateTotal.ts <<'EOF'
export function calculateTotal(prices: number[]): number {
  return prices.reduce((sum, price) => sum + price, 0);
}
EOF

cat > src/billing/checkout.ts <<'EOF'
import { calculateTotal } from './calculateTotal';
export const total = calculateTotal([12, 8]);
export const currency = process.env.CURRENCY ?? 'CLP';
EOF

cat > src/config/env.ts <<'EOF'
export const apiUrl = process.env.API_URL ?? 'http://localhost:3000';
export const logLevel = process.env.LOG_LEVEL ?? 'info';
export const timeout = process.env['REQUEST_TIMEOUT'] ?? '5000';
EOF

cat > tests/calculateTotal.test.ts <<'EOF'
import { calculateTotal } from '../src/billing/calculateTotal';
if (calculateTotal([12, 8]) !== 20) throw new Error('Total incorrecto');
EOF

cat > 'docs/billing notes.md' <<'EOF'
calculateTotal recibe precios; no calcula impuestos ni descuentos.
EOF

cat > .env.example <<'EOF'
API_URL=http://localhost:3000
CURRENCY=CLP
LOG_LEVEL=info
REQUEST_TIMEOUT=5000
EOF

printf 'FAKE_TOKEN=example-only\n' > .env
printf 'calculateTotal: copia generada\n' > dist/bundle.js
printf 'calculateTotal: dependencia ficticia\n' > node_modules/demo/index.js

Todos los valores son ficticios. Incluimos a propósito una copia generada, una dependencia simulada, un archivo oculto y una ruta con espacios.

1. Encontrar archivos por nombre

Desde terminal-search-lab:

fd -t f -e ts calculateTotal

Se encontraron estas dos rutas; el orden de salida puede variar:

src/billing/calculateTotal.ts
tests/calculateTotal.test.ts

Para encontrar el directorio del módulo:

fd -t d billing

Devuelve src/billing/. Si solo recuerdas que el archivo es TypeScript y vive dentro de src, puedes listar candidatos así:

fd -t f -e ts . src

Aquí . es el patrón y src es el directorio de búsqueda. Es una distinción útil: en fd docs, docs sería un patrón de nombre, no una instrucción para recorrer únicamente esa carpeta.

2. Buscar referencias a una función

rg -n -F 'calculateTotal' src tests

-F interpreta el patrón literalmente. En este laboratorio aparecen cinco líneas entre tres archivos: dos importaciones, dos llamadas y la declaración de la función.

Para buscar el nombre seguido de un paréntesis, con espacios opcionales:

rg -n 'calculateTotal\s*\(' src tests

Esta expresión regular reduce el resultado a tres líneas: las dos llamadas y la declaración. No distingue entre ellas porque examina texto. Tampoco sigue un alias como import { calculateTotal as sum } para reconocer después sum(...).

Para renombrar un símbolo o conocer sus referencias semánticas, utiliza el servicio de lenguaje de TypeScript en tu editor. rg resulta muy útil para investigar, incluyendo comentarios y documentación, pero no reemplaza ese análisis.

Las expresiones se escriben entre comillas para que el shell no las expanda. El motor predeterminado de ripgrep no admite todas las construcciones de otros motores, como look-around y referencias hacia atrás. -P permite usar PCRE2 si la compilación instalada lo incluye. Puedes consultar esa diferencia en las preguntas frecuentes de ripgrep.

3. Localizar variables de entorno utilizadas

Primero, los accesos mediante notación de punto:

rg -n -g '*.ts' 'process\.env\.[A-Z_][A-Z0-9_]*' src

Para obtener solo las referencias y eliminar duplicados:

rg --no-heading -o --no-filename \
  'process\.env\.[A-Z_][A-Z0-9_]*' src | sort -u

Salida obtenida:

process.env.API_URL
process.env.CURRENCY
process.env.LOG_LEVEL

Falta deliberadamente REQUEST_TIMEOUT: está escrito con corchetes. Una segunda búsqueda permite revisar ese estilo:

rg -n -F 'process.env[' src

Obtendrás la línea 3 de src/config/env.ts. La búsqueda no enumera los posibles valores de accesos dinámicos como process.env[name], ni reconoce automáticamente destructuración, wrappers o import.meta.env. Puede encontrar texto dentro de comentarios y cadenas. Es un inventario inicial para revisar, no una auditoría completa del entorno.

4. Filtrar resultados interactivamente

Para elegir entre archivos TypeScript:

fd -t f -e ts | fzf

Escribe calctot, revisa los candidatos y pulsa Enter. Para elegir entre líneas que mencionan la función:

rg --line-number --no-heading --color=never \
  -F 'calculateTotal' src tests | fzf

Ahora escribe checkout. Verás las coincidencias de ese archivo. rg ya terminó la búsqueda de contenido; escribir dentro de fzf filtra esa lista, no vuelve a ejecutar ripgrep con cada tecla.

También puedes comprobar la coincidencia difusa sin abrir la interfaz:

fd -t f -e ts | fzf --filter='calctot'

En la prueba devolvió los dos archivos cuyo nombre contiene calculateTotal. --filter imprime las coincidencias y termina; sirve para automatizar comprobaciones, pero no constituye una selección interactiva. El manual de fzf detalla ambas modalidades.

5. Abrir el archivo encontrado en VS Code o Neovim

Cuando solo necesitas la ruta, pide a ripgrep nombres de archivos mediante -l:

if file=$(rg -l -F 'calculateTotal' src tests | fzf); then
  code -- "$file"
fi

Las comillas conservan los espacios. El if evita abrir el editor cuando cancelas o no hay selección. Con Neovim, cambia la última orden por:

nvim -- "$file"

Ejecuta esa variante dentro del mismo if, en lugar de code, y con Neovim instalado. Para una ubicación conocida del laboratorio:

code --goto 'src/billing/checkout.ts:2'
# Alternativa:
nvim +2 -- src/billing/checkout.ts

VS Code documenta --goto y la instalación del comando code; Neovim documenta +[num] y --. En macOS quizá debas habilitar code desde la paleta de comandos del editor.

La variante sencilla trata cada línea como una ruta. Para nombres que puedan contener saltos de línea necesitas separadores NUL de extremo a extremo. Esta función es para Bash y evita guardar un byte NUL en una variable:

fe() {
  local file
  if IFS= read -r -d '' file < <(
    fd -t f -0 . | fzf --read0 --print0
  ); then
    code -- "$file"
  fi
}

El read consume hasta NUL; el editor recibe una sola ruta entre comillas. -- separa opciones de nombres. No uses eval para abrir una selección ni code $(...) sin comillas: los nombres no deben convertirse en instrucciones o fragmentarse en varios argumentos.

Qué ocurre con .gitignore y los archivos ocultos

Prueba una búsqueda desde la raíz del laboratorio:

rg -n -F 'calculateTotal' .

Se obtienen seis líneas, incluida la nota de documentación. Las copias de dist/ y node_modules/ no aparecen. Para revisar deliberadamente esas carpetas:

rg -n --no-ignore -F 'calculateTotal' dist node_modules

Ahora aparecen las dos copias. Separar el origen del código generado evita confundir archivos que necesitas editar con resultados que se regenerarán.

Para comprobar el archivo de ejemplo oculto sin desactivar .gitignore:

rg -n --hidden '^API_URL=' .
fd --hidden '^\.env'

La primera orden encuentra .env.example; la segunda también lo lista. El .env sigue excluido por .gitignore. En cambio:

fd --hidden --no-ignore '^\.env'

lista tanto .env como .env.example.

.gitignore no es una barrera de seguridad. Un archivo explícito o determinados filtros pueden cambiar el conjunto examinado. En ripgrep, un patrón de inclusión -g puede imponerse a las reglas de ignorados: rg --hidden -g '.env*' ... no significa «solo archivos de entorno seguros para mostrar». Evita copiar resultados de archivos reales con secretos a tickets o chats.

La guía de filtrado de ripgrep también describe .ignore, .rgignore y --no-require-git. No asumas que .gitignore se aplicará igual fuera de un repositorio Git.

Atajos y aliases que no cambian toda tu terminal

Puedes empezar con dos aliases en ~/.bashrc o ~/.zshrc:

alias ff='fd --type f'
alias rgn='rg --line-number'

Así conservas el comportamiento original de grep, find y rg. Para que fzf use fd al ejecutarlo sin una tubería, y también en su selector de archivos:

export FZF_DEFAULT_COMMAND='fd --type f'
export FZF_CTRL_T_COMMAND='fd --type f'

Las variables no son intercambiables: la integración del shell tiene sus propias fuentes de candidatos. Si usas el nombre fdfind, ajusta ambas cadenas o configura antes el enlace descrito en la instalación.

En versiones recientes de fzf, la integración oficial se activa con una de estas opciones, según tu shell:

# Bash: en ~/.bashrc
eval "$(fzf --bash)"
# Zsh: en ~/.zshrc
source <(fzf --zsh)

Ese eval carga código generado por el ejecutable instalado de fzf; no ejecuta un nombre de archivo elegido. Configura las variables antes de cargar la integración y abre una terminal nueva.

Los atajos principales son Ctrl+T para insertar rutas seleccionadas, Ctrl+R para buscar en el historial y Alt+C para elegir un directorio y entrar en él. Si tu versión no reconoce --bash o --zsh, consulta los scripts incluidos por tu paquete; sus rutas varían. La combinación Alt también puede depender de la configuración del emulador de terminal.

No hace falta adoptar todos los atajos a la vez. Si ya utilizas una interfaz como LazyGit, fzf añade un selector reutilizable para otras listas, mientras LazyGit mantiene su flujo específico para Git.

Cómo trasladarlo a un repositorio grande

Antes de añadir opciones, reduce el alcance. En un monorepo, empezar por packages/billing/src puede ser más útil que revisar cada coincidencia de todo el árbol. Estas son plantillas para adaptar a las carpetas reales de tu proyecto, no resultados de un benchmark:

# Investigar una función sin incluir tests en esta primera pasada
rg -n -g '*.ts' -g '!*.test.ts' -F 'calculateTotal' src tests

# Elegir un archivo del módulo que conoces
fd -t f -e ts . src/billing | fzf

# Listar candidatos TypeScript sin instalar fd
rg --files -g '*.ts' | fzf

Si necesitas comprobar el impacto de un cambio, vuelve después a los tests: excluirlos ayuda a enfocar una búsqueda, pero también oculta evidencia relevante.

Para una migración de configuración, localiza primero los usos de process.env, revisa los accesos dinámicos y contrástalos con la documentación del proyecto. Para una función compartida, busca tanto su nombre como los puntos de exportación e importación. Para localizar un archivo cuyo nombre recuerdas a medias, comienza con fd | fzf y deja la búsqueda de contenido para cuando tengas un ámbito más pequeño.

Evita activar globalmente --hidden --no-ignore. En repositorios con dependencias, compilados y cachés puede multiplicar el ruido. Tampoco conviene ejecutar vistas previas costosas sobre cada candidato sin una necesidad concreta.

Límites que conviene conocer antes de automatizar

Las tres herramientas reducen pasos manuales, pero no garantizan una búsqueda completa ni más rápida en todos los escenarios:

  • Miden cosas distintas. Encontrar nombres, leer contenido y ordenar candidatos no son operaciones comparables. El tamaño de los archivos, el disco, las exclusiones y el patrón influyen en el trabajo realizado.
  • Los valores predeterminados importan. ripgrep distingue mayúsculas por defecto; fd utiliza smart case. Para ignorar mayúsculas explícitamente en una búsqueda de contenido puedes usar rg -i.
  • Sin coincidencias no significa error del repositorio. ripgrep devuelve 1 cuando no encuentra resultados; fzf devuelve 1 sin coincidencias y 130 al cancelar. Contempla esos estados al usar scripts con set -e.
  • El texto no sustituye al lenguaje. Una coincidencia puede estar en un comentario; una referencia real puede utilizar un alias que tu patrón no cubre.
  • La portabilidad sigue teniendo valor. En un servidor mínimo puede que solo estén disponibles grep y find. No introduzcas dependencias nuevas en un script compartido sin documentarlas.
  • La interfaz necesita una terminal. En CI suele ser más adecuado un filtro determinista y comprobaciones explícitas; pedir una selección manual puede dejar un job esperando.

El laboratorio comprobó las búsquedas, las exclusiones, el filtrado automático y una selección interactiva real. También se invocó VS Code mediante su CLI; la alternativa de Neovim se verificó contra su documentación, sin ejecutarla en ese entorno. No se midieron tiempos ni se extrapoló el comportamiento del ejemplo a un monorepo de producción.

Para empezar en tu siguiente tarea, prueba únicamente rg -n -F 'nombreDeFuncion' src. Cuando tengas una lista difícil de recorrer, añade fzf; cuando recuerdes el nombre del archivo y no su contenido, utiliza fd. Esa separación permite componer un flujo pequeño que entiendes y puedes ajustar a cada repositorio.

#ripgrep#rg#fd#fzf#terminal#búsqueda de archivos#TypeScript#VS Code#Neovim

Artículos relacionados