Заставить compgen включать косые черты в каталогах при поиске файлов

Я хотел бы получить следующее поведение от моего пользовательского завершения

Дано

$ mkdir foo
$ touch foo faz/bar faz/baz

Я хотел бы получить это

$ foo -u <tab><tab> =>
foo faz/

$ foo -u fa<tab><tab> =>
foo -u faz/

$ foo -u faz/<tab><tab> =>
bar baz

Я предполагал, что compgen -f f выведет foo faz/, но он выводит foo faz, что мне мало помогает.

Нужно ли мне постобрабатывать вывод или есть какая-то волшебная комбинация опций для компиляции, которая работает?


person Trygve Laugstøl    schedule 17.10.2012    source источник


Ответы (6)


Я столкнулся с той же проблемой. Вот обходной путь, который я использую:

  1. Зарегистрируйте функцию завершения с помощью -o default, например, complete -o default -F _my_completion.
  2. Если вы хотите завершить имя файла, просто установите COMPREPLY=() и позвольте Readline взять на себя управление (это то, что делает -o default).

С этим связана потенциальная проблема — вы можете использовать COMPREPLY=() для отказа в завершении там, где это неуместно, и теперь это больше не будет работать. В Bash 4.0 и выше вы можете обойти это, используя compopt следующим образом:

  1. В верхней части функции завершения всегда запускайте compopt +o default. Это отключает завершение имени файла Readline, когда COMPREPLY пусто.
  2. Если вы хотите завершить имя файла, используйте compopt -o default; COMPREPLY=(). Другими словами, включайте завершение имен файлов Readline только тогда, когда вам это нужно.

Я не нашел полного обходного пути для Bash до версии 4.0, но у меня есть кое-что, что мне подходит. Я могу описать это, если кому-то действительно интересно, но, надеюсь, эти старые версии Bash в любом случае скоро выйдут из употребления.

person jjlin    schedule 28.09.2013
comment
Спасибо!! Это помогло мне. Интересно, что опция plus (compopt +o), похоже, не задокументирована в руководство по bash.. - person Håkon Hægland; 14.11.2014
comment
Рад, что это помогло. Кстати, вот тема, в которой автор Bash говорит мне, что мой подход, описанный выше, в значительной степени подходит для этого (к сожалению): lists.gnu.org/archive/html/bug-bash/2013-10/msg00002.html - person jjlin; 14.11.2014

Предыдущие ответы требуют bash 4 или не могут быть объединены с другими дополнениями на основе compgen для той же команды. Следующее решение не требует compopt (поэтому оно работает с bash 3.2) и компонуется (например, вы можете легко добавить дополнительную фильтрацию, чтобы соответствовать только определенным расширениям файлов). Пропустите вниз, если нетерпеливы.


Вы можете протестировать compgen, запустив его прямо из командной строки:

compgen -f -- $cur

где $cur — это слово, которое вы бы набрали до сих пор во время интерактивного завершения табуляции.

Если я нахожусь в каталоге, в котором есть файл afile.py и подкаталог adir и cur=a, то приведенная выше команда показывает следующее:

$ cur=a
$ compgen -f -- $cur
adir
afile

Обратите внимание, что compgen -f показывает файлы и каталоги. compgen -d показывает только каталоги:

$ compgen -d -- $cur
adir

Добавление -S / добавит косую черту к каждому результату:

$ compgen -d -S / -- $cur
adir/

Теперь мы можем попробовать перечислить все файлы и все каталоги:

$ compgen -f -- $cur; compgen -d -S / -- $cur
adir
afile.py
adir/

Обратите внимание, ничто не мешает вам вызывать compgen более одного раза! В вашем сценарии завершения вы должны использовать его следующим образом:

cur=${COMP_WORDS[COMP_CWORD]}
COMPREPLY=( $(compgen -f -- "$cur"; compgen -d -S / -- "$cur") )

