Как работать с именами файлов, содержащими одну цитату внутри функции завершения zsh?

Я использую оболочку zsh и имею следующий код внутри моего .zshrc :

 fpath=(~/.zsh/completion $fpath) autoload -Uz _vc autoload -Uz compinit compinit 

Первая строка добавляет путь ~/.zsh/completion к массиву, хранящемуся внутри переменной fpath среды.

Вторая строка загружает функцию завершения, называемую _vc , для пользовательской функции оболочки, называемой vc . Цель vc – просто отредактировать файл внутри определенной папки ( ~/.cheat ). _vc определяется в файле ~/.zsh/completion/_vc .

Последние 2 строки включают систему завершения.

Вот код для моей функции завершения _vc :

 #compdef vc declare -a cheatsheets cheatsheets="$(ls ~/.cheat)" _arguments "1:cheatsheets:(${cheatsheets})" && return 0 

Я скопировал его с этого адреса и адаптировал его для моих нужд.

Пока каталог ~/.cheat не имеет файла, имя которого содержит одну цитату, завершение работы. Но если есть такой, как foo'bar , завершение завершается с этим сообщением об ошибке:

 (eval):56: unmatched ' (eval):56: unmatched ' (eval):56: unmatched ' (eval):56: unmatched ' 

Я нашел решение, заменив двойные кавычки в строке cheatsheets="$(ls ~/.cheat)" , с одиночными кавычками cheatsheets='$(ls ~/.cheat)' .

Теперь, когда я нажимаю Tab после моей команды vc , zsh предлагает файлы внутри ~/.cheat , включая foo\'bar (zsh, похоже, автоматически ускользает от одной кавычки).

Однако я не понимаю, как и почему это работает. С одинарными кавычками переменные cheatsheets могут содержать литеральную строку. Таким образом, подстановка $(...) не должна расширяться. Например, если я выполняю следующие команды:

 myvar='$(ls)' echo $myvar → outputs `$(ls)` literally 

Итак, почему '$(ls ~/.cheat)' расширен внутри _arguments "1:cheatsheets:(${cheatsheets})" && return 0 ? И почему он автоматически избегает одиночной цитаты внутри foo'bar ?

Если мы настаиваем на том, чтобы делать вещи The Wrong Way ™

 #compdef vc declare -a cheatsheets cheatsheets=(${(f)"$(ls ~/.cheat/)"}) _arguments '1:cheatsheets:(${cheatsheets})' && return 0 

Тьфу! Это, конечно, сломается, если имя файла содержит новую строку, поскольку (f) разделяет их. Разбор ls – это любая плохая идея ; ZSH может собирать файлы непосредственно в массив:

 % mkdir ~/.lojban % touch ~/.lojban/{go\'i,na\ go\'i} % words=(~/.lojban/*) % print -l ${words:t} go'i na go'i % words=(~/.lojban/*(On)) % print -l ${words:t} na go'i go'i % 

Но нам, вероятно, не нужен локальный массив; _files могут завершаться по глобусу:

 #compdef vc _arguments '1:cheatsheets:_files -g "~/.cheat/*"' && return 0 

Это возвращает полностью определенные пути к файлу; если из каталога поиска требуется только голые имена файлов, мы можем вместо этого использовать:

 #compdef vc _arguments '1:cheatsheets:_files -W ~/.cheat' && return 0