bash自动完成:添加可能的完成描述

时间:2011-09-01 07:25:28

标签: bash tab-completion bash-completion

是否可以使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是可能的完成 和路由器A& routerB只描述(不执行)。

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

5 个答案:

答案 0 :(得分:12)

我有一个解决方案,不需要按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

这会给出您正在寻找的确切输出,当只有一个可能的完成时,评论会在完成之前从中删除。

答案 1 :(得分:2)

我会根据候选人的数量(如@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

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

表现如下:

$ x 1

标签

$ x 10.10.10.1

标签 标签

1: 10.10.10.10 - routerA
2: 10.10.10.11 - routerB
$ x 10.10.10.1

答案 2 :(得分:1)

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

您还可以查看官方bash手册的completion section


修改

我尝试了一些实验,现在我的结论是不能完全按照您的要求执行操作:bash不支持complete结果旁边的帮助文字。你可以做的是添加提供的完整单词的图例。这可以在bash函数_myfoo中用作complete -F _myfoo,也可以在complete -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  

答案 3 :(得分:1)

经过一番研究后,我找到了解决方案。我不知道它在思科看起来如何,但我知道它在Vyatta中是如何运作的。唯一的缺陷是,在此变体中,您必须按 TAB 3次才能获得第一次详细帮助(前两次打印正常完成)。一旦显示详细的帮助,下一个 TAB 将切换正常和详细的完成。

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

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

答案 4 :(得分:0)

灵感来自here

基本技巧是将帮助文本PS1(扩展)和原始命令打印到ncurses_init_pair(1, NCURSES_COLOR_YELLOW, NCURSES_COLOR_BLUE); $x = 0; foreach ($columns as $curcol) { ncurses_wmove($win,1,$x); ncurses_wcolor_set($win,1); ncurses_wvline($win,NCURSES_ACS_VLINE,$termheight); ncurses_wcolor_set($win,0); $x+= $colwidth[$curcol]+3; } ,然后将完成选项打印到stderr

以下是在bash中提供的代码段,类似于stdout的完成功能。它将调用ruby脚本(称为telnet)来生成实际的完成输出。

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