Busca como un rayo y alimenta a tu IA

Vistas: 71
Busca como un rayo y alimenta a tu IA

Llevo años usando find y grep. Toda la vida. Y nunca me había parado a pensar lo lentos que son hasta que un día, haciendo una búsqueda en un directorio con unos cuantos miles de archivos, me fui a por un café. Literalmente. Cuando volví, seguía ejecutándose. Ahí me di cuenta de que algo no iba bien. Que estaba usando herramientas de los 90 cuando tenemos alternativas modernas, escritas en Rust, que vuelan. Ya hablamos de fd y ripgrep en el episodio 731, y de cómo combinarlos con fzf y bat en el 810, pero hoy nos centramos en algo que no vimos allí: los pipelines con IA. Porque la gracia no es solo buscar rápido, sino conectar esa búsqueda con un modelo local para que puedas preguntarle sobre tus propias notas, tu código, tus documentos. Todo desde la terminal, todo en milisegundos.

El café que nunca llegó

Te pongo en situación. Tenía un directorio con cientos de archivos Markdown, apuntes sobre Ollama, scripts de Python, notas de Docker, configuraciones de servicios… Todo el batiburrillo que acumulamos los que llevamos años en esto del selfhosting y el desarrollo. Necesitaba encontrar una nota concreta sobre cómo configurar los contextos de los modelos. Hice lo de siempre: find . -name "*.md" | xargs grep -l "contexto". Y me fui a por un café. Cuando volví, el terminal seguía parpadeando. Literalmente, había estado más tiempo esperando que bebiendo el café.

Y no es que find y grep sean malas herramientas. Son herramientas de los 70, diseñadas para sistemas de los 70. Funcionan, pero no están optimizadas para el mundo de hoy: repositorios git con decenas de miles de archivos, directorios .git que ralentizan las búsquedas, archivos binarios, comprimidos, codificaciones raras… El problema es que con el paso del tiempo van apareciendo necesidades que estas herramientas no cubren bien. Por ejemplo, la búsqueda en repositorios git. Un find normal va a buscar también dentro del directorio .git, y eso puede ser un infierno de lentitud. O el tema de las codificaciones. O los archivos binarios.

Así que después de aquel café frustrante, decidí que tenía que haber una forma mejor. Y la hay. De hecho, hay dos: fd y ripgrep.

fd: el find que siempre quisiste tener

fd, escrito por David Peter —el mismo de bat—, es un sustituto de find escrito en Rust. 44 mil estrellas en GitHub, más de 2 mil commits, y una velocidad que pone los pelos de punta.

La instalación es sencilla, aunque en Debian y Ubuntu hay una particularidad que te tienes que saber.

# Debian/Ubuntu — el paquete es fd-find, el binario es fdfind
sudo apt install fd-find
ln -s $(which fdfind) ~/.local/bin/fd

# Arch
sudo pacman -S fd

# Fedora
sudo dnf install fd-find

Fíjate, en Debian el paquete se llama fd-find y el binario se instala como fdfind. Si escribes fd y te dice comando no encontrado, ya sabes por qué. Crea ese enlace simbólico y olvídate. En Arch es sudo pacman -S fd, sin más.

La comparativa que duele

Los benchmarks oficiales de fd son para echarse a llorar. Buscando archivos [0-9].jpg en unos 750 mil subdirectorios y 4 millones de archivos, los resultados son estos:

HerramientaTiempovs fd
find -iregex19.922 s23× más lento
find -iname11.226 s13× más lento
fd -u0.855 s

23 veces más lento. Te puedes ir a por un café mientras find trabaja. Con fd, el café lo tomas después de tener el resultado.

El uso básico

# Archivos .md en el directorio actual
fd -e md

# Directorios que contienen "src"
fd -t d src

# Archivos Python modificados esta semana
fd --changed-within 1week -e py

# Archivos de más de 10 megas
fd -S +10M

Pero lo que realmente nos interesa para los pipelines con IA son los placeholders. Mira esto.

Los placeholders que te cambiarán la vida

fd -e md -x echo "Archivo: {}, sin ext: {.}, nombre: {/}, dir: {//}"

