Написание своих дополнений для Shell. Часть 2: bash

от автора


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

Преамбула

В процессе разработки одного своего проекта возникло желание добавить также файлы дополнений (только не спрашивайте зачем). Благо я как-то уже брался за написание подобных вещей, но читать что-либо тогда мне было лень, и так и не осилил.

Введение

Bash, в отличие от zsh, требует к себе некоторого велосипедостроения в отношении дополнений. Бегло погуглив, я не нашел более-менее нормальных туториалов, потому за основу были взяты имеющиеся в системе файлы дополнений для pacman (искренне надеюсь, что отцы-основатели Arch’а не придумывали много велосипедов).

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

proga [ -h | --help ] [ -e ESSID | --essid ESSID ] [ -с FILE | --config FILE ]       [ -o PROFILE | --open PROFILE ] [ -t NUM | --tab NUM ] [ --set-opts OPTIONS ] 

Список флагов:

  • флаги -h и --help не требуют аргументов;
  • флаги -e и --essid требуют аргумента в виде строки, без дополнения;
  • флаги -c и --config требуют аргумента в виде строки, файл с произвольной локацией;
  • флаги -o и --open требуют аргумента в виде строки, дополнение по файлам из определенной директории;
  • флаги -t и --tab требуют аргумента в виде строки, дополнение из указанного массива;
  • флаг --set-opts требует аргумента в виде строки, дополнение из указанного массива, разделены запятыми;
Структура файла

Здесь все переменные должны возвращать массив. Каких-либо особых форматов тут уже нет. Сначала опишем флаги, потом уже все остальные переменные. Я напомню (так как ниже я уже не буду приводить функции более подробно), что _proga_profiles(), в отличие от других переменных, должна возвращать актуальный на данный момент массив:

# variables _proga_arglist=() _proga_settings=() _proga_tabs=() _proga_profiles() {} 

Затем идут основные функции, которые будут вызываться для дополнения для определенной команды. В моем случае команда одна, и функция одна:

# work block _proga() {} 

Далее, опять, без выделения в отдельную функцию делаем соответствие «функция-команда»:

complete -F _proga proga 
Флаги

Как было сказано выше, особого формата тут нет, доступные флаги располагаются просто массивом:

_proga_arglist=(     '-h'     '--help'     '-e'     '--essid'     '-c'     '--config'     '-o'     '--open'     '-t'     '--tab'     '--set-opts' ) 
Массивы переменных

Приведу только функцию, которая в zsh выглядела таким образом:

_proga_profiles() {     print $(find /some/path -maxdepth 1 -type f -printf "%f\n") } 

В bash так не получится, пришлось чуть-чуть изменить:

_proga_profiles() {     echo $(find /some/path -maxdepth 1 -type f -printf "%f\n") } 
Тело функции

За дополнение в bash отвечает переменная COMPREPLY. Для отслеживания текущего состояния нужно вызвать функцию _get_comp_words_by_ref с параметрами cur (текущая опция) и prev (предыдущая, собственно состояние). Ну и нужно несколько точек, на которых сворачивать в определенную часть case (переменные want*). Для генерации дополнения используется compgen. После флага -W ему подается список слов. (Есть еще флаг -F, который вызывает функцию, но у меня он помимо этого еще и ворнинг выдает.) Последним аргументом идет текущая строка, к которой и нужно генерировать дополнение.

Таким образом, наша функция выглядит так:

_proga() {     COMPREPLY=()     wantfiles='-@(c|-config)'     wantprofiles='-@(o|-open|s|-select)'     wantsettings='-@(-set-opts)'      wanttabs='-@(t|-tab)'     _get_comp_words_by_ref cur prev      if [[ $prev = $wantstring ]]; then         # не делать дополнения, ждать введенной строки         COMPREPLY=()     elif [[ $prev = $wantfiles ]]; then         # дополнение по существующим файлам         _filedir     elif [[ $prev = $wantprofiles ]]; then         # дополнение из функции         COMPREPLY=($(compgen -W '${_proga_profiles[@]}' -- "$cur"))     elif [[ $prev = $wanttabs ]]; then         # дополнение из массива         COMPREPLY=($(compgen -W '${_proga_tabs[@]}' -- "$cur"))     elif [[ $prev = $wantsettings ]]; then         # дополнение из массива         # -S вставит запятую после, но вот мультивыбор не включил =(         COMPREPLY=($(compgen -S ',' -W '${_proga_settings[@]}' -- "$cur"))     else         # вывести доступные аргументы         COMPREPLY=($(compgen -W '${_proga_arglist[@]}' -- "$cur"))     fi      true } 

Заключение

Файл хранится в директории /usr/share/bash-completion/completions/ с произвольным именем. Файл примера полностью может быть найден в моем репозитории.

ссылка на оригинал статьи http://habrahabr.ru/post/230425/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *