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?

2
  • You mean you never extract archives that are not in the current working directory (as in tar xf foo/file.tar.xz or tar xf ~/Downloads/file.tgz)? – Stéphane Chazelas Feb 8 at 17:05
  • @Stéphane Chazelas - exactly. I cd to the location and then extract the file. – 400 the Cat Feb 8 at 18:15
3
+200

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
2
  • 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. – 400 the Cat Feb 11 at 4: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 at 6:37

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.