Getting the completion system to present only files for every invocation of
tar
is relatively straightforward. This zstyle
command added to .zshrc
will do the trick:
zstyle ':completion:*:*:tar:*' 'file-patterns' '*(.)'
This triggers code in the _files
builtin function that restricts the
file pattern match to plain files only, via the (.)
glob qualifier.
Unfortunately, it overrides the pattern-matching for all tar
invocations, so
the extension-based matching in your example no longer works, and directories
are not part of the matching when creating an archive. Changing zsh
to
ignore directories more selectively involves getting a bit deeper into the
completion code.
We can accomplish this by modifying the _tar_archive
function (thanks to
@Gilles for pointing out how). That function already has separate handling
for extracting archives and creating archives, so we just need to change the
lines for extracting archives.
This is the modified code that does that, based on the code from my system
(macOS 10.15, zsh 5.7.1). It calls _path_files
directly in the extraction
cases, so that it only looks at files. The original code used _files
, which
looped through both files and directories.
#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
Modifying the functions in the completion system can be slightly tricky. It's
probably best to create copies rather than changing the release code in the
original directories. For a quick reference, these are the commands that were
needed on my system; other systems may have different requirements. There
are more details in this Stack Exchange answer:
https://unix.stackexchange.com/a/33898/432774
One time changes:
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)
These commands are needed after every code change. Note that just restarting
the shell may not be sufficient:
unfunction _tar_archive
autoload -Uz _tar_archive
rm ~/.zcompdump
rm ~/.zcompcache/*
compinit
tar xf foo/file.tar.xz
ortar xf ~/Downloads/file.tgz
)? – Stéphane Chazelas Feb 08 '21 at 17:05cd
to the location and then extract the file. – Martin Vegter Feb 08 '21 at 18:15