1

When using tab completion in bash, the $_ variable is altered:

$ mkdir test
$ cd <TAB><TAB> $_
bash: cd: -d: invalid option
cd: usage: cd [-L|[-P [-e]]] [dir]

(The <TAB><TAB> will list all files in current directory, but I don't end up using the output and write $_ instead. The command executed in this line will just be cd $_.)

Expected behavior would be to change into ./test.

How can I prevent bash completion from altering $_?

kurtm
  • 7,055
f0i
  • 35
  • 3
    bash is pretty limited with its autocompletion options: I am not sure if that is possible. If you need more powerfull and customisable autocompletion in interactive session I suggest moving to zsh if that is possible. – IBr Oct 12 '13 at 09:10
  • 4
    This is almost certainly due to programmable completion, the default (readline) completion doesn't do this. Please indicate which version of bash, which completion package and/or the output of complete -p. – mr.spuratic Oct 12 '13 at 09:55
  • cd <TAB><TAB> should give you all possible completions (the 2nd tab),how are you entering $_? – terdon Oct 12 '13 at 14:03
  • @mr.spuratic Bash version is GNU bash is version 4.2.45(2)-release on x86_64. The output of complete -p is here: https://js.f0i.de/complete-p.html – f0i Oct 13 '13 at 14:53

1 Answers1

2

You're using the bash-completion package (or a derivative). For each argument completion of the cd command (as shown by the complete -p output):

complete -o nospace -F _cd cd

the _cd function is invoked to determine the completions (slightly edited for brevity):

_cd()
{
    local cur prev words cword
    _init_completion || return

    local IFS=$'\n' i j k

    compopt -o filenames

    if [[ -z "${CDPATH:-}" || "$cur" == ?(.)?(.)/* ]]; then
        _filedir -d
        return 0
    fi
    ....

So for example, when you complete on a directory with no CDPATH set, the last-seen argument to a command seen is -d, and this is placed automatically in _. There are several other code paths in that function with similar side-effects.

Since _ is a bash internal, a conventional save/restore (as for IFS) won't work as hoped. You could do it with a little trickery:

_cd()
{
    local save_="$_"
    ...
    : $save_
    return 0

You must save _ immediately on entry to a function, : is the null command, which does nothing per-se but has the usual side-effects of a command, such as setting _. This restore operation will be required for each return point of each potentially disruptive function. There's a subtlety here too: normally _ is set immediately after a return from a function (to the last argument of the function call, as expected), which would make this method ineffective. But, this doesn't happen when a completion function is invoked, since it's not explicit invoked. I don't consider this to be very robust...

(I prefer history expansion, and stick to !$ which doesn't suffer this problem.)

mr.spuratic
  • 9,901