ИТ Блог. Администрирование серверов на основе Linux (Ubuntu, Debian, CentOS, openSUSE)

Linux Fu. Завершение пользовательских команд Bash

Linux Fu. Завершение пользовательских команд Bash

Если вы не являетесь пользователем Linux и наблюдаете, как кто-то, кто знает, что они делают, использует Bash – популярный интерпретатор командной строки – у вас может сложиться впечатление, что он печатает гораздо быстрее, чем на самом деле. Это потому, что опытные пользователи Linux знают, что нажатие клавиши табуляции, как правило, завершает ввод текста, поэтому вы можете набрать всего несколько символов и получить гораздо более длинную строку текста. Эта функция очень умная, так что вы, возможно, не поняли ее, но она хорошо знает, что можно набрать. Например, если вы попытаетесь разархивировать файл, он знает, что ожидаемое имя файла, вероятно, имеет расширение .zip.

Linux Fu. Завершение пользовательских команд BashКак это происходит? Сначала вы можете подумать: «кого волнует, как это происходит?» Проблема в том, что когда вы пишете сценарий оболочки или программу, работающую в Linux, завершение становится тупым. Кто-то должен сделать Bash умным в отношении каждой программы командной строки, и если вы автор, то кто-то – это вы.

 

Анатомия завершения программы

Оказывается, завершение зависит от конкретной библиотеки GNU, известной как readline.  Он читает текст для множества различных программ, включая Bash, и вы можете настроить его, используя  файл .inputrc в вашем домашнем каталоге. Например, вот пример.inputrc :

"\e[A": history-search-backward
"\e[B": history-search-forward
$if Bash
Space: magic-space
$endif
set match-hidden-files off
set completion-ignore-case on
set visible-stats on
set show-all-if-ambiguous on

 

Это не выглядит много, но есть системная конфигурация в /etc/inputrc, которая намного более существенна. Вы также можете выполнить определенные команды, которые будут изменять конфигурацию на лету. Например, встроенный в Bash «bind» может настроить все, а также покажет вам длинный список того, что уже настроено. Если вы делаете эту команду:

bind -p

 

Вы увидите длинный список результатов. Интересны следующие несколько строк:

"\C-i": complete
"\e\e": complete
"\e!": complete-command
"\e/": complete-filename
"\e@": complete-hostname
"\e{": complete-into-braces
"\e~": complete-username
"\e$": complete-variable

 

Ваш может отличаться, но, по сути, это говорит о том, что, когда появляется клавиша табуляции или двойной выход, выполнить операцию завершения. Есть также несколько специализированных дополнений, которые начинаются с побега.

 

Но как это работает?

Есть три встроенные команды оболочки, которые взаимодействуют, чтобы управлять завершением, это:

 

Мы не будем вдаваться во все возможные варианты, хотя вы можете прочитать руководство по Bash, если вам интересно. Для наших целей вам понадобится всего несколько, и все три команды в большинстве случаев принимают одинаковые аргументы.

Давайте попробуем несколько вещей с compgen, так как это то, что bash в конце концов вызывает для завершения. Предположим, вы набираете команду ls в командной строке и нажимаете tab. Bash сделает следующий вывод:

compgen -c ls

 

Вы увидите вывод (в зависимости от того, что вы установили) всех команд, начинающихся с ls , таких как ls , lsusb , lspci и т. д. Вы также можете использовать более современный синтаксис «-A command» для получения того же результата.

Попробуйте это:

compgen -d / e

 

Опять же, вы можете использовать каталог -A, если хотите. Понятно, что это выводит каталоги. Это хорошо, но как Bash узнает, какие опции нужно передать в compgen? Вот где приходит команда complete. Команда complete может указать Bash вызвать функцию оболочки или команду в ответ на завершение выполнения определенной команды. Он также может установить один для пустой команды или команды, у которой иначе нет соответствующего правила. Есть также хорошие ярлыки для общих случаев.