К сожалению, это по-прежнему не дает нам желаемого поведения, потому что если вы наберете ad<TAB>, у вас будет два возможных завершения: adir и adir/. Таким образом, ad<TAB> завершится до adir, после чего вам все равно придется вводить / для устранения неоднозначности.

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

$ grep -v -F -f <(compgen -d -P '^' -S '$' -- "$cur") \
>     <(compgen -f -P '^' -S '$' -- "$cur") |
>     sed -e 's/^\^//' -e 's/\$$//'
afile.py

Давайте разберем это:

  • grep -f file1 file2 означает показать строки в файле2, которые соответствуют любому из шаблонов в файле1.
  • -F означает, что шаблоны в файле1 должны точно совпадать (как подстрока); они не являются регулярными выражениями.
  • -v означает инвертировать соответствие: отображать только те строки, которых нет в файле1.
  • <(...) — это bash замена процесса. Он позволяет запускать любую команду в тех местах, где ожидается файл.

Итак, мы говорим grep: вот список файлов — удалите все, которые соответствуют этому списку каталогов.

$ grep -v -F -f <(compgen -d -P '^' -S '$' -- "$cur") \
>     <(compgen -f -P '^' -S '$' -- "$cur")
^afile.py$

Я добавил маркеры начала и конца с -P ^ и -S '$' в compgen, потому что -F в grep выполняет сопоставление подстрок, и мы не хотим удалять имя файла, такое как a-file-with-adir-in-the-middle, только потому, что его средняя часть совпадает с именем каталога. Получив список файлов, мы удаляем эти маркеры с помощью sed.

Теперь мы можем написать функцию, которая делает то, что мы хотим:

# Returns filenames and directories, appending a slash to directory names.
_mycmd_compgen_filenames() {
    local cur="$1"

    # Files, excluding directories:
    grep -v -F -f <(compgen -d -P ^ -S '$' -- "$cur") \
        <(compgen -f -P ^ -S '$' -- "$cur") |
        sed -e 's/^\^//' -e 's/\$$/ /'

    # Directories:
    compgen -d -S / -- "$cur"
}

Вы используете это так:

_mycmd_complete() {
    local cur=${COMP_WORDS[COMP_CWORD]}
    COMPREPLY=( $(_mycmd_compgen_filenames "$cur") )
}
complete -o nospace -F _mycmd_complete mycmd

Обратите внимание, что -o nospace означает, что вы не получаете пробел после / каталога. Для обычных файлов мы добавили пробел в конце с помощью sed.

Одна приятная вещь в том, что это отдельная функция, это то, что ее легко протестировать! Например, вот автоматический тест:

diff -u <(printf "afile.py \nadir/\n") <(_mycmd_compgen_filenames "a") \
    || { echo "error: unexpected completions"; exit 1; }
person David Röthlisberger    schedule 24.10.2016
comment
Я использовал его как: COMPREPLY=( $(_my_compgen_filenames ${cur}) ), но замена пробела в sed, похоже, не происходит. Я думаю, что sed работает так, как ожидалось, но подстановка команд в моем использовании приводит к тому, что он проглатывается (?). Любые подсказки? - person stellarhopper; 19.11.2016
comment
@stellarhopper, каковы ваши симптомы - когда вы нажимаете TAB, он не добавляет пробел в конец уникального имени файла? Что произойдет, если вы запустите _my_compgen_filenames "whatever-you-typed-before-pressing-tab" -- есть пробел в конце или нет? Вы используете complete с -o nospace? - person David Röthlisberger; 21.11.2016
comment
Правильно, он не добавляет пробел в конце уникального имени файла. Если я запускаю функцию вручную в командной строке, она правильно добавляет пробел. У меня есть -o nospace, но я думаю, что мне это нужно? Как еще я могу остановить добавление пробела для каждого завершения? - person stellarhopper; 02.12.2016
comment
@stellarhopper да, вам нужно -o nospace. Какую версию Баша вы используете? Я проверил это с помощью bash 4.3.46. - person David Röthlisberger; 02.12.2016
comment
У меня 4.3.42 (от Fedora 24) - person stellarhopper; 02.12.2016

