5

the completion system for tar command is quite clever. When I have different archive types, it offers the relevant files depending on tar options used:

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

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

$ tar xzf f<TAB> file.tar.gz

It recognizes xJf as acting on tar.xz files, and xzf acting on tar.gz files.

But when directory exists in current location, it offers it for completion as well :

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

Can I tell the completion system, that I only want to complete files, when extracting archives (ie, tar option x...) ?

But it should still offer directories when creating archives (tar option c...):

tar cpJf foo.tar.xz 

I assume this will need modifying the main completion file for tar command: /usr/share/zsh/functions/Completion/Unix/_tar. But how?

Martin Vegter
  • 358
  • 75
  • 236
  • 411

1 Answers1

3

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' oruser@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

Gairfowl
  • 523
  • I have applied the changes to _files and _tar_archive (I also have zsh version 5.7.1). But when I do: tar xpJf f<TAB>, it still offers me directories as well as files. – Martin Vegter Feb 11 '21 at 04:08
  • @400theCat It took me a few tries to get the new code recognized, and I may not have transcribed every step correctly. Try adding debug printouts, like date >> ~/log.txt, to the functions to make sure you're reaching the modified code. – Gairfowl Feb 11 '21 at 06:37