Los placeholders son la clave para montar pipelines potentes. Funcionan así:

  • {} — ruta completa del archivo
  • {.} — ruta sin extensión
  • {/} — solo el nombre del archivo
  • {//} — solo el directorio padre
  • {/.} — nombre del archivo sin extensión ni ruta

Y luego está la diferencia entre -x y -X. La x minúscula ejecuta el comando una vez por cada archivo. La X mayúscula pasa todos los archivos juntos como argumentos. Es la diferencia entre:

# -x: un wc -l por cada archivo
fd -e md -x wc -l {}

# -X: un solo wc -l con todos los archivos
fd -e md -X wc -l

La -X mayúscula es la que nos interesa para los pipelines con rg, como veremos en un momento.

Flags de supervivencia

fd -H              # Incluir archivos ocultos
fd -I              # Ignorar .gitignore
fd -E .git         # Excluir directorio
fd -d 3            # Profundidad máxima
fd -g '*.config'   # Modo glob (por defecto usa regex)

Un detalle que te puede salvar. Por defecto fd interpreta el patrón como regex, no como glob. Si vienes de find y piensas en *.md, vas a tener problemas. Usa -g para modo glob, o mejor aún, usa -e md para filtrar por extensión directamente.

ripgrep: el grep con superpoderes

Pasamos a la segunda pieza. ripgrep —o rg— es el sustituto de grep. Creado por Andrew Gallant (BurntSushi en GitHub), 67 mil estrellas y un motor de regex que pone a temblar a cualquier competidor.

La instalación, igual de sencilla:

# Debian/Ubuntu
sudo apt install ripgrep

# Arch
sudo pacman -S ripgrep

# Fedora
sudo dnf install ripgrep

La comparativa que duele (segunda parte)

Buscando en el árbol de fuentes del kernel de Linux (Intel i9-12900K):

HerramientaTiempovs rg
rg0.082s1.00×
hypergrep0.167s2.04×
git grep -P0.273s3.34×
ag (Silver Searcher)0.443s5.43×
ugrep0.639s7.82×
grep (Unicode)2.670s32.70×
ack2.935s35.94×

32 veces más rápido que grep. Casi 36 veces más rápido que ack. No tiene competencia.

Uso básico

# Búsqueda simple
rg "Ollama"

# Case insensitive
rg -i "linux"

# Palabra completa
rg -w "bash"

# Contar coincidencias
rg -c "linux"

# Solo nombres de archivo
rg -l "Ollama"

Contexto alrededor de la coincidencia

Esto es fundamental para los pipelines con IA. Necesitas contexto alrededor de cada coincidencia para que el modelo entienda de qué se está hablando.

# 3 líneas alrededor
rg -C 3 "llama3.2"

# 2 líneas después
rg -A 2 "docker"

# 3 líneas antes
rg -B 3 "error"

Filtrar por tipo de archivo

# Solo Python
rg -t py "def"

# Excluir JavaScript
rg -T js "patron"

# Excluir por glob
rg -g '!*.min.js' "patron"

Niveles de agresividad

Cuando no encuentras algo y estás seguro de que está ahí, sube la intensidad.

rg -u         # Ignora .gitignore
rg -uu        # + busca archivos ocultos
rg -uuu       # + busca en binarios

A esto lo llamo yo búsqueda nuclear. Cuando nada funciona, rg -uuu encuentra hasta lo que no sabías que tenías.

Flags avanzados

# Passthru: muestra también líneas no coincidentes
rg --passthru -r 'NUEVO'

# Salida JSON para procesar con jq
rg --json "patron" | jq '.data.path.text'

# Ordenar resultados
rg --sort path "patron"

# Buscar en comprimidos
rg -z "patron"             # gzip, bz2, xz

# Multilínea
rg -U "patron1\npatron2"

# Forzar codificación
rg -E utf-16 "patron"

# Depurar qué archivos se ignoran
rg --debug "patron"

# Listar archivos que se buscarían (sin buscar)
rg --files

El --passthru combinado con -r es una maravilla. Ves el archivo entero con las sustituciones aplicadas en vivo, como un sed pero sin modificar nada. Y --json es la puerta de entrada a procesamiento estructurado con jq. Eso es clave para lo que viene.

Y luego está --sort path. Por defecto rg no ordena los resultados —los devuelve según los encuentra, que puede ser en cualquier orden. Con --sort path te aseguras una salida ordenada y predecible. Cuando estás alimentando a un modelo de IA, el orden importa más de lo que crees.

La combinación estrella: fd + ripgrep

Por separado son potentes. Juntos, son imparables. Aquí es donde empieza la magia de verdad.

Imagina que tienes un directorio lleno de notas en Markdown. Cientos, miles de archivos. Quieres encontrar todos los que tengan la etiqueta linux en su frontmatter. ¿Cómo lo harías?

El error más común

# Esto NO hace lo que crees
fd -e md | rg "tags:"

Esto tiene un problema. Le estás pasando las rutas de los archivos por la tubería a rg, y rg las interpreta como archivos donde buscar. Pero la salida de fd son rutas, y rg busca texto dentro de los archivos. No es exactamente lo que queremos. rg buscará en los nombres de los archivos, no en el contenido.

La forma correcta: -X

# La forma correcta: -X pasa todos los archivos como argumentos
fd -e md -X rg "tags:"

La X mayúscula de fd pasa todos los archivos encontrados como argumentos a rg. Es como hacer fd -e md -0 | xargs -0 rg "tags:" pero más limpio, más rápido, y con menos cosas que puedan romperse.

El pipeline maravilla: extraer frontmatter

Vamos a por el frontmatter completo. Y aquí es donde la cosa se pone interesante.

# Extraer las líneas entre los dos --- del frontmatter YAML
fd -e md -X rg "^---$" -A 20 | rg "^---$" -v | rg -v "^\s*$"

Este pipeline es una maravilla. Vamos por partes. Primero, fd encuentra todos los .md. Luego, rg busca las líneas con --- que delimitan el frontmatter y saca 20 líneas de contexto después. Después, un segundo rg quita esas líneas de ---, y un tercero quita las líneas vacías. El resultado: todo el frontmatter de todas tus notas, limpio y listo para procesar.

Pero podemos afinar más. ¿Y si solo queremos ciertos campos?

# Extraer solo campos específicos del frontmatter
fd -e md -X rg "^(tags|title|date):"

Búsquedas compuestas

Aquí es donde fd y rg muestran todo su potencial combinado.

# Buscar "ollama" solo en archivos Python
fd -e py -X rg "ollama"

# Buscar en archivos .md modificados esta semana que hablen de IA
fd --changed-within 7days -e md -X rg "inteligencia"

# Buscar archivos grandes que contengan "error"
fd -S +1M -X rg -l "error"

Fíjate en la potencia de la combinación. fd filtra por extensión, por fecha de modificación, por tamaño… Luego pasa esos archivos a rg, que busca el patrón. Estás haciendo en una línea lo que antes requería scripts de varias líneas con find, grep, y xargs.

Solo nombres de archivo: como un índice invertido

# Buscar y mostrar solo los nombres de archivo que coinciden
fd -e md -X rg -l "tags: linux"

El flag -l de rg hace que solo muestre los nombres de los archivos que contienen el patrón. Es perfecto cuando quieres saber qué archivos tienen algo, pero no necesitas ver el contenido. Es como un índice invertido improvisado.

Resultados ordenados con –sort path

# Resultados ordenados y con contexto
fd -e md -X rg --sort path -C 2 "ollama"

Por defecto rg no ordena. Los resultados salen en el orden en que los encuentra el sistema de archivos, que puede ser aleatorio. Con --sort path te aseguras una salida ordenada alfabéticamente. Cuando luego le pasas eso a un modelo de IA, el orden consistente ayuda a que el contexto sea más predecible.

Salida JSON estructurada

# Salida JSON para procesar con jq
fd -e md -X rg --json "tags:" | \
  jq '[.[] | select(.type == "match") | \
  {file: .data.path.text, line: .data.line_number, content: .data.lines.text}]'

Esto ya es otra liga. Estás obteniendo un JSON estructurado con el archivo, el número de línea y el contenido de cada coincidencia. Puedes filtrar, ordenar, agrupar… Las posibilidades son infinitas. Y cuando tienes JSON, puedes pasárselo a cualquier herramienta: jq, python, node, o directamente a un modelo de IA.

El pipeline definitivo: fd → rg → Ollama

Y esta es la parte que más me gusta. Porque aquí conectamos la búsqueda con la inteligencia artificial. El concepto es sencillo: usa fd + rg para extraer contexto de tus propios archivos, y pásaselo a un modelo local con Ollama.

¿Por qué querrías hacer esto? Porque los modelos de IA son tan buenos como el contexto que les das. Si le preguntas a un modelo general sobre qué es fd, te dará una respuesta genérica. Si le pasas el contexto de tus propias notas, te dará una respuesta personalizada, basada en tu conocimiento, en tus apuntes, en tu código. Y todo local, sin enviar nada a la nube.

Verificar que Ollama funciona

# Primero, verifica que Ollama está funcionando
curl -s http://localhost:11434/api/tags | jq '.models[].name'

Si tienes Ollama instalado y corriendo, esto te devuelve la lista de modelos disponibles. Si no, ya sabes: ollama pull llama3.2 o el modelo que prefieras. Del episodio 790 ya sabes todo sobre Ollama en la terminal, así que no me voy a extender.

El pipeline bestial

Ahora, el pipeline completo. Prepárate, porque esto es bestial.

# Buscar archivos .md modificados en los últimos 30 días,
# extraer contexto sobre "ollama", y preguntar a la IA

fd --changed-within 30days -e md -X rg -C 3 "ollama" | \
  curl -s http://localhost:11434/api/generate \
    -d "{
      \"model\": \"llama3.2\",
      \"prompt\": \"Resume el siguiente contenido sobre Ollama:\n$(cat)\n\",
      \"stream\": false
    }" | jq -r '.response'

