12 puntos por GN⁺ 2025-08-13 | 1 comentarios | Compartir por WhatsApp
  • Introducción a cómo implementar una función de autocompletación con tabulador en Bash y Zsh que muestra descripciones incluso para palabras ya completadas
  • Bash y Zsh usan APIs de autocompletación distintas, y solo Zsh ofrece por defecto la función de mostrar descripciones en la autocompletación
  • Se implementa una estructura que genera candidatos con _generate_foo_completions y los devuelve con COMPREPLY en Bash y compadd en Zsh
  • Para implementar en Bash la función de descripciones de Zsh, se incluyen las descripciones en las cadenas candidatas y solo se eliminan cuando hay un único candidato
  • Incluso cuando hay un solo candidato, se mejora el comportamiento agregando ambigüedad intencionalmente para que al presionar <TAB> se muestre la descripción
  • El script final ofrece la misma experiencia de usuario en ambos shells y soporta completado parcial, completado total y visualización de descripciones de candidatos

Contexto del problema

  • La autocompletación con tabulador (tab-completion) es útil para explorar comandos o flags, y ayuda especialmente cuando se usa por primera vez una API o una herramienta CLI
  • Zsh por defecto solo muestra descripciones cuando hay varios candidatos, y Bash solo puede hacerlo mediante configuración adicional
  • Sin embargo, para una palabra ya completada, ninguno de los dos shells muestra descripciones
  • Por eso, para ver una descripción, el usuario debe pasar por un proceso incómodo como este
    • borrar algunos caracteres para que coincidan varios candidatos
    • presionar la tecla <TAB> para revisar la lista de candidatos
    • buscar visualmente la descripción deseada
    • volver a escribir los caracteres eliminados para ejecutar el comando

Resumen de la solución

  • Incluso cuando hay un solo candidato, se agrega un candidato ficticio (dummy completion) para volver ambigua la lista
  • De este modo, tanto Bash como Zsh muestran la lista de candidatos junto con sus descripciones
  • Eso sí, hay que evitar que el texto de la descripción se inserte en el comando real

Conceptos básicos

  • La autocompletación con tabulador funciona recibiendo la palabra actual y la posición del cursor al presionar <TAB>, y devolviendo una lista de candidatos posibles
  • Bash: asigna los candidatos al arreglo COMPREPLY
  • Zsh: registra los candidatos con el comando compadd
  • La función _generate_foo_completions imprime cadenas candidatas, y en un caso real puede generarlas dinámicamente según el estado del CLI

Soporte simultáneo para Bash y Zsh

  • Se implementa para cada shell con las funciones _complete_foo_bash y _complete_foo_zsh
  • Se distingue con if [ -n "${ZSH_VERSION:-}" ]; then ... elif [ -n "${BASH_VERSION:-}" ]; then ... fi
  • El usuario aplica el script registrándolo en .bashrc o .zshrc

Mostrar descripciones en Zsh

  • Se usa el formato nombre: descripción en las cadenas candidatas
  • Zsh: compadd -d raw -- $trimmed para pasar nombre y descripción como arreglos paralelos
  • Bash: solo se pasan a COMPREPLY los candidatos sin la parte de la descripción (por defecto no soporta descripciones)

Implementar descripciones en Bash

  • Cuando hay varios candidatos, se muestran tal cual las cadenas que incluyen la descripción
  • Cuando hay un solo candidato, solo entonces se elimina la descripción
  • Se aprovecha el comportamiento de autocompletación de Bash, que inserta solo el prefijo común, para evitar que la descripción forme parte de la entrada real

Mostrar descripciones incluso con un solo candidato

  • Para mostrar la descripción incluso al presionar <TAB> sobre una palabra ya completada, se agrega un candidato ficticio que introduce ambigüedad
  • Zsh: se agrega el candidato ficticio tanto a los dos arreglos paralelos (raw, trimmed)
  • Bash: cuando hay un solo candidato, se agrega solo el nombre a trimmed

Resultado final

  • Con varios candidatos, se muestran tanto el nombre como la descripción
  • Incluso con un solo candidato, la descripción puede verse con <TAB>
  • Bash y Zsh ofrecen la misma experiencia
  • Ejemplo de uso:
    $ foo <TAB>  
    apple: a common fruit banana: starchy and high in potassium  
    apricot: sour fruit... cherry: small and sweet...  
    

