- Esta guía es una referencia de diseño open source para crear CLI que actualizan de forma moderna los principios tradicionales de UNIX, fáciles de usar para las personas y robustas para la automatización
- Una buena CLI debe funcionar con un enfoque de personas primero, pero respetando convenciones como
stdout/stderr, códigos de salida, pipes y JSON para integrarse de forma natural con otros programas - La ayuda, la documentación, los mensajes de error y la salida deben permitir que el usuario entienda cuál es el siguiente paso; aquí cumplen un papel clave
--help, los ejemplos, las sugerencias, los indicadores de progreso y las explicaciones de estado - Los argumentos, flags, interacciones, configuración y variables de entorno deben diseñarse pensando tanto en scripts como en el uso desde terminal, y por seguridad es mejor no recibir secretos directamente por flags o variables de entorno
- El nombre, la distribución e incluso la recolección de analítica de una CLI no deben afectar la sensación de control del usuario ni la compatibilidad a largo plazo; incluso si se rompen convenciones, el propósito debe ser claro
Filosofía básica del diseño de CLI
- Esta guía es un documento open source que, con base en los principios tradicionales de UNIX, ofrece principios y lineamientos concretos para diseñar programas modernos de línea de comandos
- En el pasado, la línea de comandos era un entorno centrado en la máquina, más cercano a un REPL sobre una plataforma de scripting, pero hoy la CLI tiene un carácter mucho más de personas primero como una interfaz de texto para acceder a múltiples herramientas, sistemas y plataformas
- La línea de comandos tiene limitaciones antiguas y costumbres peculiares, pero puede usarse en casi cualquier laptop, sirve tanto para uso interactivo como para automatización y cambia con menos rapidez que otros componentes del sistema
- Esta guía no trata programas de terminal de pantalla completa como emacs o vim, ni depende de un lenguaje de programación o toolchain específicos
CLI centradas en personas y combinables
- Si una CLI es principalmente una herramienta que usan personas, entonces hay que pensar primero en las personas
- Muchos programas CLI están dirigidos a usuarios humanos, pero siguen conservando diseños de interacción heredados de un enfoque centrado en la máquina
- La filosofía UNIX, donde programas pequeños trabajan juntos mediante interfaces limpias, sigue siendo importante
- Convenciones como entrada estándar, salida estándar, error estándar, señales y códigos de salida hacen posible la composición entre programas
- El texto plano por líneas es fácil de pasar por pipes, y JSON resulta útil cuando se necesita información más estructurada
- La consistencia es clave para convertir el costo de aprender una CLI en eficiencia a largo plazo
- Si se siguen patrones existentes, a los usuarios les resulta más fácil adivinar comandos y opciones
- Cuando una convención perjudica la usabilidad, puede romperse con cuidado
- Una buena CLI no debe ser ni demasiado silenciosa ni demasiado verbosa; debe dar al usuario la cantidad de información que necesita
- Suele considerarse que las CLI tienen menor capacidad de descubrimiento que las GUI, pero se puede mejorar su facilidad de aprendizaje con ayuda completa, ejemplos, sugerencias de siguientes comandos e indicaciones sobre cómo actuar ante errores
Interacción conversacional y robustez
- Usar la línea de comandos se parece más a una conversación de varios intentos y correcciones que a una sola ejecución
- Ante una entrada incorrecta, se pueden sugerir correcciones cuando sea posible
- Se puede mostrar con claridad el estado intermedio de tareas de varios pasos
- Se puede pedir confirmación antes de operaciones peligrosas
- Una CLI debe ser realmente robusta y, al mismo tiempo, hacer que el usuario sienta que lo es
- Debe manejar con elegancia entradas inesperadas
- Cuando sea posible, las operaciones deberían ser idempotentes
- No debería mostrar por defecto stack traces alarmantes en la salida normal
- La empatía es importante para que el usuario sienta que el software está de su lado
- No se trata de usar emojis ni gamificar todo, sino de diseñar cuidadosamente para que el usuario tenga éxito
- El ecosistema de terminal tiene muchas inconsistencias y confusión, pero sus bajas restricciones también han permitido nuevas invenciones
- Conviene seguir los patrones existentes, pero los estándares que dañan la productividad o la satisfacción del usuario pueden dejarse de lado de forma intencional
Reglas básicas que deben cumplirse
- Siempre que sea posible, se debe usar una biblioteca de parseo de argumentos de línea de comandos
- El código de salida debe ser
0en caso de éxito y distinto de 0 en caso de error- Los scripts determinan si un programa tuvo éxito o falló a partir del código de salida
- La salida principal del comando debe enviarse a
stdout- La salida legible por máquinas también debe ir por defecto a
stdoutpara que los pipes funcionen correctamente
- La salida legible por máquinas también debe ir por defecto a
- Los mensajes para mostrar al usuario, como logs y errores, deben enviarse a
stderr- Así, al conectar con pipes, esos mensajes no se mezclan con la entrada del siguiente comando
Diseño de ayuda
- Cuando se pase
-ho--help, se debe mostrar ayuda suficiente- Los subcomandos también pueden tener su propia ayuda
-hno debe sobrecargarse con otro significado
- Si se ejecuta sin argumentos un comando que requiere argumentos, se debe mostrar ayuda breve
- Descripción del programa
- Uno o dos ejemplos de invocación
- Descripción de los flags
- Indicación de que se use
--helppara información más detallada
- Es recomendable incluir en la ayuda las vías de soporte
- Un sitio web o un enlace a GitHub son opciones comunes
- Si existe documentación web, la ayuda debe enlazar a esa documentación
- Si un subcomando tiene una página o ancla específica, es útil enlazarla directamente
- Como los usuarios suelen acudir primero a los ejemplos antes que a la documentación, conviene colocar los ejemplos al inicio de la ayuda
- Se puede mostrar primero un caso de uso complejo pero común
- Si hay muchos ejemplos, puede ser apropiado ponerlos en un comando de chuleta o en una página web separada
- La ayuda puede formatearse para que sea fácil de escanear
- Los encabezados en negritas mejoran la legibilidad
- Debe manejarse de forma independiente del terminal para evitar que se muestren caracteres de escape sin procesar
- Si el usuario escribe algo incorrecto y se puede inferir su intención, hay que sugerir una alternativa
- Por ejemplo,
brew update jqpodría indicarbrew upgrade jq - Se puede preguntar si se desea ejecutar el comando sugerido, pero se debe evitar ejecutarlo a la fuerza
- Por ejemplo,
- Si un comando debe recibir entrada por
stdinperostdines una terminal interactiva, debe mostrar ayuda de inmediato y terminar, o bien imprimir un mensaje enstderr
Documentación
- La ayuda ofrece un resumen inmediato y guía para tareas comunes, mientras que la documentación cubre con más detalle el propósito de la herramienta, lo que no busca hacer, cómo funciona y su uso completo
- Se debe ofrecer documentación web
- Debe poder buscarse y permitir enlaces a partes específicas
- La documentación web es el formato más completo
- También se debe ofrecer documentación basada en terminal
- Permite acceso rápido
- Se mantiene sincronizada con la versión instalada de la herramienta
- Funciona sin internet
- Se puede considerar ofrecer páginas
man- Muchos usuarios revisan primero
man mycmd - Como en
gitynpm, se puede acceder a páginasmanmediante un subcomandohelp
- Muchos usuarios revisan primero
Principios de salida
- La salida legible para humanos es lo más importante
- Un criterio simple para decidir si un flujo de salida está destinado a personas es si es un TTY
- Se debe ofrecer salida legible por máquinas siempre que no perjudique la usabilidad
- Un flujo de líneas de texto plano es la interfaz universal de UNIX
- Los usuarios esperan poder pasar la salida a herramientas como
grepy que funcione como corresponde
- Si una salida amigable para humanos rompe la salida amigable para máquinas, se puede ofrecer
--plain- En una salida tabular, dividir una celda en varias líneas puede romper la expectativa de un registro por línea
--plainofrece un formato de tabla sin modificaciones para scripts
- Si se pasa
--json, se debe imprimir JSON con formato- JSON facilita trabajar con estructuras de datos complejas y puede usarse con
jqy varias herramientas JSON para CLI - También es adecuado para conectarlo por pipe directamente con servicios web mediante
curl
- JSON facilita trabajar con estructuras de datos complejas y puede usarse con
- En caso de éxito, debe haber salida, pero debe mantenerse breve
- Los comandos UNIX tradicionales muchas veces no muestran nada si no hay problemas, pero para las personas eso puede parecer que se quedó detenido
- Si se necesita no mostrar salida para scripts, se puede ocultar la salida no esencial con la opción
-q
- Los comandos que cambian el estado deben informar al usuario qué cambió
git pushes un ejemplo que muestra lo que está haciendo y el nuevo estado de la rama remota
- Debe ser fácil ver el estado actual del sistema
git statusmuestra el estado del repositorio junto con pistas sobre los siguientes comandos que se pueden ejecutar
- Las operaciones que cruzan los límites del mundo interno del programa normalmente deben ser explícitas
- Cuando se leen o escriben archivos que el usuario no pasó como argumentos
- Cuando se descarga un archivo comunicándose con un servidor remoto
- El color debe usarse con intención
- Si se usa demasiado, pierde significado y dificulta la lectura
- Se debe desactivar el color si no es un TTY, si
NO_COLORestá configurado, siTERM=dumb, o si se pasa--no-color
- No se deben mostrar animaciones si
stdoutno es una terminal interactiva- Esto evita que los indicadores de progreso ensucien los logs de CI
- Al mostrar mucho texto, se puede usar un paginador como
less- Conviene usarlo solo cuando
stdinostdoutsean una terminal interactiva less -FIRXpuede servir como un conjunto razonable de opciones
- Conviene usarlo solo cuando
Mensajes de error
- Si los errores se construyen como documentación, se puede reducir el tiempo que el usuario pasa buscando documentación
- Los errores previsibles deben capturarse y reescribirse para que una persona los entienda
- Ejemplo: indicar que no se puede escribir en
file.txty que podría hacer faltachmod +w file.txt
- Ejemplo: indicar que no se puede escribir en
- La relación señal-ruido es importante
- Cuanta más salida irrelevante haya, más difícil será para el usuario identificar el problema
- Si hay varios errores del mismo tipo, se pueden agrupar bajo un único encabezado explicativo
- Conviene poner la información más importante al final de la salida
- Como la mirada del usuario se va al texto rojo, debe usarse poco y de forma intencional
- Si el error fue inesperado o difícil de explicar, se debe ofrecer información de depuración o traceback y cómo reportar el bug
- Para no abrumar al usuario, los logs de depuración pueden escribirse en un archivo en lugar de la terminal
- Debe ser fácil reportar bugs
- Un ejemplo es ofrecer una URL con la mayor cantidad posible de información ya completada
Argumentos y flags
- Los argumentos son parámetros posicionales y los flags son parámetros con nombre
- En
cp foo bar, las rutas de archivo son argumentos -r,--recursive,--file foo.txtson flags
- En
- Siempre que sea posible, se deben preferir los flags sobre los argumentos
- Requieren escribir más, pero el significado es más claro
- Facilitan cambiar la forma de entrada en el futuro
- Todos los flags deben tener un nombre largo
- Por ejemplo, tener
-hy--helpjuntos - Es útil para dejar el significado claro en scripts
- Por ejemplo, tener
- Los flags de una sola letra deben usarse solo para opciones frecuentes
- Así se conserva el espacio de nombres corto para flags que se agreguen más adelante
- Cuando se aplica la misma operación simple a varios archivos, varios argumentos son adecuados
- Una forma como
rm file1.txt file2.txt file3.txtencaja bien con el globbing
- Una forma como
- Si hay dos o más argumentos con significados distintos, es muy probable que el diseño esté mal
- Como excepción, acciones comunes y centrales como
cp <source> <destination>son lo bastante breves como para que valga la pena recordarlas
- Como excepción, acciones comunes y centrales como
- Si existe un nombre de flag estándar, se debe seguir
-a, --all-d, --debug-f, --force--json-h, --help-n, --dry-run--no-input-o, --output-p, --port-q, --quiet-u, --user--version
- El valor predeterminado debe ser el comportamiento correcto para la mayoría de los usuarios
- Si se asume que los usuarios van a encontrar, recordar y usar siempre los flags adecuados, la experiencia de la mayoría empeora
- Si no hay entrada, se puede preguntar con un prompt, pero el prompt no debe ser obligatorio
- Siempre se debe ofrecer una forma de pasar la entrada mediante flags o argumentos
- Si
stdinno es una terminal interactiva, se debe omitir el prompt y exigir los flags o argumentos necesarios
- Se debe pedir confirmación antes de operaciones peligrosas
- En una ejecución interactiva, se puede pedir que se ingrese
yoyes - En una ejecución no interactiva, se puede exigir
-fo--force - Para operaciones graves, se puede pedir que se escriba manualmente el nombre de lo que se va a eliminar o ofrecer un flag como
--confirm="name-of-thing"
- En una ejecución interactiva, se puede pedir que se ingrese
- Si se acepta entrada o salida de archivos, se debe admitir
-parastdinostdout- Esto permite conectarlo con la salida de otros comandos sin archivos temporales
- Siempre que sea posible, se deben procesar de forma independiente el orden de argumentos, flags y subcomandos
- Porque es común que el usuario vuelva a ejecutar un comando agregando opciones al final del comando anterior
- Los valores secretos no deben leerse directamente desde flags
- Un valor de
--passwordpuede quedar expuesto en la salida depso en el historial del shell - Se debe considerar la entrada desde archivos como
--password-fileo desdestdin
- Un valor de
Interacción
- Los prompts o elementos interactivos deben usarse solo cuando
stdinsea un TTY- Durante scripts o entrada por pipe, los prompts no funcionan, así que se debe informar con un error qué flags hay que pasar
- Si se pasa
--no-input, no debe haber prompts ni comportamiento interactivo- Si hace falta entrada, debe fallar e indicar cómo pasarla mediante flags
- Al pedir una contraseña, no se debe mostrar lo que el usuario escribe
- Esto se maneja desactivando el echo de la terminal
- El usuario debe poder salir
- Incluso si está detenido por I/O de red u otra causa, Ctrl-C debe funcionar
- Si se trata de un wrapper como SSH, tmux o telnet, donde no se puede salir con Ctrl-C, se debe indicar claramente cómo escapar
Subcomandos
- Si la herramienta es lo suficientemente compleja, un conjunto de subcomandos puede reducir la complejidad
- Agrupar varias herramientas relacionadas en un solo comando puede facilitar el uso y el descubrimiento
- También facilita compartir flags globales, ayuda, configuración y mecanismos de almacenamiento
- Debe haber consistencia entre subcomandos
- Se deben usar los mismos nombres de flags para el mismo significado
- El formato de salida también debe ser similar
- Para subcomandos de varios niveles, se debe usar un esquema de nombres consistente
- Es común un patrón de dos niveles de sustantivo y verbo, como
docker container create - Tanto
noun verbcomoverb nounson posibles, peronoun verbparece más común
- Es común un patrón de dos niveles de sustantivo y verbo, como
- Se deben evitar comandos ambiguos o con nombres parecidos
- Tener
updateyupgradejuntos puede generar confusión
- Tener
Robustez
- Se debe validar todos los datos que ingresa el usuario
- Como pueden entrar datos inválidos, hay que revisarlos pronto y abortar con un error comprensible
- La capacidad de respuesta es más importante que la velocidad
- Es bueno mostrarle algo al usuario dentro de los primeros 100 ms
- Incluso antes de una solicitud de red, se debe imprimir un mensaje para que no parezca que se quedó colgado
- En las tareas largas se debe mostrar el progreso
- Si no hay salida por un rato, el programa parece averiado
- Si un indicador de progreso se queda mucho tiempo en el mismo punto, se puede mostrar una estimación del tiempo restante o una animación para indicar que sigue trabajando
- El procesamiento en paralelo conviene cuando sea posible, pero con cuidado
- Mostrar en el shell el progreso de trabajos paralelos es difícil, y si la salida se mezcla resulta confuso
- Las múltiples barras de progreso de
docker pullson un ejemplo de cómo mostrar lo que está pasando - Aunque en funcionamiento normal los logs se oculten detrás de la barra de progreso, cuando hay un error se deben mostrar para poder depurar
- Las operaciones de red deben tener timeout
- El timeout debe poder configurarse y necesita un valor predeterminado razonable
- Debe ser posible recuperarse después de un fallo
- Tras un fallo temporal, al volver a ejecutar con
<up>y<enter>, debería poder continuar desde donde se interrumpió
- Tras un fallo temporal, al volver a ejecutar con
- Si es posible, conviene un enfoque de crash-only
- Ante un fallo o interrupción, puede terminar de inmediato y dejar la limpieza para la siguiente ejecución
- Los usuarios pueden usar la herramienta de formas no previstas
- Pueden envolverla en scripts, usarla con internet inestable, ejecutar varias instancias al mismo tiempo o correrla en entornos no probados
Compatibilidad futura
- Los subcomandos, argumentos, flags, archivos de configuración y variables de entorno son parte de la interfaz, y deben mantenerse funcionando por mucho tiempo
- Los cambios, cuando sea posible, deben ser aditivos
- En vez de romper el comportamiento de un flag existente, se puede agregar uno nuevo
- Si hace falta un cambio no aditivo, se debe advertir con anticipación
- Cuando se use un flag en desuso, hay que avisar del cambio futuro y también indicar la forma compatible con el futuro que ya puede usarse
- La salida para humanos puede cambiar y, en general, está bien
- Se debe orientar a los usuarios a usar
--plaino--jsoncuando necesiten una salida estable en scripts
- Se debe orientar a los usuarios a usar
- Se deben evitar los subcomandos catch-all
- Si algo que no es un subcomando conocido se trata automáticamente como
run, agregar nuevos subcomandos más adelante puede romper usos existentes
- Si algo que no es un subcomando conocido se trata automáticamente como
- No se deben permitir abreviaturas arbitrarias de subcomandos
- Si
installaceptaioins, luego será difícil agregar otro comando que empiece coni - Los alias deben ser explícitos y mantenerse estables
- Si
- Hay que considerar si el comando seguirá ejecutándose de la misma manera dentro de 20 años
- No se debe crear una bomba de tiempo que deje de funcionar porque una dependencia externa de internet cambió o desapareció
Señales y caracteres de control
- Cuando el usuario envía Ctrl-C, es decir, la señal INT, el programa debe terminar lo más rápido posible
- Antes de empezar las tareas de limpieza, debe decir de inmediato algo
- El código de limpieza debe tener timeout
- Si durante una limpieza larga se vuelve a presionar Ctrl-C, debe ser posible omitir la limpieza
- Docker Compose avisa que, si se presiona Ctrl-C una vez más durante el apagado, puede forzar la detención inmediata de los contenedores
- El programa debe asumir que puede arrancar aunque no se haya ejecutado la limpieza anterior
Configuración y variables de entorno
- La forma de configurar debe variar según la especificidad, la estabilidad y la complejidad
- La configuración que cambia con frecuencia en cada ejecución se adapta bien a los flags
- La configuración relativamente estable que cambia según el usuario, el proyecto o la máquina se adapta bien a flags y variables de entorno
- La configuración estable dentro de un proyecto para todos los usuarios se adapta bien a un archivo de configuración específico del comando y versionado
- Conviene seguir la XDG Base Directory Specification
- Su objetivo es soportar ubicaciones de configuración de propósito general como
~/.configpara reducir la proliferación de dotfiles en el directorio personal
- Su objetivo es soportar ubicaciones de configuración de propósito general como
- Si se van a modificar automáticamente archivos que no son de configuración del propio programa, se debe pedir consentimiento al usuario y decir exactamente qué se hará
- Es preferible crear un archivo de configuración nuevo en vez de anexar contenido a un archivo de configuración del sistema ya existente
- La prioridad de configuración debe aplicarse de mayor a menor
- flags
- variables de entorno del shell en ejecución
- configuración a nivel de proyecto
- configuración a nivel de usuario
- configuración de todo el sistema
- Las variables de entorno son adecuadas para comportamientos que cambian según el contexto en que se ejecuta el comando
- Los nombres de variables de entorno deben usar solo mayúsculas, números y guiones bajos para máxima portabilidad, y no deben empezar con un número
- Los valores deberían ser de una sola línea cuando sea posible
- Los valores multilínea generan problemas de uso cuando se trabajan junto con el comando
env
- Los valores multilínea generan problemas de uso cuando se trabajan junto con el comando
- No se deben apropiarse sin cuidado de nombres muy usados
- Se puede consultar la lista de variables de entorno estándar de POSIX
- Siempre que sea posible, se deben revisar variables de entorno de uso general
NO_COLOR,FORCE_COLORDEBUGEDITORHTTP_PROXY,HTTPS_PROXY,ALL_PROXY,NO_PROXYSHELLTERM,TERMINFO,TERMCAPTMPDIRHOMEPAGERLINES,COLUMNS
- Cuando corresponda, se pueden leer variables de entorno desde
.env- Es útil para variables que casi no cambian mientras se trabaja dentro de un directorio específico
.envno sustituye a un archivo de configuración formal- A menudo no se guarda en el control de código fuente
- Solo tiene tipo string
- Tiende a desordenarse con facilidad
- Es propenso a problemas de codificación
- Tiende a contener credenciales sensibles y material de claves
- Los valores secretos no deben leerse desde variables de entorno
- Las variables de entorno pueden exponerse a través de procesos, logs,
docker inspect,systemctl show, etc. - Los secretos deben recibirse mediante archivos de credenciales, pipes, sockets
AF_UNIX, servicios de gestión de secretos u otros mecanismos de IPC
- Las variables de entorno pueden exponerse a través de procesos, logs,
Nombres, distribución y recolección de analíticas
- Como los usuarios escriben con frecuencia el nombre de un programa CLI, debe ser simple y fácil de recordar
- Si es demasiado genérico, puede chocar con otros comandos o confundir a los usuarios
- Conviene que el nombre use minúsculas y guiones solo cuando haga falta
curles un buen nombre, mientras queDownloadURLes un ejemplo de lo contrario
- El nombre debe ser corto, pero los nombres demasiado cortos encajan mejor en utilidades muy comunes como
cd,lsyps - Debe ser un nombre fácil de teclear
- Un caso conocido es que el nombre inicial de Docker Compose era
plum, pero se cambió afigporque resultaba incómodo de escribir con una sola mano
- Un caso conocido es que el nombre inicial de Docker Compose era
- Cuando sea posible, se debe distribuir como un solo binario
- Si un solo binario es difícil, se debe distribuir usando el instalador de paquetes predeterminado de la plataforma, de forma que sea fácil de desinstalar
- Las herramientas específicas de un lenguaje, por ejemplo los linters de código, sí pueden asumir que el usuario ya tiene el intérprete de ese lenguaje
- Debe ser fácil de desinstalar
- Como es común querer quitarlo justo después de instalarlo, conviene poner las instrucciones de desinstalación debajo de las de instalación
- Las métricas de uso pueden ayudar a mejorar la herramienta, pero los usuarios de CLI esperan tener control sobre su entorno
- No se deben enviar datos de uso ni de fallos sin consentimiento
- Se debe dejar claro qué se recopila, por qué se recopila, cuánto se anonimiza, cómo se anonimiza y cuánto tiempo se conserva
- Idealmente debería ser opt-in, y si se elige la recolección por defecto con opt-out, debe informarse claramente en el sitio web o en la primera ejecución y poder desactivarse con facilidad
- También se pueden considerar alternativas a la recolección de analíticas
- instrumentar la documentación web
- instrumentar las descargas
- hablar directamente con los usuarios y fomentar comentarios y solicitudes de funciones en la documentación y el repositorio
1 comentarios
Opiniones de Hacker News
Es cierto que “hoy la mayoría ni siquiera sabe qué es la línea de comandos”, pero lo mismo pasaba en la década de 1980, que TFA toma como referencia.
La diferencia es que hoy hay más personas que saben y pueden usar la línea de comandos que en cualquier otro momento de la historia; aumentaron al menos en un orden de magnitud, quizá en dos, así que se puede decir que estamos en una edad dorada de la CLI.
El estado global y las dependencias dificultan razonar sobre el sistema y, al final, también complican la depuración y la optimización de rendimiento.
Si extraes código de 500, 1.000, 5.000, 10.000 o a veces 50.000 líneas para que se ejecute por separado, muchas cosas se vuelven mucho más claras.
Si se hace bien, también puede inducir una estructura de Functional Core, Imperative Shell, porque una línea de comandos alineada con la filosofía Unix debería tener muchas operaciones sin efectos secundarios y pocas con efectos secundarios.
Puedes crear pequeños comandos que generen e impriman el estado del sistema justo antes de que se tome una mala decisión, y ejecutarlos en producción de forma prácticamente segura.
Así también es más fácil entregar estas herramientas e involucrar a personas que deberían formar parte del factor bus.
No viene al caso qué piensa de la terminal alguien de una aldea aislada en el Amazonas.
Para juzgar un cambio cualitativo en algo cuyo tamaño creció, hay que mirar la proporción; si solo miras el número absoluto, obtienes una frase verdadera pero sin sentido.
Por ejemplo, hoy quizá haya más oyentes de jazz en términos absolutos que durante el apogeo cultural del género, pero eso no es porque el jazz sea más popular, sino porque hay más personas escuchando música en general.
Aun así, sería difícil decir que el jazz es más importante e influyente en Estados Unidos que en su apogeo.
Me gustaría que también consideraran una opción
--dry-runque muestre de antemano qué ocurriría sin hacer cambios reales.Es realmente útil para aprender una herramienta o para verificar que usaste bien opciones complejas y globs de archivos antes de ejecutar una operación difícil de revertir.
--dry-runy exigir un flag--executepara la ejecución real.No sé qué habría hecho sin eso, y me salvó varias veces de situaciones en las que casi rompía todo por completo.
--commit.Para scripts que realizan operaciones imposibles o difíciles de revertir, este enfoque se siente más seguro.
Me confunde cómo debería diseñarlo en las herramientas que estoy creando.
Si se llama a una API para obtener datos, no sé cómo convertir eso en un dry run.
¿Hay que dar un ejemplo falso y una salida falsa?
do_thing.py | dry-runEs como pedirle al prompt: “explícame el proceso paso a paso”.
No se trata solo de no mostrar animaciones si la salida estándar no es una terminal interactiva: nunca se deberían mostrar animaciones en stdout.
TFA estaba bien en general, pero me decepcionó buscar una sección que explicara la diferencia entre stderr y stdout y ver que no lo hacía.
stderr no es solo para errores; es el lugar al que deberían ir todos los logs y toda la salida informativa, y si es una terminal también puede ser el área donde poner animaciones.
stdout debe ser la salida real y útil, y debe ser consistente sin importar si es una terminal o no.
Por ejemplo, en
echo foo | mysed 's/oo/aa/' | cat, el stdout demysedesfaa, y en stderr deberían ir logs como información de versión o lo que encontró.No quiero pelearme con una herramienta usando grep para obtener solo la salida real, ni quiero que el comportamiento cambie al quitar
| caty eso haga más difícil depurar.Si pidió
--help, debería ir a stdout; si pidió logs, los logs también deberían ir a stdout.Si es información no solicitada, stderr es lo correcto.
Si pudiéramos volver el tiempo atrás, creo que con nombres como stdin, stdout y stdext habría sido más probable que la gente siguiera esta convención.
Pero como se llama stderr, los desarrolladores piensan razonablemente que es “para reportar errores”, y si no es un error lo ponen en stdout.
Aun así, como estructura de programa este enfoque es mejor, y aunque al principio pueda confundir más, permite hacer cosas más útiles con la salida, así que vale la pena.
Tampoco creo que el color pueda considerarse información.
Ojalá evitaran el consejo de “usar símbolos y emoji cuando aporten claridad”.
El ejemplo de yubikey-agent muestra exactamente lo que no me gusta de los README de GitHub y de las interfaces de usuario juguetonas.
Técnicamente, los símbolos y emoji pueden renderizarse de forma distinta según la terminal y hacer que los mensajes resulten confusos.
Estéticamente, la tolerancia de cada persona a lo juguetón y vivaz varía mucho, así que deberían usarse con mucha moderación y solo cuando de verdad se sabe lo que se está haciendo.
También me gusta la salida de terminal con color, y los emoji también tienen color, así que ayudan a captar rápidamente el panorama general de la situación.
También me gusta el resaltado de sintaxis; sin él, siento que el código es mucho más difícil de leer.
No todo el mundo es así, y así como no espero que mis gustos sean el valor predeterminado, tampoco deberían imponerse por defecto los gustos opuestos.
A algunas personas les gusta y a otras no, así que que esté desactivada por defecto y que el usuario pueda elegir.
Por ejemplo, uno para éxito y uno para fallo sería suficiente.
Además, la información que transmite el emoji debe repetirse siempre también en texto.
La primera vez que vi CLI Guidelines, dejé de leer en esa sección.
Esta vez leí el resto, y en general estaba bastante bien.
Entiendo que algunas CLI son tan grandes como
awsque necesitan anidamiento, pero seguir una CLI anidada de nivel en nivel es realmente frustranteEn la mayoría de las apps preferiría que el help escupa todas las opciones y me deje encontrar lo que necesito con
lessEsta TUI es UI/UX pura y opcional, y pasa la configuración elegida a un archivo generado aparte o a una función
Ese archivo o función siempre se puede invocar directamente desde la terminal, y también ofrece todas las opciones y ayuda que usa la TUI
Así también se puede usar en scripts y resulta útil para usuarios con distintos niveles de experiencia
Si sabes qué subcomando necesitas, que te reduzcan las opciones solo a las necesarias es bastante útil, pero si no lo sabes puede ser un sufrimiento
Revisar con grep una lista enorme de opciones también es difícil, así que hace falta equilibrio
man?--helpde los subcomandos dentro del comando superiorPor ejemplo, que
git stash --helpmuestre tal cual la ayuda degit stash, incluyendo cada elementoLa explicación de que “tradicionalmente, los comandos UNIX se escribían principalmente suponiendo que otros programas los usarían” no es exacta
Originalmente estaban pensados sobre todo para uso interactivo dentro de un shell de login
Había programas que producían salida por stdout (
ls,cat,find,tty,who,date) y filtros de texto silenciosos (tr,grep,cut,uniq,sort,wc), y en esa época se podían hacer tareas básicas de computación con comandos de una líneaLos programas complejos se escribían en C, y después de que aparecieron lenguajes específicos de dominio como
sedy AWK, algunos programas con mucho procesamiento de strings pasaron al shellEl shell no es un entorno de programación normal, y nunca estuvo pensado para ese uso
Hoy en día, para ambos casos muchas veces conviene más usar Python o Lua, pero shell y C son lo que suele estar instalado de forma más universal
La separación en
sh,bash,ksh,cshes un buen ejemploMe parece bastante razonable ver el conjunto estándar de herramientas POSIX como la biblioteca estándar de varios lenguajes de shell
La línea de comandos de Unix actual, por un lado, es “increíblemente útil”, y por otro está rota por diseño
Su utilidad queda clara si uno piensa cuánto tomaría escribir lo siguiente en C o Rust:
curl -sS https://go.dev/doc/devel/release | html2text | grep -o -P '\bgo\d+\.\d+\.\d+\b' | sort -V | uniq | tail -1Pero para ver por qué está rota por diseño basta mirar https://news.ycombinator.com/item?id=29747034
El problema es que una interfaz de línea de comandos debe ser fácil de leer para humanos y, al mismo tiempo, fácil de leer para máquinas, y no hay una forma estándar de resolverlo
Para el problema de que humanos y máquinas puedan leer al mismo tiempo, quizá podría haber existido una solución estándar desde el principio
Apple creó las Human Interface Guidelines para concretar el significado de las abstracciones visuales de la UI de escritorio
Pero la línea de comandos no la creó gente que pensara como los diseñadores de Apple, sino gente que pensaba: “la pereza, la impaciencia y la arrogancia son virtudes, así que ¿cómo expreso lo que quiero con el mínimo código?”
En ese momento no fue una elección equivocada, y cada byte importaba, pero esa decisión de diseño quedó incrustada en una cadena de herramientas que ahora es inamovible
Para obtener algo mejor que lo actual, parece que habría que abandonar en la práctica la cadena de herramientas POSIX, y cuesta imaginar construir una UX descubrible y conceptualmente coherente sobre la base actual
Incluso en el mismo momento hay muchas formas de permitir salidas diferentes
Pero tendría que haber un estándar que todos sigan, y ahí es donde se complica
Si el problema es que casi ninguna implementación de
lspermite terminar los nombres de archivo con caracteres NUL en vez de saltos de línea, la solución parece tan obvia que siento que me estoy perdiendo algo¿La interfaz del shell lo rechaza por alguna razón?
--jsonDice “no leas secretos desde variables de entorno” y propone archivos de credenciales, pipes, sockets AF_UNIX, servicios de gestión de secretos y otros mecanismos de comunicación entre procesos; me da curiosidad cuál de estos es el más cómodo y portable.
También me pregunto si los servicios de gestión de secretos se usan solo en el trabajo, o también en proyectos personales.
Hay un artículo que profundiza un poco más en el manejo de secretos en la línea de comandos en https://smallstep.com/blog/command-line-secrets/
Los archivos de credenciales son una opción simple y portable.
Los archivos ya tienen un modelo de permisos y no dependen de servicios externos ni de APIs propietarias.
Si un programa acepta un archivo de credenciales, también es compatible con systemd credentials.
systemd credentials es más seguro que los archivos de credenciales sin cifrar, está cifrado y también puede vincularse a un TPM, pero el software que usa las credenciales no necesita soportar TPM por su cuenta.
Por ejemplo, mbsync(https://isync.sourceforge.io/mbsync.html) tiene aproximadamente tres formas de proporcionar la contraseña de autenticación IMAP.
Si no configuras una contraseña, aparece un prompt al ejecutarlo; también puedes poner una contraseña en texto plano en el archivo de configuración, pero eso es incómodo para compartir.
Además, puedes configurar un comando para obtener la contraseña, de modo que puedas delegar el manejo a un gestor de contraseñas como pass(1)(https://www.passwordstore.org/) o a un prompt gráfico interactivo.
Está bien documentado y se puede configurar fácilmente sin tener que construir nada adicional por tu cuenta.
Los gestores de secretos como Doppler(https://Doppler.com) o AWS Secrets Manager(https://aws.amazon.com/secrets-manager/) tienen la ventaja de proteger los secretos en un lugar seguro y minimizar su exposición.
Incluso pueden reducir la exposición ante desarrolladores internos, lo que ayuda a evitar filtraciones de datos que habrían sido fáciles de prevenir.
Estas filtraciones pueden poner en riesgo a toda la empresa y son cada vez más comunes.
Artículos relacionados: Command Line Interface Guidelines - https://news.ycombinator.com/item?id=38053692 - octubre de 2023, 1 comentario
Command Line Interface Guidelines - https://news.ycombinator.com/item?id=31651161 - junio de 2022, 1 comentario
Command Line Interface Guidelines - https://news.ycombinator.com/item?id=25492119 - diciembre de 2020, 5 comentarios
Es realmente molesto que algunas terminales ejecuten automáticamente un comando si la cadena pegada desde el portapapeles termina con un salto de línea.
Personalmente, creo que una interfaz de línea de comandos no debería hacer eso.
bind 'set enable-bracketed-paste on'.Si te resulta molesto, también puedes desactivarlo.
También permite varias líneas si hace falta, y se ejecuta solo al presionar Enter.
Fish shell suele tener valores predeterminados razonables, y hasta ahora no he encontrado mucho que quiera personalizar aparte del prompt.
La mayoría de los que probé tenían una opción para eliminar los saltos de línea del contenido del portapapeles.
#y luego pego.Aunque se interprete el salto de línea, toda la línea queda como comentario y es seguro; antes de ejecutarlo de verdad, basta con borrar el
#del inicio de la línea.