автозаполнение bash: добавить описание для возможных дополнений - PullRequest
15 голосов
/ 01 сентября 2011

Можно ли сделать так, чтобы автозаполнение bash выглядело в оболочке Cisco IOS?

Я имею в виду добавить краткие описания для каждого завершения, например:

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 может выполнять команды с "complete -W", но способен ли он распечатать описания для них

Ответы [ 5 ]

12 голосов
/ 12 апреля 2012

У меня есть решение, которое не требует нажатия клавиши 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

Это дает точный вывод, который вы ищете, и когда есть только одно возможное завершение, комментарий удаляется из него перед завершением.

2 голосов
/ 24 июля 2012

Я бы использовал конвертацию в зависимости от того, станет ли число кандидатов одним (как показано @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 для установки FOR_DISPLAY в Bash 4.x, но мне нужно было также поддерживать Bash 3.x.

Это ведет себя следующим образом:

$ x 1

Tab

$ x 10.10.10.1

Tab Tab

1: 10.10.10.10 - routerA
2: 10.10.10.11 - routerB
$ x 10.10.10.1
1 голос
/ 22 января 2012

После некоторых исследований я нашел решение.Я не знаю, как это выглядит в Cisco, но я знаю, как это работает в Vyatta.Единственный недостаток в том, что в этом варианте вы должны нажать TAB 3 раза, чтобы получить подробную справку в первый раз (первые два раза при нормальном завершении печатаются).Как только была показана подробная справка, следующие TAB s переключат нормальное и подробное завершение.

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 , поэтому есть место для дальнейших исследований.

1 голос
/ 01 сентября 2011

Да, но вам нужно немного bash kung foo, чтобы построить такую ​​систему. Способ завершения обычно работает, связывая нормальные функции с командами, которые вы хотите выполнить. Вы можете найти несколько основных примеров , чтобы лучше понять, как работает завершение, и приступить к разработке функций завершения. Кроме того, если у вас установлен пакет bash-completion, вы можете найти в своей системе ряд других примеров, которые в настоящее время приводят к завершению вашей оболочки.

Вы также можете взглянуть на раздел завершения официального руководства по bash.


EDIT

Я попытался провести несколько экспериментов, и теперь я пришел к выводу, что вы не можете делать именно то, что вам нужно: 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  
0 голосов
/ 18 декабря 2018

Вдохновленный с 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
...