На это поведение влияет переменная readline mark-directories (и связанная с ней mark-symlinked-directories). Я считаю, что переменная должна быть установлена ​​on для complete, чтобы печатать завершающую косую черту в именах каталогов (по умолчанию в bash v3.00.16). По-видимому, связанное поведение compgen не добавляет косую черту к именам каталогов:-\

Установите значение mark-directories поочередно на on и off, затем повторите тест:

bind 'set mark-directories on'
bind 'set mark-directories off'

Чтобы сделать изменение постоянным для будущих вызовов bash, добавьте следующее в файл INPUTRC, обычно ~/.inputrc:

$if Bash
# append '/' to dirnames
set mark-directories on
$endif

Совет по установке переменных readline в текущей оболочке был найден здесь: https://unix.stackexchange.com/a/27545. Я не определил, как проверить текущее значение переменной readline.

Дополнительные идеи

Возможно, только академический интерес...

Создайте список только имен каталогов и добавьте косую черту: -

compgen -d -S / f
person crw    schedule 17.10.2012
comment
Хм, я думаю, что это уже установлено: bind -V|grep mark-directories => mark-directories установлен на `on'. Пробовал переключить, но не повезло, насколько я могу судить. - person Trygve Laugstøl; 18.10.2012
comment
@trygvis Вы правы, я вижу ваше поведение при использовании compgen. Сначала я провел тестирование, определив compspec с complete, который действительно генерировал завершающую косую черту. Хм, на данном этапе у меня нет другого ответа, хотя я исправил приведенный выше текст. - person crw; 18.10.2012
comment
У меня была аналогичная проблема на Mac, mark-directories был on, проблема все еще была. После того, как я установил mark-symlinked-directories, все заработало. Просто так получилось, что я часто переходил в каталог с символической ссылкой Dropbox, и, по-видимому, он не добавлял / даже в более глубокие части пути, которые сами не являются символическими ссылками. - person agentofuser; 16.11.2012

Используйте параметр -o filenames, переданный директиве complete. Пока содержимое COMPREPLY является допустимым путем, косая черта будет добавлена ​​соответствующим образом, при этом по-прежнему будет добавляться пробел после завершения совпадений, не относящихся к каталогу. (Работает на bash 3.2.)

_cmd() {
  COMPREPLY=( $( compgen -f -- "$cur" ))
  COMPREPLY=( $( compgen -A file -- "$cur" ))  # same thing
}

complete -o filenames -F _cmd cmd

См. info -n "(bash)Programmable Completion Builtins":

полный

... -o COMP-ОПЦИЯ ...

     filenames
           Tell Readline that the compspec generates filenames, so
           it can perform any filename-specific processing (like
           adding a slash to directory names or suppressing
           trailing spaces).  This option is intended to be used
           with shell functions specified with `-F'.

А также:

-A ACTION

... 'файл'

           File names.  May also be specified as -f.

Примечание. compgen и complete имеют одинаковые аргументы.

person Alissa H    schedule 07.08.2019

Это сработало для меня:

_my_completion() { 
  compopt -o nospace 
  COMPREPLY=( $(compgen -S "/" -d "${COMP_WORDS[$COMP_CWORD]}") )
}

complete -F _my_completion foo
person Håkon Hægland    schedule 14.11.2014

Если вы хотите продолжить использовать вкладку после завершения каталога; вот полное решение:

cur=${COMP_WORDS[COMP_CWORD]}
COMPREPLY=( $(compgen -S "/" -d -- ${cur}) ); 
compopt -o nospace;

сначала добавьте косую черту к ответу. Затем запустите команду compopt -o nospace, чтобы удалить завершающий пробел. Теперь, если в вашем каталоге всего foo/bar, для их ввода будет достаточно двух вкладок!

person Chris Maes    schedule 28.11.2014