Предыдущие ответы требуют 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