1

I wrote a function to get my hosts from ssh config without getting wildcard hosts:

sshConfAutoComplete() { 
   cat ~/.ssh/config | \
   grep 'host '      | \
   sed '
     s#.*\*##g; 
     s#host ##g
   '
}

Output:

pi
lynx
iridium
batchelorantor
rasp

The output is correct, so I added this function to: /usr/local/etc/bash_completion.d/ssh

Like this:

sshConfAutoComplete() { 
   cat ~/.ssh/config | \
   grep 'host '      | \
   sed '
     s#.*\*##g; 
     s#host ##g
   '
}
complete -F sshConfAutoComplete ssh

Then I added this source: . /usr/local/etc/bash_completion.d/ssh to ~/.bash_profile

Sourced ~/bash_profile, then when I type ssh <tab> the following comes up:

pi
lynx
iridium
batchelorantor
rasp

If I type ssh ly <tab> it doesn't autocomplete to lynx, it just outputs the above.

How do I fix this?

Nickotine
  • 467

2 Answers2

1

In man bash the section headed Programmable Completion explains how the function called by -F needs to provide the matching results in the array variable COMPREPLY. Typically the full list as provided by your sed commands are passed to the command compgen -W for it to try matching the words against the target word, which is arg 2 ($2) of your function. Simplifying a little we get:

sshConfAutoComplete() { 
   COMPREPLY=( $(compgen -W \
    "$(sed -n ' /host /{s#.*\*##g; s#host ##g; p} ' ~/.ssh/config)" -- "$2"))
}
complete -F sshConfAutoComplete ssh

For MacOS the standard sed command does not accept ; so each command must be on a new line:

   COMPREPLY=( $(compgen -W \
    "$(sed -n '/^host /{
     s#.*\*##g
     s#host ##g
     p
    }' ~/.ssh/config)" -- "$2"))
meuh
  • 51,383
  • that doesn't work unfortunately, the sed cmd has a fault or at least on mac it does – Nickotine Nov 15 '23 at 16:42
  • 1
    Yes, you should always mention when using MacOS as it has quite a few incompatibilities in its base commands. Many solutions involve using homebrew to install gnu versions of the tools to avoid this. You can also make the sed in the answer work by resorting to the very old syntax such as putting each command on a newline instead of using ;, and so on. – meuh Nov 15 '23 at 17:23
  • Sorry about that, do you mean, piping to a new sed cmd instead of ;? – Nickotine Nov 15 '23 at 18:16
  • 1
    I updated the answer what I assume MacOS sed accepts as syntax. There's some discussion on other differences here. – meuh Nov 16 '23 at 13:15
  • I'll test after work and give you the answer if it works. – Nickotine Nov 16 '23 at 15:37
  • thanks I like this one better as it's shorter, it works :) – Nickotine Nov 16 '23 at 18:34
  • I can't find anything on sed -n can you explain the curly brace syntax please, and why you need the p? I see people saying it's to tell sed to print once but I've never had the problem of it printing twice @meuh, also confused as to why you give seem to pass in /^host/ as a parameter if that's what's going on? – Nickotine Nov 16 '23 at 18:42
  • 1
    By default sed reads an input line, applies the commands in the script, and outputs the resulting line with any changes made. The -n option suppresses this default output, so instead you must use the p command to produce output of the current changes. The alternative is to use d to delete all the unwanted lines so they don't print. {} are just used to join many commands into one, as a sed script consists principally of a pattern to match a line against, and a single command to apply if it matches. The alternative would be to repeat the match for each command. – meuh Nov 17 '23 at 14:30
  • There's a very complete and very long description of gnu sed which has all the answers, but it is a lot to read. It does have some examples in part 7 to show how powerful it can be, but they are not all that easy to understand. There must be better tutorials out there. – meuh Nov 17 '23 at 14:36
  • Now I understand completely why you had to use p and the -n option. Thanks, much appreciated. – Nickotine Nov 18 '23 at 14:57
0

I got it working doing this and adding it my ~/bash_profile:

IFS=$'\n'

getSshConfHosts() {
grep '^host' ~/.ssh/config | \
grep -v '[?*]' | \
cut -d ' ' -f 2-
}

sshConfAutoComplete() {
local cur prev opts
cur=${COMP_WORDS[COMP_CWORD]}
prev=${COMP_WORDS[COMP_CWORD-1]}
opts=$(getSshConfHosts)
COMPREPLY=( $(compgen -W "$opts" -- $cur ) )
}
complete -F sshConfAutoComplete ssh

My previous sed command left a blank line at the deletion of host *

Update

This answer works, but I gave it to the accepted answer as he didn't know I was on Mac, and he hadn't written it first. His version is much easier to understand.

I use the regex part in another function so I have it like this in my bash_profile:

IFS=$'\n'

getSshConfHosts() { sed -n '/^host /{ s#.**##g s#host ##g; p }' ~/.ssh/config }

sshConfAutoComplete() { local wordList wordList=$(getSshConfHosts) COMPREPLY=( $(compgen -W "$wordList" -- $2 ) ) } complete -F sshConfAutoComplete ssh

Nickotine
  • 467