zsh 完成:使用 tar 提取存档时不提供目录

400*_*Cat 5 shell zsh autocomplete

tar命令的完成系统非常聪明。当我有不同的存档类型时,它会根据使用的 tar 选项提供相关文件:

$ ls
file.tar.xz
file.tar.gz

$ tar xJf f<TAB>
file.tar.xz

$ tar xzf f<TAB>
file.tar.gz
Run Code Online (Sandbox Code Playgroud)

它识别xJf为作用于tar.xz文件,并xzf作用于tar.gz文件。

但是当当前位置存在目录时,它也会提供它以供完成:

$ tar xJf f<TAB>
foo/   file.tar.xz
Run Code Online (Sandbox Code Playgroud)

在提取档案(即tar选项x...)时,我可以告诉完成系统我只想完成文件吗?

但是在创建档案时它仍然应该提供目录(tar选项c...):

tar cpJf foo.tar.xz 
Run Code Online (Sandbox Code Playgroud)

我认为这将需要修改tarcommand:的主要完成文件/usr/share/zsh/functions/Completion/Unix/_tar。但是如何?

Gai*_*owl 3

让完成系统只为每次调用 提供文件tar相对简单。zstyle添加此命令.zshrc 即可解决问题:

zstyle ':completion:*:*:tar:*' 'file-patterns' '*(.)'
Run Code Online (Sandbox Code Playgroud)

这会触发内置函数中的代码_files,通过 glob 限定符将文件模式匹配仅限于纯文件(.)

不幸的是,它会覆盖所有调用的模式匹配tar,因此示例中基于扩展名的匹配不再起作用,并且在创建存档时目录不是匹配的一部分。更改zsh为更有选择性地忽略目录涉及更深入地了解完成代码。


我们可以通过修改_tar_archive函数来实现这一点(感谢@Gilles 指出如何实现)。该函数已经对提取档案和创建档案进行了单独的处理,因此我们只需要更改用于提取档案的行即可。

这是基于我的系统(macOS 10.15、zsh 5.7.1)中的代码修改后的代码。它直接在提取案例中调用_path_files,因此它只查看文件。原始代码使用_files,它循环遍历文件和目录。

#autoload

# This is used to generate filenames usable as a tar archive.  This may
# get one argument, a collection of tar option characters that may be
# used to find out what kind of filename is needed.  If no argument is
# given but the parameter `_tar_cmd' is set, that is used.
# If your version of `tar' supports this you may want to complete
# things like `host:file' or `user@host:file' here.

local expl

[[ $# -eq 0 && $+_tar_cmd -ne 0 ]] && set "$_tar_cmd"

_description files expl 'archive file'

if [[ "$1" = *[urtx]* ]]; then
  if [[ "$1" = *[zZ]* ]]; then
    _path_files "$expl[@]" -g '*.((tar|TAR).(gz|GZ|Z)|tgz)(-.)'
  elif [[ "$1" = *[Ijy]* ]]; then
    _path_files "$expl[@]" -g '*.(tar|TAR).bz2(-.)'
  elif [[ "$1" = *J* ]]; then
    _path_files "$expl[@]" -g '*.(tar|TAR).(lzma|xz)(-.)'
  elif [[ "$_cmd_variant[$service]" == (gnu|libarchive) ]]; then
    _path_files "$expl[@]" -g '*.((tar|TAR)(.gz|.GZ|.Z|.bz2|.lzma|.xz|)|(tbz|tgz|txz))(-.)'
  else
    _path_files "$expl[@]" -g '*.(tar|TAR)(-.)'
  fi
else
  _files "$expl[@]"
fi
Run Code Online (Sandbox Code Playgroud)

修改完成系统中的功能可能有点棘手。最好创建副本,而不是更改原始目录中的发布代码。为了快速参考,这些是我的系统所需的命令;其他系统可能有不同的要求。此 Stack Exchange 答案中有更多详细信息: https://unix.stackexchange.com/a/33898/432774

一次改变:

mkdir ~/.zfunc
for d in $fpath; do
  [[ -f $d/_tar_archive ]] && cp $d/_tar_archive ~/.zfunc; break
done
# (edit file as noted above)

print 'fpath=( ~/.zfunc "${fpath[@]}" )' >> ~/.zshrc
# (restart shell)
Run Code Online (Sandbox Code Playgroud)

每次代码更改后都需要这些命令。请注意,仅重新启动 shell 可能还不够:

unfunction _tar_archive
autoload -Uz _tar_archive

rm ~/.zcompdump
rm ~/.zcompcache/*
compinit
Run Code Online (Sandbox Code Playgroud)