Esto es bestial. En una línea estás haciendo cuatro cosas:

  1. Encontrando todos los archivos Markdown de los últimos 30 días
  2. Buscando dentro el término ollama con 3 líneas de contexto
  3. Enviando ese contexto a un modelo local
  4. Mostrando la respuesta

Pero hay que tener cuidado. El contexto no puede ser demasiado grande porque los modelos tienen límite de tokens. Si tienes muchos archivos, puedes limitar la salida.

Con límite de contexto

# Con límite de contexto para no saturar el modelo
fd --changed-within 30days -e md -X rg -C 3 "ollama" | \
  head -n 100 | \
  curl -s http://localhost:11434/api/generate \
    -d "{
      \"model\": \"llama3.2\",
      \"prompt\": \"Responde basándote en este contexto:\n$(cat)\nPregunta: ¿Qué dice sobre Ollama?\",
      \"stream\": false
    }" | jq -r '.response'

Ese head -n 100 limita la entrada a 100 líneas. Así no te pasas del límite de contexto del modelo. Ajusta el número según el modelo que uses. Los modelos pequeños como llama3.2:3b tienen contextos de 8k tokens, mientras que llama3.2:8b llega a 128k tokens. Siempre es mejor ir con cuidado.

Versión experta: frontmatter a JSON

