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