How do I get bash completion to work with aliases?
Asked Answered
I

15

217

Case in point:

I'm a on mac with bash v3.2.17, I'm using git installed via macports with the bash_completion variant.

When I type git checkout m<tab>. for example, I get it completed to master.

However, I've got an alias to git checkout, gco. When I type gco m<tab>, I don't get the branch name autocompleted.

Ideally I'd like autocompletion to just magically work for all my aliases. Is it possible? Failing that, I'd like to manually customize it for each alias. So, how do I go about either?

Idler answered 5/12, 2008 at 5:17 Comment(3)
complete -o default -o nospace -F doesn't work nowadaysDannydannye
Questions with more upvotes than the top answer often imply great feature requestsGreenbrier
Another answer from superuser as someone pointed out to me that my question there was a dupe of this one. superuser.com/questions/436314/…Fugacious
M
198

As stated in the comments above,

complete -o default -o nospace -F _git_checkout gco

will no longer work. However, there's a __git_complete function in git-completion.bash which can be used to set up completion for aliases like so:

__git_complete gco _git_checkout
Marchpast answered 21/2, 2013 at 18:25 Comment(14)
If you use global alias "g" for git, you could also add __git_complete g __git_main to get code completition working on all git commands.Waiter
^^ For those new to git/shell/bash. The above comment refers to a global shell alias, not a native git alias.Sarita
What if git was aliased to g and checkout is alised to co so I do g co <tab> but doesn't autocomplete as well.Vertex
Where should I put this?Linked
Finally figured out how to do this properly! Step 1) Copy git-completion.bash from <your git install folder>/etc/bash-completion.d/ to ~/.git-completion.bash Step 2) add source ~/.git-completion.bash to your .bash_profile Step 3) Add __git_complete gco _git_checkout anywhere after the above line in your .bash_profile. Step 4) Reboot shell and enjoy your alias auto completion! :)Mathre
@Linked I placed it directly below the source ~/.git_completion.sh in my ~/.bash_profileAlmire
-1 This answer seems to assume that you'll have read and understood some older, incorrect answer (but which one?), and therefore know in what file and where in that file this line of text belongs. Try to only assume knowledge of things established in the question.Cwmbran
There is a nice Gist that provides all information needed to set it upStane
To check the number of leading underscores for commands other than __git_main and _git_checkout (such as _git_merge and _git_pull), search in the .git-completion.bash script. All of the common commands (the ones I alias) have only one leading underscore.Xylol
What if the alias you want completion for has nothing to do with git; what if you don't have git installed on your system/server?Crud
This is not an actual answer because it doesn't actually tell you what to do so much as just provide some information. Very incomplete.Stakeout
For people who are looking for the completion script: find / -name "git-completion.bash".Kidd
@OndrejMachulda +1 Grazie mille! 👍 Your solution worked!Masochism
@OndrejMachulda But now I get this error with parallel_env: "env_parallel: Error: Your environment is too big."Masochism
T
60

I ran into this problem as well and came up with this code snippet. This will automatically give you completion for all aliases. Run it after declaring all (or any) alias.

