Получение 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
, что не очень помогает мне.
Нужно ли выполнять пост-обработку вывода или есть какая-то волшебная комбинация параметров для компиляции, которые работают?
Ответы
Ответ 1
Я столкнулся с той же проблемой. Вот обходной путь, который я использую:
- Зарегистрируйте функцию завершения с помощью
-o default
, например, complete -o default -F _my_completion
.
- Если вы хотите заполнить имя файла, просто установите
COMPREPLY=()
и включите Readline (что делает -o default
).
У вас есть потенциальная проблема - вы можете использовать COMPREPLY=()
для отказа в завершении, где это не подходит, и теперь это больше не будет работать. В Bash 4.0 и выше вы можете обойти это с помощью compopt
следующим образом:
- В верхней части вашей функции завершения всегда запускайте
compopt +o default
. Это отключает завершение имени файла Readline, когда COMPREPLY
пуст.
- Если вы хотите заполнить имя файла, используйте
compopt -o default; COMPREPLY=()
. Другими словами, включите чтение имени файла Readline только тогда, когда вам это нужно.
Я не нашел полного обходного пути для pre-4.0 Bash, но у меня есть что-то, что работает для меня достаточно хорошо. Я могу описать это, если кто-то действительно заботится, но, надеюсь, эти более старые версии Bash скоро все равно выпадут из общего использования.
Ответ 2
На это поведение влияет переменная readline mark-directories
(и связанная с ней mark-symlinked-directories
). Я считаю, что переменная должна быть установлена на для 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
Ответ 3
Предыдущие ответы требуют bash 4 или не могут быть скомбинированы с другими дополнениями на основе процессоров для одной и той же команды. Следующее решение не требует 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 более одного раза! В вашем завершении script вы будете использовать его следующим образом:
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$
Я добавил маркеры начала и конца с помощью compgen -P ^
и -S '$'
, потому что grep -F
выполняет подстроку, и мы не хотим удалять имя файла, например 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; }
Ответ 4
Используйте опцию -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 -o PTION ...
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
имеют одинаковые аргументы.
Ответ 5
Это сработало для меня:
_my_completion() {
compopt -o nospace
COMPREPLY=( $(compgen -S "/" -d "${COMP_WORDS[$COMP_CWORD]}") )
}
complete -F _my_completion foo
Ответ 6
Если вы хотите продолжить использование вкладки после завершения работы каталога; здесь полное решение:
cur=${COMP_WORDS[COMP_CWORD]}
COMPREPLY=( $(compgen -S "/" -d -- ${cur}) );
compopt -o nospace;
сначала добавьте конечную косую черту в ответ. Затем запустите команду compopt -o nospace
, чтобы удалить конечное пространство. Теперь, если у вас есть только foo/bar
в вашем каталоге, для их ввода достаточно двух вкладок!