После посещения заметки «Создание списка имен хостов для ssh на лету для автодополнения» долго вчитывался в man bash с целью понять, как же там это автодополнение работает. В конце концов, поняв, что с наскоку эту гремучую смесь из фич баша и возможностей readline не осилить, скачал оба исходника и нашел там великое множество примеров использования автозаполнения в bash, значительно упрощающих повседневную работу в консоли. Эти команды можно добавить в ~/.bash_profile или вынести в отдельный файл ~/.bash_completions или /etc/bash_completions и подключать его в .bash_profile. Список текущих дополнений можно посмотреть по команде complete (без параметров).
Данные скрипты скопированы почти без изменений из папки examples/complete архива с исходными текстами bash. Автор всех скриптов, за исключением последнего — Ian Macdonald. На его странице доступен архив с огромным количеством скриптов автодополнения. Автор последнего скрипта (автодополнение длинных опций у configure) — Manu Rouat. Большое им человеческое спасибо 🙂
Для команд работы с каталогами tab показывает только каталоги:
complete -d cd mkdir rmdir pushd
Для команд работы с файлами показываются только файлы (все или определённых типов, в зависимости от команды):
complete -f cat less more chown ln strip complete -f -X '*.gz' gzip complete -f -X '*.Z' compress complete -f -X '!*.+(Z|gz|tgz|Gz)' gunzip zcat zmore complete -f -X '!*.Z' uncompress zmore zcat complete -f -X '!*.+(gif|jpg|jpeg|GIF|JPG|bmp)' ee xv complete -f -X '!*.+(ps|PS|ps.gz)' gv complete -f -X '!*.+(dvi|DVI)' dvips xdvi dviselect dvitype complete -f -X '!*.+(pdf|PDF)' acroread xpdf complete -f -X '!*.texi*' makeinfo texi2dvi texi2html complete -f -X '!*.+(tex|TEX)' tex latex slitex complete -f -X '!*.+(mp3|MP3)' mpg123
Для команд работы с заданиями показываются номера заданий, предваряемые символом «%»:
complete -A signal kill -P '%' complete -A stopped -P '%' bg complete -j -P '%' fg jobs disown
Сетевым командам подставляется список хостов из файла, заданного в переменной окружения HOSTFILE. Обычно это файл /etc/hosts:
complete -A hostname ssh rsh telnet rlogin ftp ping fping host traceroute nslookup
По уже упоминавшейся ссылке рекомендуют подставлять команде ssh список хостов из ~/.ssh/known_hosts:
complete -W "$(echo `cat ~/.ssh/known_hosts | cut -f 1 -d ' ' | sed -e s/,.*//g | uniq | grep -v "["`;)" ssh
Для ряда встроенных команд bash, а также системных команд тоже свои списки дополнений:
# подставляются переменные окружения complete -v export local readonly unset # подставляются параметры команд set, shopt, help, unalias, bind complete -A setopt set complete -A shopt shopt complete -a unalias complete -A binding bind
Для команд управления выполнением программ подставляются названия возможных программ:
complete -c command time type nohup exec nice eval strace gdb
Думаю, основной принцип понятен — указываем ключевое слово «complete», за ним некий список возможных «подстав» и далее команды, для которых этот список работает. Список подстановок может быть задан в виде уже готовой опции, как в большинстве вышеприведённых примеров (в фрагменте complete -d cd mkdir rmdir pushd опция -d означает, что должен подставляться список доступных каталогов; на самом деле это сокращённый вид опции -A directory. За списком возможных параметров опции -A обращайтесь к man builtin), в виде списка возможных слов, как в примере с .known_hosts (тогда список предваряется опцией -W), или в виде списка файлов (-X «*.gz» покажет все доступные файлы, которые не совпадают с шаблоном «*gz»; чтобы, наоборот, показать все файлы *.gz, нужно в начало шаблона добавить восклицательный знак).
Конечно же, этим возможности автодополнения bash не ограничиваются. Можно использовать свои функции, которые сработают после того, как пользователь напишет команду, но перед тем, как будет нажата кнопка Tab, показывающая список возможных параметров. Такие функции должны произвести какие-то действия для нахождения подходящих параметров для автодополнения и заполнить этими параметрами массив COMPREPLY; при этом доступны массив COMP_WORDS, содержащий все слова из уже написанной строки, переменные COMP_CWORD (номер текущего слова в массиве COMP_WORDS), COMP_LINE (вся строка целиком в одной переменной) и еще парочка (см. man bash).
Формат команды настройки автодополнения в случае использования функции следующий:
_function_name () { ... } complete -F _function_name command
Чтобы не быть голословным, приведу несколько примеров, из которых всё станет ясно:
Для команды umount подставляется список примонтированных файловых систем:
_umount () { local cur COMPREPLY=() cur=${COMP_WORDS[COMP_CWORD]} COMPREPLY=( $( mount | cut -d' ' -f 3 | grep ^$cur) ) return 0 } complete -F _umount umount
Для команд работы с группами пользователей подставляются названия групп из /etc/group:
_gid_func () { local cur COMPREPLY=() cur=${COMP_WORDS[COMP_CWORD]} COMPREPLY=( $( awk 'BEGIN {FS=":"} {if ($1 ~ /^'$cur'/) print $1}' /etc/group ) ) return 0 } complete -F _gid_func groupdel groupmod
А теперь те две функции, ради которых, собственно, и затевался этот пост 🙂
Автодополнение параметров для команды find:
_find () { local cur prev COMPREPLY=() cur=${COMP_WORDS[COMP_CWORD]#-} prev=${COMP_WORDS[COMP_CWORD-1]} case "$prev" in -@(max|min)depth) COMPREPLY=( $( compgen -W '0 1 2 3 4 5 6 7 8 9' ) ) return 0 ;; -?(a)newer|-fls|-fprint?(0|f)) COMPREPLY=( $( compgen -f $cur ) ) return 0 ;; -fstype) # this is highly non-portable (the option to -d is a tab) COMPREPLY=( $( cut -d' ' -f 2 /proc/filesystems | grep ^$cur ) ) return 0 ;; -gid) COMPREPLY=( $( awk 'BEGIN {FS=":"} {if ($3 ~ /^'$cur'/) print $3}' /etc/group ) ) return 0 ;; -group) COMPREPLY=( $( awk 'BEGIN {FS=":"} {if ($1 ~ /^'$cur'/) print $1}' /etc/group ) ) return 0 ;; -?(x)type) COMPREPLY=( $( compgen -W 'b c d p f l s' $cur ) ) return 0 ;; -uid) COMPREPLY=( $( awk 'BEGIN {FS=":"} {if ($3 ~ /^'$cur'/) print $3}' /etc/passwd ) ) return 0 ;; -user) COMPREPLY=( $( compgen -u $cur ) ) return 0 ;; -[acm]min|-[acm]time|-?(i)?(l)name|-inum|-?(i)path|-?(i)regex| -links|-perm|-size|-used|-exec|-ok|-printf) # do nothing, just wait for a parameter to be given return 0 ;; esac # complete using basic options ($cur has had its dash removed here, # as otherwise compgen will bomb out with an error, since it thinks # the dash is an option to itself) COMPREPLY=( $( compgen -W 'daystart depth follow help maxdepth mindepth mount noleaf version xdev amin anewer atime cmin cnewer ctime empty false fstype gid group ilname iname inum ipath iregex links lname mmin mtime name newer nouser nogroup perm regex size true type uid used user xtype exec fls fprint fprint0 fprintf ok print print0 printf prune ls' $cur ) ) # this removes any options from the list of completions that have # already been specified somewhere on the command line. COMPREPLY=( $( echo "${COMP_WORDS[@]}-" | (while read -d '-' i; do [ "$i" == "" ] && continue # flatten array with spaces on either side, # otherwise we cannot grep on word boundaries of # first and last word COMPREPLY=" ${COMPREPLY[@]} " # remove word from list of completions COMPREPLY=( ${COMPREPLY/ ${i%% *} / } ) done echo ${COMPREPLY[@]}) ) ) # put dashes back for (( i=0; i < ${#COMPREPLY[@]}; i++ )); do COMPREPLY[i]=-${COMPREPLY[i]} done return 0 } complete -F _find find
Гвоздь программы! Автодополнение параметров для команды configure:
_longopt_func () { case "$2" in -*) ;; *) return ;; esac case "$1" in ~*) eval cmd=$1 ;; *) cmd="$1" ;; esac COMPREPLY=( $("$cmd" --help | sed -e '/--/!d' -e 's/.*--([^ ]*).*/--1/'| grep ^"$2" |sort -u) ) } complete -o default -F _longopt_func wget bash configure