# Convertir frontmatter a JSON y filtrar con jq
fd -e md -X bash -c '
  for f in "$@"; do
    awk "NR==1,/^---$/" "$f" | yq -o json
  done
' _ | jq -s '[.[] | select(.tags)]'

Esto busca todos los .md, extrae el frontmatter con awk, lo convierte a JSON con yq, y luego lo filtra con jq para quedarse solo con los que tienen etiquetas. El resultado es un JSON estructurado con los metadatos de todas tus notas. Puedes preguntarle a la IA: basándote en estos metadatos, ¿qué temas cubro más?

Versión interactiva con trazabilidad

# Saber de qué archivo viene cada fragmento
fd -e md -X rg -l "concepto" | \
  while read -r archivo; do
    echo "=== $archivo ==="
    rg -C 3 "concepto" "$archivo"
  done

Este patrón te da una salida con el nombre del archivo y el contexto alrededor de la coincidencia. Perfecto para alimentar a un modelo sin perder la trazabilidad de dónde viene cada fragmento. Luego puedes pasarle eso a Ollama y decirle: basándote en este contexto, responde a esta pregunta. Y lo mejor: sabes exactamente de qué nota sacó la información.

Contexto ordenado y en JSON

# Contexto ordenado y en JSON para la IA
fd -e md -X rg --sort path --json -C 3 "ollama" | \
  jq '[.[] | select(.type == "context" or .type == "match") | \
    .data.lines.text] | join("\n")' -r | \
  curl -s http://localhost:11434/api/generate \
    -d "{\"model\": \"llama3.2\", \
         \"prompt\": \"Resume:\n$(cat)\n\", \
         \"stream\": false}" | \
  jq -r '.response'

Esto ya es una pasada. Estás obteniendo el contexto ordenado, en JSON, filtrando solo los tipos que te interesan, uniendo las líneas, y pasándoselo al modelo. Es un pipeline de cuatro herramientas —fd, rg, jq, curl— trabajando en perfecta armonía.

Funciones shell y aliases para el día a día

Vale, todo esto está muy bien, pero si tienes que escribir el pipeline cada vez… pues cansa. La gracia es crear funciones en tu shell y tenerlo todo a mano.

Aliases básicos

# Para Bash / Zsh — en ~/.bashrc o ~/.zshrc
alias f='fd'
alias rg='rg --smart-case'
alias rga='rg -uuu'        # Búsqueda nuclear

Para Fish, que es mi shell actual, la sintaxis es ligeramente diferente:

# Para Fish — en ~/.config/fish/config.fish
alias f "fd"
alias rg "rg --smart-case"
alias rga "rg -uuu"

Ese alias rga es mi favorito. Lo llamo búsqueda nuclear. Ignora todo, busca en todos los rincones, no respeta nada. Cuando no encuentras algo y estás seguro de que está ahí, usas rga.

Funciones de búsqueda

# Buscar en notas Markdown
buscar_notas() {
    local patron="$1"
    fd -e md -X rg -C 2 --smart-case "$patron"
}

# Buscar archivos por extensión y texto
btexto() {
    local ext="$1"
    local texto="$2"
    fd -e "$ext" -X rg --smart-case "$texto"
}

La función estrella: aresumen

Y ahora mi función favorita. La que conecta todo con la IA. La he llamado aresumen y hace exactamente lo que su nombre indica: busca en tus notas, extrae contexto, y te devuelve un resumen generado por Ollama.

# aresumen — busca y resume con IA local
aresumen() {
    local concepto="$1"
    if [ -z "$concepto" ]; then
        echo "Uso: aresumen <concepto>"
        return 1
    fi
    local contexto
    contexto=$(fd -e md -X rg -C 5 "$concepto" 2>/dev/null)
    if [ -z "$contexto" ]; then
        echo "No se encontró información sobre: $concepto"
        return 1
    fi
    echo "$contexto" | curl -s http://localhost:11434/api/generate \
        -d "{\"model\": \"llama3.2\",
             \"prompt\": \"Resume el siguiente contenido sobre $concepto:\n$(cat)\n\",
             \"stream\": false}" \
        | jq -r '.response'
}

Ejecutas aresumen "fd" y te devuelve un resumen de todo lo que has escrito sobre fd en tus notas. Es como tener un segundo cerebro que sabe exactamente lo que has apuntado.

La versión Fish: pregunta

Para los que usáis Fish, la versión queda aún más limpia.

# pregunta — versión Fish del pipeline IA
function pregunta
    if test (count $argv) -eq 0
        echo "Uso: pregunta <texto>"
        return 1
    end
    set contexto (fd -e md -X rg -C 5 $argv[1] 2>/dev/null)
    if test -z "$contexto"
        echo "No encontré información sobre: $argv[1]"
        return 1
    end
    echo $contexto | curl -s http://localhost:11434/api/generate \
        -d "{\"model\": \"llama3.2\",
             \"prompt\": \"Contexto:\n$(cat)\n\nBasándote en el contexto, responde: $argv[1]\",
             \"stream\": false}" \
        | jq -r '.response'
end

rfb: el buscador interactivo definitivo

Y la guinda del pastel. Como vimos en el episodio 810, podemos combinar rg con fzf y bat para tener un buscador interactivo que es una maravilla.

# rfb — rg + fzf + bat: buscador interactivo con preview
rfb() {
    if [ -z "$1" ]; then
        echo "Uso: rfb <texto_a_buscar>"
        return 1
    fi
    rg --line-number --no-heading --color=always --smart-case "$1" | \
        fzf --ansi \
            --color="ansi" \
            --delimiter=":" \
            --preview="bat --color=always --highlight-line={2} {1}" \
            --preview-window="right:60%:wrap" \
            --bind="enter:become(${EDITOR:-nano} +{2} {1})"
}