# wrap_alias takes three arguments:
# $1: The name of the alias
# $2: The command used in the alias
# $3: The arguments in the alias all in one string
# Generate a wrapper completion function (completer) for an alias
# based on the command and the given arguments, if there is a
# completer for the command, and set the wrapper as the completer for
# the alias.
function wrap_alias() {
  [[ "$#" == 3 ]] || return 1

  local alias_name="$1"
  local aliased_command="$2"
  local alias_arguments="$3"
  local num_alias_arguments=$(echo "$alias_arguments" | wc -w)

  # The completion currently being used for the aliased command.
  local completion=$(complete -p $aliased_command 2> /dev/null)

  # Only a completer based on a function can be wrapped so look for -F
  # in the current completion. This check will also catch commands
  # with no completer for which $completion will be empty.
  echo $completion | grep -q -- -F || return 0

  local namespace=alias_completion::

  # Extract the name of the completion function from a string that
  # looks like: something -F function_name something
  # First strip the beginning of the string up to the function name by
  # removing "* -F " from the front.
  local completion_function=${completion##* -F }
  # Then strip " *" from the end, leaving only the function name.
  completion_function=${completion_function%% *}

  # Try to prevent an infinite loop by not wrapping a function
  # generated by this function. This can happen when the user runs
  # this twice for an alias like ls='ls --color=auto' or alias l='ls'
  # and alias ls='l foo'
  [[ "${completion_function#$namespace}" != $completion_function ]] && return 0

  local wrapper_name="${namespace}${alias_name}"

  eval "
function ${wrapper_name}() {
  let COMP_CWORD+=$num_alias_arguments
  args=( \"${alias_arguments}\" )
  COMP_WORDS=( $aliased_command \${args[@]} \${COMP_WORDS[@]:1} )
  $completion_function
  }
"

  # To create the new completion we use the old one with two
  # replacements:
  # 1) Replace the function with the wrapper.
  local new_completion=${completion/-F * /-F $wrapper_name }
  # 2) Replace the command being completed with the alias.
  new_completion="${new_completion% *} $alias_name"

  eval "$new_completion"
}

# For each defined alias, extract the necessary elements and use them
# to call wrap_alias.
eval "$(alias -p | sed -e 's/alias \([^=][^=]*\)='\''\([^ ][^ ]*\) *\(.*\)'\''/wrap_alias \1 \2 '\''\3'\'' /')"

unset wrap_alias
Tepper answered 24/11, 2009 at 21:52 Comment(17)
the line let COMP_CWORD+=$num_alias_arguments did not work on Mac OS X for some reason. Replacing it with ((COMP_CWORD+=$num_alias_arguments)) fixed it thoughLeucocratic
Wow, that's awesome -- thanks! wrap_alias chokes on double quotes in the alias definition, and I guess it doesn't make much sense for multi-command aliases (alias 'foo=bar; baz'), so I'm putting an extra | grep -v '[";|&]' after the alias -p. Also, it gets a bit slow for hundreds of alias definitions, but I'm happy to confirm that using echo instead of eval and piping the output into a cache file (which can then be eval'ed in one go) works fine and is super-fast.Awad
Another hint: wrap_alias requires the completions to be set up, so I had to move source /etc/bash_completion in front of the wrap_alias code.Awad
I think there needs to be \" around \${COMP_WORDS[@]:1} to make this work with blank spaces in arguments. (But I don't really know what I'm doing, so I'm not comfortable editing the answer at the moment.)Awad
This worked for me on OS X 10.7.2 after changing the line let COMP_CWORD+=$num_alias_arguments to let \"COMP_CWORD+=$num_alias_arguments\".Coarsen
Did the following to wrap only specified aliases: function wrap_aliases { for cmdname in "$@"; do cmdname="$(alias $cmdname | sed 's/sudo //')"; eval "$(echo $cmdname | sed -e 's/alias ([^=][^=]*)='\''([^ ][^ ]*) *(.*)'\''/wrap_alias \1 \2 '\''\3'\'' /')"; done }Lepore
OSX 10.7.2 here, didn't work with either @irh's or MarioFernandez's tips. Any ideas what could be wrong or how I can debug this?Denotation
I had to exclude the following aliases: [[ "_longopt" = $completion_function ]] && return 0Entrant
Any way I can make this work with sudo? I added alias agi='sudo apt-get install' but agi+emac<TAB> doesn't do anything.Denotation
@obvio171 You would need to have a completion function for sudo.Tepper
@HeskyFisher could you give some more detail, please? Sorry, I'm not too experienced with bash.Denotation
@obvio171 I suggest asking this as a stack overflow question.Tepper
@obvio171: From Graham's reply: Add | sed 's/sudo //' before the sed -e 's/alias' and it should automatically bring in the completion function for the command after sudo.Larrisa
If anyone is having trouble with some aliases not getting the correct completion, please see my comment hereLarrisa
See the updated version of this script at superuser.com/a/437508/102281 (for example, I added support for COMP_LINE and COMP_POINT which are required for some git completions).Simmon
I'm sorry, but non-threaded comments are the NOT proper place collaborate on code. At a minimum this should be a gist. But, since gists allow neither issues to be created or pull requests to be offered, this really should be an actual git repo. (even the link John Mellor shared is full comment coders)Gloam
Yikes, cool script, but it fails when it's a non-trivial alias which contains tokens such as (($UID)) ;) ... still, very cool. I'm using grep -v to get rid of the few offending aliases and added that to the pipe sequence right after alias -p ...Suffragette
I
20

Ideally I'd like autocompletion to just magically work for all my aliases. Is it possible?

Yes, it is possible with the complete-alias project (on Linux). Support for Mac is experimental but users have reported success.

Introrse answered 24/12, 2016 at 7:15 Comment(1)
thanks a lot, this is so much better than figuring out how every utility in the world implements bash completion.Wilk
H
19

In git-completion.bash there is a line:

complete -o default -o nospace -F _git git

Looking at that line (and the _git function) you can add this line to your .bash_profile:

complete -o default -o nospace -F _git_checkout gco
Heterophyte answered 5/12, 2008 at 5:55 Comment(4)
some of the git* bash functions no longer work using this methodTabulator
Yes this used to work great until something changed in git_completion.bash... Now it works with the full command but not with the alias.Hilde
See the end of this page for answers that work in modern git.Dannydannye
this works well - added this to my .bash_profile, and works fine with and without aliases so far: github.com/larrybotha/dotfiles/blob/master/…Matins
R
15

I have aliased g='git', and combined with my git aliases I type things like

$ g co <branchname>

The simpler fix for my specific use case was to add a single line to git-completion.

Right below this line:

__git_complete git _git

I added this line to handle my single 'g' alias:

__git_complete g _git
Rame answered 22/5, 2012 at 18:7 Comment(2)
(I'm using Cygwin.) I couldn't find the file git-completion or that line in /etc/bash_completion.d/git, but I added complete -o default -o nospace -F _git g after my alias in .bash_aliases and it worked!Marjorymarjy
Beware, that if you edit a file in /etc/bash-completion.d/ or newly in /usr/share/bash-completion/, you will lose your changes whenever that file gets updated using your package manager.Utoaztecan
U
7

One more option is to use ~/.bash_completion file. To create the gco alias for git checkout just put this in there:

_xfunc git __git_complete gco _git_checkout

Then in ~/.bashrc you have to put just the alias itself:

alias gco='git checkout'

Two lines. That's it.

Explanation:

The ~/bash_completion gets sourced at the end of the main bash_completion script. In gentoo I found the main script in /usr/share/bash-completion/bash_completion.

The _xfunc git bit takes care of sourcing the git-completion file for you so you don't need to put anything else in ~/.bashrc.

The accepted answer require you to copy .git-completion.sh and source it from your ~/.bashrc file which I find lame.


PS: I'm still trying to figure out how not to source the whole git-completion script into my bash environment. Please comment or edit if you find a way.

Utoaztecan answered 26/2, 2016 at 18:52 Comment(3)
Why is _xfunc git required?Bobbysoxer
@TomHale I tried to improve the answer. Rather than doing source ~/.git-completion.sh I let _xfunc do it for me. It just feels nicer and cleaner to do it solely in ~/.bash_completion. Without the _xfunc (or the sourcing) the __git_complete function doesn't exist.Utoaztecan
No need for the ~/.bash_completion file - the _xfunc line works for me in .bashrc.Bobbysoxer
S
5

You could also try using Git aliases. For example, in my ~/.gitconfig file, I have a section that looks like this:

[alias]
        co = checkout

So you could type git co m<TAB>, and that should expand to git co master, which is the git checkout command.

Shuffle answered 5/12, 2008 at 15:39 Comment(0)
S
5

This forum page shows a solution.

Put these lines into your .bashrc or .bash_profile:

# Author.: Ole J
# Date...: 23.03.2008
# License: Whatever

# Wraps a completion function
# make-completion-wrapper <actual completion function> <name of new func.>
#                         <command name> <list supplied arguments>
# eg.
#   alias agi='apt-get install'
#   make-completion-wrapper _apt_get _apt_get_install apt-get install
# defines a function called _apt_get_install (that's $2) that will complete
# the 'agi' alias. (complete -F _apt_get_install agi)
#
function make-completion-wrapper () {
    local function_name="$2"
    local arg_count=$(($#-3))
    local comp_function_name="$1"
    shift 2
    local function="
function $function_name {
    ((COMP_CWORD+=$arg_count))
    COMP_WORDS=( "$@" \${COMP_WORDS[@]:1} )
    "$comp_function_name"
    return 0
}"
    eval "$function"
}

# and now the commands that are specific to this SO question

alias gco='git checkout'

# we create a _git_checkout_mine function that will do the completion for "gco"
# using the completion function "_git"
make-completion-wrapper _git _git_checkout_mine git checkout

# we tell bash to actually use _git_checkout_mine to complete "gco"
complete -o bashdefault -o default -o nospace -F _git_checkout_mine gco

This solution is similar to balshetzer's script, but only this one actually works for me. (balshetzer's script had problems with some of my aliases.)

Superload answered 16/1, 2010 at 18:19 Comment(2)
;This almost works -- I get a couple of errors, but the completion goes through. Anything else I can do? -bash: eval: line 28: unexpected EOF while looking for matching ''' -bash: eval: line 29: syntax error: unexpected end of fileShiny
@Shiny I can see quoting issues above... the " quotes inside the function string should be quoted as \". This probably eats one of your ' quotes somewhere along the line.Bobbysoxer
S
4

You just have to find the complete command and duplicate the line having the alias name instead.

I have alias d-m="docker-machine". In words, d-m shall be the alias for docker-machine.

So on Mac (via brew), the completion files are in cd `brew --prefix`/etc/bash_completion.d/.
For my case I edited the file called docker-machine.
All the way at the bottom there was:

complete -F _docker_machine docker-machine

So I just added another line, with my alias:

complete -F _docker_machine docker-machine
complete -F _docker_machine d-m
Steamheated answered 7/11, 2016 at 11:7 Comment(1)
This is the best solution for simple (one to one) aliases, like docker aliased to d. Although for the example in the question, git checkout aliased to gco is more complex.Coconut
C
2

First, look up the original completion command. Example:

$ complete | grep git

complete -o bashdefault -o default -o nospace -F __git_wrap__git_main git

Now add these to your startup script (e.g. ~/.bashrc):

# copy the original statement, but replace the last command (git) with your alias (g)
complete -o bashdefault -o default -o nospace -F __git_wrap__git_main g

# load dynamically loaded completion functions (may not be required)
_completion_loader git

The _completion_loader line may not be required. But for some situations, the completion function is only loaded dynamically after you type the command and press TAB the first time. So if you haven't used the original command, and try the alias + TAB, you may get an error like "bash: completion: function '_docker' not found".

Coconut answered 26/7, 2018 at 22:50 Comment(1)
The _completion_loader line is always required if the completion script is located in /usr/share/bash-completion/completions/. For backwards compatibility scripts that are located in /etc/bash_completion.d will still be loaded when bash_completion is being loaded. See: github bash-completion commitVertigo
W
2

There are a lot of answers to this question and like myself I bet a lot of confused readers. For my case I had also had the requirement to have my dotfiles work on multiple platforms with different versions of Git. I also don't alias g=git but instead have g defined as a function.

To accomplish this I had to slap together different answers here into one solution. Although this reiterates the answers already I thought someone in my boat might find this compilation useful as I would have when I first came to this question.

This assumes older and newer Git completion, Ubuntu defaults, and brew install git on MacOS. In the later case the brew installed completions were not being processed by bash (something I will diagnose later).

# Alias g to git

g() {
  if [[ $# > 0 ]]; then
    git "$@"
  else
    git status -sb
  fi
}

# Preload git completion in Ubuntu which is normally lazy loaded but we need
# the __git_wrap__git_main function available for our completion.
if [[ -e /usr/share/bash-completion/completions/git ]]; then
  source /usr/share/bash-completion/completions/git
elif [[ -e /usr/local/etc/bash_completion.d/git-completion.bash ]]; then
  source /usr/local/etc/bash_completion.d/git-completion.bash
fi

if command_exists __git_complete; then
  __git_complete g _git
elif command_exists __git_wrap__git_main; then
  complete -o bashdefault -o default -o nospace -F __git_wrap__git_main g
fi
Wundt answered 19/3, 2019 at 12:35 Comment(0)
B
0

Felipe Contreras, who is already quite active for Git completion features (see Zsh completion in Git 2.30) proposes (for -- possibly -- Git 2.31, Q1 2021) a public function which will help with alias autocompletion.

His proposal:

Back in 2012 I argued for the introduction of a helper that would allow users to specify aliases like:

git_complete gf git_fetch

Back then there was pushback because there was no clear guideline for public functions (git_complete vs _git_complete vs _GIT_complete), and some aliases didn't actually work.

Fast-forward to 2020 and there's still no guideline for public functions, and those aliases still don't work (even though I sent the fixes).

This has not prevented people from using this function that is clearly needed to setup custom aliases (this page), and in fact it's the recommended way.

But it is cumbersome that the user must type:

__git_complete gf _git_fetch

Or worse:

__git_complete gk __gitk_main

8 years is more than enough time to stop waiting for the perfect to come; let's define a public function (with the same name) that is actually user-friendly:

__git_complete gf git_fetch
__git_complete gk gitk

While also maintaining backwards compatibility.

The logic is:

  1. If $2 exists, use it directly
  2. If not, check if __$2_main exists
  3. If not, check if _$2 exists
  4. If not, fail
Bertha answered 29/12, 2020 at 1:13 Comment(0)
M
-1

If you use alias g='git', i add this line of code in .bash_aliases

complete -o default -o nospace -F _git g
Migrant answered 17/4, 2019 at 18:55 Comment(0)
B
-1

You can bind Tab to alias-expand-line and complete (its default action) in ~/.inputrc. To do that you first need to bind each action to a key and then chain them together thus:

"\M-z":alias-expand-line
"\M-x":complete
TAB:"\M-z\M-x"

You can use whatever key combinations you like, I use the Meta one because it is free. See man 3 readline for more information.

Now, if you open a new terminal and type the alias:

gco m<TAB>

The line will be transformed into

git checkout master

Of course, Tab will still work as usual even if no alias is involved.

Bebe answered 10/3, 2021 at 12:38 Comment(0)
E
-2

I know this question is about bash but in case you're using zsh this change to @hesky-fisher answer will fix it.

From

# For each defined alias, extract the necessary elements and use them
# to call wrap_alias.
eval "$(alias -p | sed -e 's/alias \([^=][^=]*\)='\''\([^ ][^ ]*\) *\(.*\)'\''/wrap_alias \1 \2 '\''\3'\'' /')"

to:

# For each defined alias, extract the necessary elements and use them
# to call wrap_alias.
eval "$(alias | sd '^([^=]+)=(.+)' 'wrap_alias $1=$2')"

You need to have sd (this can be replaced by sed but sd is much better in my opinion)

This is done because zsh doesn't have alias -p and alias output in zsh doesn't output

alias <something>=<value>

like in bash, but rather

<something>=<value>

BTW, I put the init code in the .zlogin file

Egor answered 17/4, 2022 at 17:40 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.