109

I have a Bash tab-completion script for Apache's Hadoop. Normally, I use zsh as my day-to-day shell. It tends to be pretty bash-like when I need it to be, but it looks like the tab-completion systems are radically different between them. Is there a simple way to "convert" the existing bash-tab-completion definitions to work in zsh? I don't want to invest a ton of time in this, but if it's easy I'd save a moderate amount of effort.

codeforester
  • 39,467
  • 16
  • 112
  • 140
Coderer
  • 25,844
  • 28
  • 99
  • 154

6 Answers6

183
autoload bashcompinit
bashcompinit
source /path/to/your/bash_completion_file
freestyler
  • 5,224
  • 2
  • 32
  • 39
  • 11
    This is the better answer since it tells you *how* to use bashcompinit. Nice job. – pyrospade Jul 10 '14 at 19:54
  • 6
    Thanks, great answer, unfortunately this doesn't work if the script uses _init_completion bash command. – 0fnt Aug 16 '14 at 18:16
  • 6
    This didn't work for me. I was still getting `complete:13: command not found: compdef` error. – Jatin Kumar Jan 09 '15 at 04:41
  • 1
    If it doesn't work, check the script (doh :)) - it might have a `if is-bash` check at the top. – olejorgenb Sep 02 '15 at 13:27
  • This doesn't work for some scripts using `_get_comp_words_by_ref` which is not defined by `bashcompinit`. Most completions don't use this function though. – Franklin Yu Jan 22 '20 at 01:57
  • 1
    @FranklinYu, were u able to resolve it,I am also facing the same issue and my orignal question was closed in favour of this question :( –  May 03 '20 at 05:01
  • @es-enthu Nope, sorry. I end up jumping back to bash when running that command. If it is frequent enough I may want to write it myself. – Franklin Yu May 04 '20 at 00:26
56
autoload -U +X compinit && compinit
autoload -U +X bashcompinit && bashcompinit
source /path/to/your/bash_completion_script

I am running zsh zsh 5.0.2 (x86_64-apple-darwin13.0) without any ~/.zshrc and the above sequence worked in a freshly spawned zsh shell.

Thanks to git-completion.bash script for the hint :D


Read-on for more details on above 3 lines:

Bash has awesome in built auto completion support but the bash autocomplete scripts don't work directly zsh as zsh environment doesn't have the essential bash autocomplete helper functions like compgen, complete. It does so in an effort to keep zsh session fast.

These days zsh is shipped with appropriate completion scripts like compinit and bashcompinit which have the required functions to support bash autocomplete scripts.

autoload <func_name>: Note that autoload is defined in zsh and not bash. autoload looks for a file named in the directory paths returned by fpath command and marks a function to load the same when it is first invoked.

  • -U: Ignore any aliases when loading a function like compinit or bashcompinit
  • +X: Just load the named function fow now and don't execute it

For example on my system echo $fpath returns /usr/share/zsh/site-functions and /usr/share/zsh/5.0.5/functions and both compinit and bashcompinit are available at /usr/share/zsh/5.0.5/functions.

Also for most people may be only autoload -U +X bashcompinit && bashcompinit is required because some other script like git autocomplete or their own ~/.zshrc may be doing autoload -U +X compinit && compinit, but it's safe to just run both of them.

Jatin Kumar
  • 2,635
  • 9
  • 36
  • 46
  • What are `-U`and `+X` for? – jmk Mar 23 '15 at 15:03
  • 1
    Updated answer with more details. – Jatin Kumar Jul 07 '15 at 07:28
  • 6
    This still makes little sense; the manpage describes the point of `+X foo` as doing the same auto-loading just invoking `foo` does, but without immediately executing it … so `autoload +X foo && foo` seems completely redundant. Can you explain why you chose this pattern? – ELLIOTTCABLE Oct 13 '15 at 20:59
  • 2
    @ELLIOTTCABLE The point of doing `autoload -U +X bashcompinit && bashcompinit` is that (due to the `&&`) it will execute `bashcompinit` IFF the module is actually found. Normal `autoload foo` always returns true. Later, when you try to run `foo`, _then_ zsh finally tells you "function definition file not found". With `autoload +X` you get the error immediately. – FeRD Sep 16 '19 at 21:52
  • 2
    The man page is still somewhat unclear on that point, it seems to me: `autoload +X` loads the function without executing it — which is _more_ than `autoload` does, without `+X`. Without `+X`, autoload **doesn't even** load the function; It merely marks the name as referencing a function to be loaded later. The actual loading isn't attempted until the function is first invoked. If you want to determine whether the named function _successfully_ loads, you need to use `+X` to perform the loading step immediately. – FeRD Sep 16 '19 at 22:00
  • 1
    @FeRD I have refused to switch from `bash` (which I have used since the late 90s) because I didn't understand details like what you just explained in our comments. Thank you! I actually get it and can make decades worth of my tools work in `zsh` with this new understanding. ☮️❤️ – Bruno Bronosky Jun 10 '22 at 03:14
  • 1
    @BrunoBronosky Zsh can be... challenging. Its documentation is nebulous, and despite there being so much of it, it still feels like so many features are... if they're not _undocumented_, then their documentation is extremely well hidden. And even the documentation that does exist is a mixed bag. Sometimes I have a hard time deciding whether what I'm reading is a valiant attempt at documenting a confusing feature, or a feature being documented confusingly. (**#WhyNotBoth?**) – FeRD Jun 10 '22 at 07:48
  • 1
    Here's one of my favorites, on how globbing works when you use a range qualifier on a timestamp match, and specify the cutoff distance in a given time unit: "Any fractional part of the difference between the access time and the current part in the appropriate units is ignored in the comparison. For instance, `echo *(ah-5)` would echo files accessed within the last five hours, while `echo *(ah+5)` would echo files accessed at least six hours ago, as times strictly between five and six hours are treated as five hours." ...Guh? I'm going with "definitely both", on that one. – FeRD Jun 10 '22 at 07:54
44

From this page (dated 2010/01/05):

Zsh can handle bash completions functions. The latest development version of zsh has a function bashcompinit, that when run will allow zsh to read bash completion specifications and functions. This is documented in the zshcompsys man page. To use it all you need to do is run bashcompinit at any time after compinit. It will define complete and compgen functions corresponding to the bash builtins.

Noelle L.
  • 100
  • 6
Dennis Williamson
  • 346,391
  • 90
  • 374
  • 439
  • The good news: I think this should work, so thanks a lot. The bad news: for some reason, the system I'm trying to accomplish this on doesn't seem to load /etc/zshrc when I log in via SSH. I don't know enough about the login process to tell why this is the case. Maybe time to ask another question... – Coderer Jul 15 '10 at 21:01
  • 1
    @Coderer: First of all, you say "/etc/zshrc". On my system, it's `/etc/zsh/zshrc`. Also, check the following files in reverse order (listed in the order they are sourced during zsh startup): `/etc/zsh/zshenv`, `$ZDOTDIR/.zshenv`, `/etc/zsh/zprofile` and `$ZDOTDIR/.zprofile` (probably `~/.zprofile`) to see if the `RCS` variable is unset. If it is, that would prevent the subsequent files after the file in which it's unset from being sourced. Finally, check the shell for the user in `/etc/passwd` and make sure it's `zsh` and make sure it doesn't have a `-f` argument. – Dennis Williamson Jul 16 '10 at 00:18
  • Thanks for all the feedback -- I resorted to the #1 classic programmer hack, "stdout debugging" (echo statements in each startup script) and found the issue. For some reason, our /etc/zprofile was sourcing the various /etc/profile.d scripts, which were then getting sourced *again* in /etc/zshrc. Simply moving the autoload statements to the top of /etc/zprofile fixed the problem. – Coderer Jul 16 '10 at 19:17
5

For zsh use:

  • compdef
  • compadd

My example:

# bash completion for bxrun (/home/ecuomo/projects/bashx/bxrun)
_bxrun_methods() {
    grep "^\s*\(function\s\+\)\?__.\+()\s*{.*$" "${1}" | while read line ; do
        echo "$line" | sed "s/()\s*{.*//g" | sed "s/\s*\(function\s\+\)\?__//g"
    done
}
_bxrun_lst() {
    if [ -d "/home/ecuomo/projects/bashx/src/actions" ]; then
        for f in /home/ecuomo/projects/bashx/src/actions/* ; do
            if [ -f "${f}" ]; then
                basename "${f}" | sed 's/\..*$//g'
            fi
        done
    fi
    _bxrun_methods "/home/ecuomo/projects/bashx/bxrun"
    _bxrun_methods "/home/ecuomo/projects/bashx/src/bashx.sh"
}
_bxrun() {
    local cur
    COMPREPLY=()
    cur=${COMP_WORDS[COMP_CWORD]}
    COMPREPLY=( $( compgen -W '$( _bxrun_lst )' -- $cur  ) )
}
_bxrun_zsh() {
    compadd `_bxrun_lst`
}
if type complete >/dev/null 2>/dev/null; then
    # bash
    complete -F _bxrun bxrun
else if type compdef >/dev/null 2>/dev/null; then
    # zsh
    compdef _bxrun_zsh bxrun
fi; fi

Source: My code https://github.com/reduardo7/bashx

Eduardo Cuomo
  • 17,828
  • 6
  • 117
  • 94
5

I am running Antigen as a Oh-My-Zsh plugin manager. I had a few bash completion scripts written by coworkers that I wanted to load into Zsh with a simple source /path/to/completion.

I had some trouble, because it seems like either Antigen or OMZ (hard to tell) concern themselves with only loading completion scripts from their plugins. I finally got around this by autoloading bashcompinit and compinit after antigen apply. Simply autoloading bashcompinit wasn't enough.

source ~/.antigen/antigen.zsh
antigen use oh-my-zsh
antigen apply

autoload -U +X compinit && compinit
autoload -U +X bashcompinit && bashcompinit

source /path/to/bash_completion

Antigen creates its .zcompdump file at $ANTIGEN_COMPDUMP which for me was ~/.antigen/.zcompdump

The re-invoke of compinit and bashcompinit create a second .zcompdump at $HOME/.zcompdump

That seems to all work out, because I am able to use the completions set up by /path/to/bash_completion. I've deleted both .zcompdump files a few times to make sure they're regenerated and seems to work.

I've had to rm the .zcompdump files a few times after a machine reboot because of errors thrown when trying to tab complete, but I'm unsure if that's due to this set up or something else. rm ~/.zcompdump && rm $ANTIGEN_COMPDUMP and a new shell fixes that for me.

Versions used at time of writing:

Antigen = v2.2.3 = d3d4ee0
Oh-my-zsh = c3b072e
Zsh = 5.3
4

@JatinKumar's answer got me on the right track, but I had to use complete instead of source. So all together:

autoload -Uz compinit && compinit
autoload -U +X bashcompinit && bashcompinit

complete -C /usr/local/bin/terraform terraform
complete -C /usr/local/aws/bin/aws_completer aws
complete -C /usr/local/bin/az az
jschmitter
  • 1,669
  • 19
  • 29