Writing Your Own Simple Tab-Completions for Bash and Zsh

50 lihaoyi 22 8/10/2025, 9:50:25 AM mill-build.org ↗

Comments (22)

homebrewer · 1h ago
With fish, if the program you're interested in hasn't betrayed the decades-old tradition of shipping man pages, it's often as simple as running `fish_update_completions`.

It parses all man pages on your system and generates completion files for you. By default, they go into ~/.cache/fish/generated_completions/*

If the man page was written poorly/is missing, you can always write your own completion (and hopefully send it upstream). fish uses such a simple format that I don't think there's any need for tutorials save the official doc:

https://fishshell.com/docs/current/completions.html

For example, here's an excerpt from curl

  complete --command curl --short-option 'L' --long-option 'location' --description 'Follow redirects' 
  complete --command curl --short-option 'O' --long-option 'remote-name' --description 'Write output to file named as remote file'
btreecat · 1h ago
When I screen share, people don't realize I'm not using zsh and dozen plugins. It's just fish and it's beautiful out of the box.
kekebo · 27m ago
Thank you for the comment. https://github.com/umlx5h/zsh-manpage-completion-generator appears to adapt this to ZSH. Have yet to try through
derriz · 42m ago
I feel that the ergonomics of bash completion took a hit as the configurations got “smarter” and “helpfully” started blocking file or directory name completion if it thinks it wouldn’t be appropriate to have a file name at the current cursor position. Instead of blocking, the default should always be to fall back to filename completion.

Sometimes I’m close to disabling/uninstalling all completion scripts out of irritation as decades of muscle memory are frustrated by this behavior.

It’s like that bad/annoying UX with text fields where the UI is constantly fighting against you in order prevent you from producing “illegal” intermediate input - e.g. let me paste the clipboard here goddammit - I know what I’m doing - I’ll correct it.

IshKebab · 4m ago
I agree that is annoying. It's waaay less confusing to complete a filename and then get an error from the actual program than it is for just ...nothing to happen so you get confused and have to `ls` to check if the file actually exists and it does and so you think tab completion is broken for some reason and you copy & paste the filename and then finally you get the error that explains what is going on.

It should at least print a message like "file foo.exe exists but it isn't executable".

compressedgas · 6m ago
As it works as desired after running: complete -r; there is something broken about the bash-completion script.
cryptoz · 35m ago
I have come to absolutely despise web form inputs with front end email validators that are broken. Input field hints to type your email, so you start typing. As soon as you type the first letter it goes red and says “error!!! Invalid email!”

Unbelievably frustrating.

lihaoyi · 1h ago
I wrote this, hope everyone finds it as interesting reading this as I did figuring this out for the first time!
tetha · 1h ago
It's a good first dive into zsh completion. The whole thing is quite the large system to wrap ones head around it and I'm still somewhat struggling.

But at work, I've been slowly adding auto completion to our ansible wrapper scripts, like explanations which playbooks to use when, smart `-l` completion based off a possibly selected playbook (so, if the playbook is postgres.yml, it doesn't suggest mariadb groups), tag autocompletion (with a few, admittedly, hardcoded explanations how these tags should be used) and such.

It's somewhat of a friday-afternoon struggle project, but it's making the big ansible project pretty approachable to use.

oezi · 1h ago
Thanks, for the interesting read.
oezi · 1h ago
Isn't there a standard flag which programs can implement to avoid writing this bash script?

Ideally this could all be part of a library such as argparse for typical cases, right?

vcdimension · 1h ago
In zsh you can use the _gnu_generic function for simple completion of commands with a --help flag. Just put a line like this somewhere in your startup file: compdef _gnu_generic <CMD>
cb321 · 1h ago
_gnu_generic is fantastic. I use it all the time. If your CLI toolkit emits colorized help, but skips said colorization if NO_COLOR[1] is set then you can prefix _gnu_generic with NO_COLOR=1 as in https://github.com/c-blake/cligen/wiki/Zsh-completion-for-cl... for the cligen Nim CLI toolkit.

[^1]: https://no-color.org/

mnahkies · 19m ago
I've wondered this as well - it would sure be nice if there was a standard --completion or something that common argument parsing libraries could automatically implement for us (much like they often implement automatic help text)
vcdimension · 1h ago
Here's another tutorial for creating zsh completers using the built-in functions: https://github.com/vapniks/zsh-completions/blob/master/zsh-c...
bravesoul2 · 1h ago
Do many people do bash on osx or zsh on Linux, and would this make much of a difference?
homebrewer · 1h ago
I don't know about "use" — luckily, there's no opt-out telemetry — but enough of "enthusiast distribution" users who have also opted in (very biased sample) have explicitly installed zsh (not necessarily run it)

https://pkgstats.archlinux.de/compare/packages#packages=bash...

OTOH, it's only 4-7% on Debian (also opt-in):

https://qa.debian.org/popcon.php?package=zsh

bravesoul2 · 24m ago
I use zsh at work and bash at home. I am such an unsophisticated user that I haven't noticed a real difference! Other than I can install ohmyzsh on zsh.
camdroidw · 1h ago
Why doesn't someone (not me) just build a basic DSL and a transpiler that does this?
cb321 · 42m ago
The answer to your question is that command-lines have a much larger diversity of syntax (even to get help!) than most people realize. Folks have their 30..60 commands they run frequently and don't run into many or conveniently forget/neglect older ones like `gcc` or `tar` or `dd`. Many people (not saying you specifically) do not even realize that double-dash long options are a GNU extension never standardized or that Python toolkits typically allow --my-opt for --my-option abbreviations, just to name a couple of the dozen variations (space or '=', or ':' or '/' or any of the above or etc., etc.). There are probably hundreds if not thousands of syntax possibilities, but people often act like there is only one.

As an example of diversity estimation that you can try at home, a couple of times I have run every single command in my command search PATH with --help </dev/null >/tmp/help.$c 2>&1 . Caution - be careful if you do this! Have backups/checksums of everything important and run as an unprivileged user. I always have to kill off several processes that just hang doing something or otherwise manually intervene. Anyway, this alone suggests data collection of help text is not a trivial problem.

Beyond data collection, many commands did not/do not use CLI toolkits at all. Their commands may have even less regular syntax. Freeform help makes it harder to produce a regular help syntax to convert into the interpreter needed by a completion system. That said, as elsethread commented for some toolkits the Zsh _gnu_generic works great! It essentially IS the "automagic" system you might want, just for a highly restricted circumstance.

Any CLI toolkit itself does have the data, by necessity. So, if the CLI framework supports the 2 or 3 common shells there is no need for a translator exactly. You just need a code generator. There is a stab at an auto-generation framework from said data for the Nim CLI toolkit, cligen, over at:

https://github.com/c-blake/cligen/blob/master/util/complgen....

but it only works for Zsh right now. Anyway, I don't think perfect should be the enemy of the good or anything like that, but you seemed to ask an earnest "why" question and these are some of the complexities.

wiseowise · 1h ago
Shell syntax is the exact reason why we've needed LLMs in the first place.
camdroidw · 1h ago
You mean Unix shell syntax. Powershell has got this absolutely right ,and only this (which is probably still a 50% of what a shell is)