1 comentarios

 
GN⁺ 2025-08-13
Opiniones en Hacker News
  • En fish, si un programa que te interesa ofrece una página man, solo hace falta ejecutar fish_update_completions
    • Este comando analiza todas las páginas man del sistema y genera archivos de autocompletado en ~/.cache/fish/generated_completions/
    • Si la página man es deficiente o no existe, puedes escribirla tú mismo y contribuirla upstream
    • El formato de fish es simple, así que con la documentación oficial basta
    • Ej.: curl con -L → 'Follow redirects', -O → 'Write output to file named as remote file'
    • Cuando comparto pantalla, la gente piensa que uso zsh y plugins, pero en realidad uso fish bonito solo con la configuración predeterminada
    • Pero estaría bien que mejoraran el autocompletado no prefijo donde car TAB se expande a blkdiscard, porque eso falla incluso si cargo no está en el PATH
    • En el caso de programas que no tienen página man y solo ofrecen --help, me pregunto si fish tiene algo como _gnu_generic de zsh o complete -F _longopt de bash
    • También hay un script para generar autocompletado basado en páginas man en zsh → zsh-manpage-completion-generator
    • En OpenSUSE, al ejecutar zypper search fish-completion, salen más de 200 paquetes, así que sentí que algo andaba raro
  • Es incómodo que el autocompletado de bash se vaya volviendo más “inteligente” y bloquee el completado de archivos/directorios si decide que un nombre de archivo no es apropiado en la posición actual del cursor
    • Preferiría que siempre hiciera fallback a completado de nombres de archivo
    • Por esto incluso pensé en desactivar por completo los scripts de completado
    • En bash existen funciones como complete-filename, enlazada a M-/, que completan solo nombres de archivo ignorando el contexto
    • Hay comandos cuyo completado de archivos está totalmente roto, así que lo esquivo completando primero con ls y luego cambiando el comando
    • Si el nombre de archivo existe pero no es ejecutable, sería menos confuso mostrar un mensaje como “file foo.exe exists but it isn't executable”
    • Al ver que después de ejecutar complete -r se comporta como quiero, parece que el problema está en los scripts de bash-completion
    • Se parece a esa UX frustrante de la validación en frontend en formularios web cuando, al escribir solo la primera letra de un email, ya aparece el error “Invalid email!”
  • El artículo lo escribí yo; ojalá a otros también les resulte interesante
    • En lugar de cargar cada vez los scripts de completado de zsh, si los instalas en $fpath quedan en caché y mejora la velocidad de inicio
    • Al distribuir con Homebrew, puedes instalar completions automáticamente
    • El sistema de completado de zsh es grande y difícil de aprender, pero en el trabajo lo he ido puliendo mientras agrego opciones por playbook, autocompletado de tags y más a un wrapper de ansible
  • Últimamente empecé a usar la librería usage para desarrollar CLI → se integra con clap y puede generar completions, argparse y páginas man
    • Todavía estoy pensando si vale la pena reemplazar el bloque argparse de scripts fish, pero es muchísimo mejor que optparse
    • Yo también estoy creando una herramienta parecida para definir especificaciones de CLI y justo acabo de agregar soporte para fish
  • Se presenta fx.wtf, que soporta autocompletado de campos JSON en bash/zsh
    • Es más liviano que ijq, aunque según el caso puede resultar útil
  • Tutorial para crear un completer con funciones integradas de zsh → zsh-completions-howto
  • Me pregunto si existe algún flag estándar que permita que un programa no tenga que escribir scripts de bash
    • En zsh, _gnu_generic permite un completado simple basado en --help
    • Hay ejemplos como clap_complete de Rust, la opción --generate de ripgrep y la generación de completado en tiempo de ejecución de PHP Symfony
    • Estaría bien tener un estándar --completion que las bibliotecas parser comunes implementen automáticamente
  • En ksh, se puede implementar completado simple solo definiendo arreglos
    • Ej.: si pones nombres de señales en el arreglo complete_kill_1, obtienes completado para el primer argumento del comando kill
    • Me pregunto si eso es sintaxis de ksh93 o si fue backporteado a oksh
  • Ejemplo de un snippet simple de completado zsh que hice: en la función set-java-home, usar _describe para completar por versión la lista de ~/apps/java/*
    • Es una estructura muy simple, casi un one-liner
  • Como la función principal de los LLM es el autocompletado de texto, GitHub Copilot hace bastante bien el completado de bash y zsh en la terminal de vscode