Рассмотрим скрипт под названием burnhex. Если вы введете Burnhex , пробел и вкладку, Bash (через readline ) предложит вам все файлы в текущем каталоге для завершения. Однако вы можете догадаться, что вам нужны только файлы, заканчивающиеся на .hex. Вы можете легко сделать с помощью complete:

complete -G "*.hex" burnhex

 

Теперь будут отображаться только файлы с расширением .hex.

Чтобы взглянуть на еще более сложные дополнения, давайте начнем с простой функции оболочки. По соглашению, они начинаются с подчеркивания, хотя это не техническое требование:

function _andreyex {
   COMPREPLY=( "andreyex.ru" "andreyex.io" )
   return 0
}

 

Это говорит системе, что у нас есть два дополнения. Давайте представим, что у нас есть скрипт или программа под названием « goandreyex », и мы хотим, чтобы это было завершением (команда не должна существовать, чтобы это работало):

complete -F _andreyex goandreyex

 

Теперь, если вы введете:

goandreyex<SPACE><TAB>

 

Вы увидите свое завершение в действии. Система достаточно умна, чтобы знать, что она может заполнить часть «andreyex», поскольку оба варианта начинаются с этого. Если вы наберете «c» или «i» и снова нажмете «Tab», он выберет правильный вариант.

Кстати, функция получает три аргумента, которые мы не используем. Первый аргумент ($ 1) – это имя активной команды, второй – завершаемое слово, а третий – слово перед текущим словом. Например, рассмотрим строку ниже и три аргумента следующие:

ls -l /foo
$1=ls
$2=/foo
$3=-l

 

Также есть несколько переменных среды, которые вы хотите установить, если хотите извлечь всю строку или определить, как пользователь вызвал завершение. Для простых случаев вам, вероятно, это не нужно.

Конечно, это простой пример. Для вещей, которые больше, чем вы можете обработать в функции, вы также можете запустить полноценную команду, используя -C вместо -F. Команды получают одинаковые аргументы и большинство одинаковых переменных среды.

Кроме того, помните опции -c и -d для compgen ? Они тоже работают здесь. Так что если у вас есть команда с именем foobar, для которой в качестве аргумента нужно указать имя каталога, вы можете сказать:

complete -d foobar

 

Вы даже можете вызвать compgen в своей функции или команде, чтобы генерировать данные напрямую или для дальнейшей фильтрации и дополнения в вашем коде. Это может быть довольно сложно, и если вы посмотрите на некоторые из встроенных, вы поймете, что мы имеем в виду.

В целом, многие сценарии завершения находятся либо в /etc/bash_completion.d или /usr/share/bash-completions. Да, один из них – подчеркивание, а другой – тире. Одна из радостей Linux заключается в том, что каждая система немного отличается.

 

Более типичный пример

Рассмотрим команду ftp. Вы можете узнать, что это за завершение, набрав:

complete -p | grep ftp

 

Результат будет выглядеть так:

complete -F _known_hosts ftp

 

Это означает, что есть функция оболочки с именем _known_hosts. Если вы хотите увидеть, как это выглядит, попробуйте:

declare -f _known_hosts

 

Вы должны увидеть что-то вроде этого:

_known_hosts () 
{ 
 local cur prev words cword;
 _init_completion -n : || return;
 local options;
 [[ "$1" == -a || "$2" == -a ]] && options=-a;
 [[ "$1" == -c || "$2" == -c ]] && options+=" -c";
 _known_hosts_real $options -- "$cur"
}

 

Очевидно, что настоящая работа выполняется в _known_hosts_real, и вы можете сбросить ее таким же образом. Это сложно, но вы можете догадаться, что он будет сбрасывать имена хостов, о которых знает компьютер. Есть и другие вспомогательные функции, такие как _init_completion . Дело не в том, как они работают, а в том, что они существуют, и вы можете использовать их так же, как это делает установка завершения ftp.

Это был только краткий обзор. Кстати, Bash – не единственная игра в городе. У Zsh есть очень мощный (и другой) механизм завершения, если вам этого не достаточно.

Как только вы поймете, как Bash читает командные строки, вы можете поиграть со множеством интересных трюков.

Exit mobile version