¿Qué hace esto? Busca el texto con rg, muestra los resultados en fzf con vista previa de bat resaltando la línea coincidente, y si pulsas enter te abre el editor justo en esa línea. Es como un IDE en la terminal. Si no viste el episodio 810, te recomiendo que le eches un ojo — allí lo cubrimos en detalle con todas las variantes.

Todas estas funciones las tienes disponibles en un gist que he preparado para que las copies directamente:

Errores comunes y cómo evitarlos

No quiero que te pase lo que me pasó a mí. Así que aquí van los errores más tontos que te puedes encontrar.

Con fd

Error 1: El binario se llama fdfind en Debian/Ubuntu. Si escribes fd y te dice comando no encontrado, ya sabes por qué. Crea el enlace simbólico: ln -s $(which fdfind) ~/.local/bin/fd.

Error 2: No encuentra archivos ocultos. fd no busca en archivos ocultos por defecto. Si tienes algo en .config y no aparece, usa -H.

Error 3: Respeta el .gitignore silenciosamente. Si no encuentras un archivo que sabes que está ahí, prueba con -I.

Error 4: Patrón *.md da error. Recuerda, fd usa regex por defecto, no glob. Usa -g '*.md' o -e md.

Con rg

Error 1: No busca en binarios. Por defecto rg se salta los binarios. Usa -a para forzar la búsqueda.

Error 2: Patrón que empieza con guión. Si buscas -algo, rg lo interpreta como flag. Usa rg -- -algo.

Error 3: Archivos enormes. rg mapea los archivos en memoria por defecto. Para archivos gigantes, usa rg --no-mmap.

Con el pipeline IA

Error 1: Contexto demasiado grande. Si le pasas 5000 líneas a un modelo pequeño, se va a quejar del límite de tokens. Solución: head -n 100.

Error 2: Comillas en el JSON. Si tu prompt contiene comillas dobles, escápalas con \". Es un clásico.

Error 3: Ollama no está corriendo. Siempre comprueba con curl http://localhost:11434/api/tags antes de lanzar el pipeline.

Error 4: Modelo no descargado. Si pones ollama run llama3.2 y te dice que no lo encuentra, haz ollama pull llama3.2 primero. Parece obvio, pero te aseguro que he estado más de 5 minutos pensando que el pipeline estaba mal cuando el problema era que no tenía el modelo.

SíntomaSolución
fd → comando no encontrado (Debian/Ubuntu)ln -s $(which fdfind) ~/.local/bin/fd
No encuentra archivos ocultosUsar -H
Respeta .gitignore sin avisarUsar -I
Patrón tipo *.md da error en fdUsar -g '*.md' o -e md
rg no busca en binariosUsar -a
Patrón empieza con guiónrg -- -patron
IA: modelo saturadohead -n 100 para limitar
IA: JSON inválidoEscapar comillas con \"
IA: Ollama no respondeVerificar con curl localhost:11434/api/tags
IA: modelo no encontradoollama pull llama3.2

Conclusiones

Llegados a este punto, ¿qué conclusión he sacado? Pues que llevaba años usando herramientas que funcionaban, sí, pero que no eran ni de lejos lo mejor que podía tener. find y grep cumplen, pero fd y rg vuelan.

Y la combinación con IA local es el verdadero punto de inflexión. Porque tener un modelo como llama3.2 o mistral en tu máquina está bien, pero darle tu propio contexto —tus notas, tu código, tus documentos— es lo que lo convierte en algo realmente útil. Es como tener un asistente que ha leído todo lo que has escrito y puede responderte sobre ello.

La clave está en el tridente:

  1. fd encuentra los archivos — rápido, intuitivo, moderno
  2. rg busca el texto dentro — el motor de regex más rápido que existe
  3. Ollama entiende el contexto — IA local, privada, sin depender de nadie

Y si te ha picado el gusanillo de la IA local, la semana que viene vamos a abrir la caja negra. Porque si hoy hemos visto cómo buscar y alimentar a un modelo, el siguiente paso es entender qué pasa dentro del motor de un agente de IA. Vamos a ver qué hay realmente en el motor que mueve a Anacleto: streaming, tool calls, extended thinking. Cómo funciona por dentro, cómo se gestionan las herramientas, cómo se encadenan las llamadas. Vamos a levantar el capó y mirar el motor.

No te lo pierdas 😊


Más información,

Deja una respuesta