16

是否可以让 bash 自动完成看起来像 Cisco IOS shell 中的那样?

我的意思是为每个完成添加简短的描述,如下所示:

telnet 10.10.10. (TAB Pressed)
 10.10.10.10 - routerA
 10.10.10.11 - routerB

其中 10.10.10.10 和 10.10.10.11 是可能的完成,而 routerA 和 routerB 只是描述(不被执行)。

我知道 bash 可以使用“完整 -W”完成命令,但它能够为它们打印描述吗?

4

5 回答 5

14

我有一个解决方案,不需要按 TAB 两次以上或回显任何额外信息。关键是检查是否只有一个完成,然后将该完成剥离到有效部分,通常通过删除“评论”分隔符后最大的匹配后缀。要完成 OP 的示例:

_telnet() {
  COMPREPLY=()
  local cur
  cur=$(_get_cword)
  local completions="10.10.10.10 - routerA
10.10.10.11 - routerB
10.20.1.3 - routerC"

  local OLDIFS="$IFS"
  local IFS=$'\n'
  COMPREPLY=( $( compgen -W "$completions" -- "$cur" ) )
  IFS="$OLDIFS"
  if [[ ${#COMPREPLY[*]} -eq 1 ]]; then #Only one completion
    COMPREPLY=( ${COMPREPLY[0]%% - *} ) #Remove ' - ' and everything after
  fi
  return 0
}
complete -F _telnet -A hostnames telnet

这给出了您正在寻找的确切输出,并且当只有一个可能的完成时,注释会在完成之前从它中删除。

于 2012-04-12T19:00:09.830 回答
3

对于简单的案例,我会根据候选者的数量是否变为一个(如@bonsaiviking 所示)来使用转换,如果我需要更灵活地向用户展示什么,我会使用以下转换。

__foo () {
    local WORDS
    WORDS=("1|10.10.10.10|routerA" "2|10.10.10.11|routerB")

    local FOR_DISPLAY=1
    if [ "${__FOO_PREV_LINE:-}" != "$COMP_LINE" ] ||
            [ "${__FOO_PREV_POINT:-}" != "$COMP_POINT" ]; then
        __FOO_PREV_LINE=$COMP_LINE
        __FOO_PREV_POINT=$COMP_POINT
        FOR_DISPLAY=
    fi

    local IFS=$'\n'
    COMPREPLY=($(
        for WORD in "${WORDS[@]}"; do
            IFS=\| read -ra SP <<<"$WORD"
            if [ "${SP[1]:0:${#2}}" == "$2" ]; then
                if [ -n "$FOR_DISPLAY" ]; then
                    printf "%-*s\n" "$COLUMNS" "${SP[0]}: ${SP[1]} - ${SP[2]}"
                else
                    echo "${SP[1]}"
                fi
            fi
        done
    ))
}
complete -F __foo x

注意:您可能可以在 Bash 4.x 中使用COMP_TYPE设置FOR_DISPLAY,但我也需要支持 Bash 3.x。

其行为如下:

$ x 1

Tab

$ x 10.10.10.1

TabTab

1: 10.10.10.10 - routerA
2: 10.10.10.11 - routerB
$ x 10.10.10.1
于 2012-07-24T08:30:56.427 回答
1

经过一番研究,我找到了解决方案。我不知道它在 Cisco 中的样子,但我知道它在 Vyatta 中是如何工作的。唯一的缺陷是,在此变体中,您必须按TAB3 次才能第一次获得详细帮助(打印前两次正常完成)。显示详细帮助后, nextTAB将切换正常和详细完成。

comment_show_last_detailed=1
comment_show_last_position=0

_comment_show()
{
  local cur opts i opt comment opts comments

  opts="result1
result2"
  comments="comment1
comment2"
  [ $comment_show_last_position -gt $COMP_POINT ] &&
    comment_show_last_position=0

  if [ $comment_show_last_detailed = 0 ] &&
     [ $comment_show_last_position = $COMP_POINT ]; then
    for ((i=1; ;++i)); do
      opt=`echo "$opts" | cut -f$i -d$'\n'`
      [ -z "$opt" ] && break
      comment=`echo "$comments" | cut -f$i -d$'\n'`
      echo
      echo -n "$opt - $comment"
    done
    comment_show_last_detailed=1
    COMPREPLY=
  else
    cur="${COMP_WORDS[COMP_CWORD]}"
    SAVEIFS="$IFS"
    IFS=$'\n'
    COMPREPLY=( $(compgen -W "${opts}" ${cur}) )
    IFS="$SAVEIFS"
    comment_show_last_detailed=0
  fi
  comment_show_last_position=$COMP_POINT
}
complete -F _comment_show comment

我什至设法TAB使用变量将压力减少到只有 2 COMP_TYPE,但是如果在第一次按下后插入一些符号,bash 不会在底行重新打印当前命令行TAB,因此有进一步研究的空间。

于 2012-01-22T09:19:13.207 回答
1

是的,但是您需要一些 bash kung foo 才能构建这样的系统。完成通常的工作方式是将普通函数绑定到您要完成的命令。您可以在周围找到一些基本示例,以更好地了解完成的工作原理,并开始开发您的完成功能。此外,如果您碰巧安装了该bash-completion软件包,您可以在您的系统中搜索当前在您的 shell 中驱动完成的许多其他示例。

您还可以查看官方 bash 手册的完成部分


编辑

我尝试了一些实验,现在我的结论是你不能完全按照你的要求去做:bash 不支持complete结果旁边的帮助文本。您可以做的是为提供的完成词添加图例。这可以在用作 的 bash 函数中完成,也可以在_myfoo命令complete -F _myfooviacomplete -C myfoo中完成,该命令在完成之前打印出图例。

主要区别在于使用绑定到 Bash 的函数,而命令可以用您选择的任何语言编写,只要它能够设置所需的环境变量。

这是一个小例子:

skuro$ touch ~/bin/myfoo
skuro$ chmod +x ~/bin/myfoo
skuro$ _myfoo(){
> echo "result1 -- number one"
> echo "result2 -- number two"
> local cur prev
> _get_comp_words_by_ref cur prev
> COMPREPLY=( $(compgen -W "result1 result2" "$cur") )
> return 0
> }
skuro$ complete -F _myfoo myfoo
skuro$ myfoo result<TAB>
result1 -- number one
result2 -- number two

result1  result2  
于 2011-09-01T07:33:51.993 回答
0

灵感来自https://github.com/CumulusNetworks/NetworkDocopt

基本技巧是将帮助文本、PS1(扩展)和原始命令打印到stderr,然后将完成选项打印到stdout.

这是在 bash 中获取的代码片段,以将完成函数添加到telnet. 它将调用一个 ruby​​ 脚本(称为p.rb)来生成实际的完成输出。

_telnet_complete()
{
    COMPREPLY=()
    COMP_WORDBREAKS=" "
    local cur=${COMP_WORDS[COMP_CWORD]}
    local cmd=(${COMP_WORDS[*]})

    local choices=$(./p.rb ${cmd[*]} --completions ${COMP_CWORD} ${PS1@P})
    COMPREPLY=($(compgen -W '${choices}' -- ${cur} ))
    return 0
}
complete -F _telnet_complete telnet

这是一个实现p.rb

#!/usr/bin/env ruby                                                                                                                                                                                                                                                                    

ip = ""
out_ps1 = []
out_args = []
state = :init
completion_req = false
ARGV.each do |e|
    case state
    when :init
        if e == "--completions"
            completion_req = true
            state = :complte
        else
            out_args << e
            if /^\d+\.\d+\.\d+\.\d+$/ =~ e
                ip = e
            end
        end

    when :complte
        state = :ps1

    when :ps1
        out_ps1 << e

    end
end

routes = {
    "10.10.10.10" => "routerA",
    "10.10.10.11" => "routerB",
}

if completion_req
    $stderr.puts ""
    routes.each do |k, v|
        if k[0..ip.size] == ip or ip.size == 0
            $stderr.puts "#{k} - #{v}"
            $stdout.puts k
        end
    end
    $stderr.write "#{out_ps1.join(" ")}#{out_args.join(" ")} "
    exit 0
end

例子:

$ telnet <tab>
10.10.10.10 - routerA
10.10.10.11 - routerB
$ telnet 10.10.10.1
于 2018-12-18T08:12:08.673 回答