Bash autocompletion: добавить описание для возможных завершений

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

Ответ 1

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

Я бы использовал преобразование, основанное на том, станет ли число кандидатов одним из них (как показано на примере @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

Ответ 3

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

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


ИЗМЕНИТЬ

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

Ответ 4

После некоторых исследований я нашел решение. Я не знаю, как это выглядит в 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 прессование, поэтому есть место для дальнейших исследований.

Ответ 5

Вдохновленный из 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} ${[email protected]})
    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