zsh 補完関数内で一重引用符を含むファイル名をどのように処理しますか?

zsh 補完関数内で一重引用符を含むファイル名をどのように処理しますか?

私はzshシェルを使用しており、 内に次のコードがあります.zshrc:

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

autoload -Uz compinit
compinit

~/.zsh/completion最初の行は、環境変数内に格納されている配列にパスを追加しますfpath

_vc2 行目は、 というカスタム シェル関数の という補完関数をロードします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ここで、コマンドの後に を押すと、zsh はを含むvc内部のファイルを提案します(zsh は一重引用符を自動的にエスケープしているようです)。~/.cheatfoo\'bar

しかし、それがどのように、またなぜ機能するのか理解できません。一重引用符を使用すると、変数cheatsheetsにはリテラル文字列が含まれる必要$(...)があります。したがって、コマンド置換は展開されません。たとえば、次のコマンドを実行するとします。

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

では、'$(ls ~/.cheat)'の内部で が展開されるのはなぜでしょうか_arguments "1:cheatsheets:(${cheatsheets})" && return 0? また、 の内部で一重引用符が自動的にエスケープされるのはなぜでしょうかfoo'bar?

答え1

間違ったやり方を続けると

#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
% 

しかし、おそらくローカル配列は必要なく、_filesglob で完了できます。

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

これは完全修飾ファイル パスを返します。検索ディレクトリからファイル名のみが必要な場合は、代わりに以下を使用できます。

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

関連情報