I enjoy using Jim Blandy's dired-tar library for Emacs built on *nix systems: https://www.emacswiki.org/emacs/DiredTar

I am seeking to modify that library to support both *nix and MS Windows -- i.e., when eq system-type 'windows-nt. [Any assistance in adding that support would be greatly appreciated.]


2 Answers 2


Isn't the new compression support in Emacs 25 a superset of this feature? See http://oremacs.com/2015/10/23/dired-compress/ for details.

If not, please clarify which parts of dired-tar do not work on Windows systems.

  • I didn't realize new features included support for extracting/compressing .tar.gz. I'm doing a system backup this evening and won't be able to try it out until tomorrow. I had tried (unsuccessfully) modifying dired-tar with external executables for tar.exe, gzip.exe, etc. I look forward to testing out new built-in features. I didn't receive enough detailed error messages to debug further with dired-tar other than invalid argument, and realized it was going to be a major undertaking (setting up lots of messages and so forth to try and figure out where the problems were) . . .
    – lawlist
    Mar 25, 2016 at 5:58
  • No need to set up messages; you can use edebug; it's very easy, in fact. The compression support is not really built-in, in the sense that Emacs will still need to access an external tar binary. But in any case, if it doesn't work, you can make a bug report about it.
    – Clément
    Mar 25, 2016 at 17:09

@lawlist has not yet tested the built-in options for handling tar.gz files as of Emacs 25. That may be a valid option, but it differs from the call of the original question posed by @lawlist.

The following has been tested with Emacs -Q using the master branch (March 26, 2016) versions of Emacs built for OSX (10.6.8) and Windows (XP SP-3). The tests performed related to tar-gzipping a directory into filename.tar.gz and then extracting it back to its original state.

[The primary changes made by @lawlist have to do with the shell-file-name (which on Windows is cmdproxy.exe instead of /bin/sh) and the paths (which on Windows needs to contain backslashes instead of forward slashes when using cmd.exe -- e.g., cannot cd to a directory containing forward slashes). And, @lawlist prefers to use absolute paths when dealing with executables.]

;;  dired-tar.el -- written by Jim Blandy, and modified by @lawlist.
;;  As to packing/unpacking on a Windows box, @lawlist has chosen to use bsdtar
;;  from libarchive:  http://gnuwin32.sourceforge.net/packages/libarchive.htm
;;  The decision to use bsdtar was made because gzip sometimes complains about
;;  broken pipes when used in conjunction with tar on a Windows box.  There is a
;;  mention in the Emacs manual about said problems:
;;    http://www.gnu.org/software/emacs/manual/html_mono/efaq-w32.html
;;    You may see many messages from tar complaining about being unable to change
;;    the modification time on directories, and from gzip complaining about a
;;    broken pipe. These messages are usually harmless, caused by incomplete
;;    ports that are not fully aware of the limitations of Windows. 
;;  USAGE:  In a `dired-mode' buffer, place point on the compressed tar-gzipped
;;  file or directory to be tar-gzipped, and type:  M-x jim-blandy-pack-unpack

(require 'compile)

;;;; Variables.
(defvar jim-blandy-should-gzip t
  "*t if jim-blandy-pack-unpack should gzip the files it creates, by default.
nil if it should leave them alone.")

(defvar jim-blandy-gzip-extension ".tar.gz"
  "*File name extension to use for gzipped tar files.
By default, this is \".tar.gz\", but some people may like to use \".tgz\".")

(defvar jim-blandy-gzip-command
    ((eq system-type 'darwin)
      "/Users/HOME/.0.data/.0.emacs/macports/bin/gzip --best --stdout")
    ((eq system-type 'windows-nt)
      "c:\\progra~1\\GnuWin32\\bin\\gzip.exe --best --stdout"))
  "*A shell command which gzips its standard input to its standard output.")

(defvar jim-blandy-ungzip-command
    ((eq system-type 'darwin)
      "/Users/HOME/.0.data/.0.emacs/macports/bin/gzip --decompress --stdout")
    ((eq system-type 'windows-nt)
      "c:\\progra~1\\GnuWin32\\bin\\gzip.exe --decompress --stdout"))
  "*A shell command which ungzips its standard input to its standard output.")

(defvar jim-blandy-tar-command
    ((eq system-type 'darwin)
    ((eq system-type 'windows-nt)
  "The tar executable.")

(defvar jim-blandy-shell-file-name
    ((eq system-type 'darwin)
    ((eq system-type 'windows-nt)
      shell-file-name)) ;; absolute path to `cmdproxy.exe'
  "The name of the shell to use to run the tar command.
The default is /bin/sh, which should work for all Unix users; I'm told
Windows users may want to use \"4NT\", but I know nothing about Windows.")

(defvar jim-blandy-cmd-switches "vf"
  "Flags to pass to the tar program, in addition to the command charcaters.
This is concatenated with command characters (\"x\" or \"c\" or
whatever).  The default is 'vf'; I'm told Windows users should use

(defvar jim-blandy-result nil
  "For internal use by jim-blandy-dired-tar functions.
This variable is made local to the buffer in which we run the tar
process, and holds the name of the file created or affected.  The
process-termination sentinal uses this to update the dired listing
when the process completes its work, or dies.")

(defconst jim-blandy-tarfile-regexp
  (format "\\(%s\\)\\'"
    (mapconcat 'regexp-quote
         '(".tar" ".tar.z" ".tar.gz" ".tar.Z" ".tgz")
  "Regular expression matching plausible filenames for tar files.")

(defconst jim-blandy-gzipped-tarfile-regexp
  (format "\\(%s\\)\\'"
    (mapconcat 'regexp-quote
         '(".tar.z" ".tar.gz" ".tar.Z" ".tgz")
  "Regular expression matching plausible filenames for compressed tar files.")

(defun jim-blandy-get-buffer ()
  "Choose a buffer to run a tar process in.
Tar output buffers have names like *Tar*, *Tar*<2>, *Tar*<3>, ...
We return the lowest-numbered buffer that doesn't have a live tar
process in it.  We delete any other buffers whose processes have
  ;; Kill all completed tar buffers.
  (let ((number 1))
    (while number
      (let* (
            (if (<= number 1)
              (format "*Tar*<%d>" number)))
           (buf (get-buffer name)) )
        (if (null buf)
          (setq number nil)
            (set-buffer buf)
            (if (let ((process (get-buffer-process buf)))
                  (not (and process (eq (process-status process) 'run))))
              (kill-buffer buf)))
          (setq number (1+ number))))))
  ;; Make us a fresh buffer.
  (generate-new-buffer "*Tar*"))

(defun jim-blandy-operation-done (buf message)
  "Internal function for use by the jim-blandy-dired-tar package.
This function is run when the tar operation completes.  It tries to
update the dired listing by looking at jim-blandy-result."
    ((null jim-blandy-result))
    ((file-directory-p jim-blandy-result)
          (function (lambda (buf) (set-buffer buf)))
          (dired-buffers-for-dir jim-blandy-result))))
    ((file-exists-p jim-blandy-result)
      (dired-relist-file jim-blandy-result)) ))

(defun jim-blandy-pack (directory _prefix-arg)
  "Internal function for use by the jim-blandy-dired-tar package.
Create a gzipped tar file from the contents of DIRECTORY.
The archive is named after the directory, and the files are stored in
the archive with names relative to DIRECTORY's parent.
  We use `jim-blandy-gzip-extension' as the suffix for the filenames we
  For example, (jim-blandy-pack \"/home/blandy/womble/\") would produce a
tar file named \"/home/blandy/womble.tar.gz\", whose contents had
names like \"womble/foo\", \"womble/bar\", etcetera.
  The second argument PREFIX-ARG is ignored."
  (let* (
          ((eq system-type 'darwin)
           (directory-file-name directory))
          ((eq system-type 'windows-nt)
            (replace-regexp-in-string "/" "\\\\"
              (w32-short-file-name directory)))))
        (if jim-blandy-should-gzip
          (concat dir jim-blandy-gzip-extension)
          (format "%s.tar" dir)))
        (if jim-blandy-should-gzip
          (concat (file-name-nondirectory (directory-file-name directory))
          (format "%s.tar"
            (file-name-nondirectory (directory-file-name directory)))))
          ((eq system-type 'darwin)
            (file-name-directory dir))
          ((eq system-type 'windows-nt)
            (replace-regexp-in-string "/" "\\\\" (file-name-directory dir)))))
      (content-name (file-name-nondirectory (directory-file-name directory))) )
      (if jim-blandy-should-gzip
          ((eq system-type 'darwin)
            (format "%s cvf - %s | %s > %s"
          ((eq system-type 'windows-nt)
            (format "%s -zcvf %s %s"
        (format "%s cvf %s %s"

(defun jim-blandy-unpack (tar-file prefix-arg)
  "Internal function for use by the jim-blandy-dired-tar package.
Unpack TAR-FILE into the directory containing it.
If PREFIX-ARG is non-nil, just list the archive's contents without
unpacking it."
  (let* (
        (if (eq system-type 'windows-nt)
          (replace-regexp-in-string "/" "\\\\"
            (w32-short-file-name tar-file))
          ((eq system-type 'darwin)
            (file-name-directory filename))
          ((eq system-type 'windows-nt)
            (replace-regexp-in-string "/" "\\\\"
              (file-name-directory filename)))
            (file-name-directory filename))))
      (action (if prefix-arg "t" "x")) )
      ;; Does this look like a tar file at all?
      ((not (string-match jim-blandy-tarfile-regexp tar-file))
        (error "bug: jim-blandy-unpack should only be passed tar file names."))
      ;; Does it look like a compressed tar file?
      ((string-match jim-blandy-gzipped-tarfile-regexp tar-file)
          ((eq system-type 'darwin)
             (format "%s < %s | %s %s%s -"
          ((eq system-type 'windows-nt)
             ;; (format "%s < %s | %s %s%s -"
             ;;   jim-blandy-ungzip-command
             ;;   filename
             ;;   jim-blandy-tar-command
             ;;   action
             ;;   jim-blandy-cmd-switches)
             (format "%s %sz%s %s"
      ;; Okay, then it must look like an uncompressed tar file.
       (format "%s %svf %s" jim-blandy-tar-command action filename)))
     ;; If we're just unpacking the archive, don't bother updating the
     ;; dired listing.
     (if prefix-arg nil dir))))

(defun jim-blandy-run-command (command directory result)
  "Internal function for use by the jim-blandy-dired-tar package.
Run COMMAND asynchronously in its own window, like a compilation.
Use DIRECTORY as the default directory for the command's execution.
RESULT is the name of the tar file which will be created, or the
name of the directory into which the tar file was unpacked."
  (let ((buf (jim-blandy-get-buffer)))
      (set-buffer buf)
      (setq buffer-read-only nil)
      (goto-char (point-min))
      (insert "cd " directory)
      (insert command)
      (setq buffer-read-only t
        mode-name "Tar-Output"
        default-directory directory)
      (set (make-local-variable 'jim-blandy-result) result)
      (set (make-local-variable 'mode-line-process) '(": %s"))
      (set (make-local-variable 'compilation-finish-function)
      (let ((process
             (let ((shell-file-name jim-blandy-shell-file-name))
               (start-process-shell-command "*Tar*" buf command))))
          (lambda (p e) (when (= 0 (process-exit-status p))
            (let ((buffer (process-buffer p)))
              (if (null (buffer-name buffer))
                ;; buffer killed
                (set-process-buffer p nil)
                (with-current-buffer buffer
                  ;; Write something in the compilation buffer
                  ;; and hack its mode line.
                  (compilation-handle-exit (process-status p)
                         (process-exit-status p)
                  ;; Since the buffer and mode line will show that the
                  ;; process is dead, we can delete it now.  Otherwise it
                  ;; will stay around until M-x list-processes.
                  (delete-process p))
                (when (get-buffer-window buffer)
                  (sit-for 2)
                  (delete-window (get-buffer-window buffer)))
                (kill-buffer buffer))
              (setq compilation-in-progress (delq p compilation-in-progress)))
            (if (eq major-mode 'dired-mode)
      ;; The window will be deleted at the end if everything went okay.
      (display-buffer buf))))

(defun jim-blandy-pack-unpack (prefix-arg)
  "Create or unpack a tar archive for the file on the current line.
If the file on the current line is a directory, make a gzipped tar
file out of its contents.
  If the file on the current line is a tar archive, unpack it.  If the
archive appears to be gzipped or compressed, expand it first.  With a
prefix argument, just list the tar archive's contents, and don't
unpack it.  The file's name must end in \".tar\", \".tar.gz\", or
\".tar.Z\" or else this command will assume it's not a tar file."
  (interactive "P")
  (let ((filename (dired-get-filename)))
      ((file-directory-p filename)
        (jim-blandy-pack filename prefix-arg))
      ((string-match jim-blandy-tarfile-regexp filename)
        (jim-blandy-unpack filename prefix-arg))
        (error "%s is neither a tar file nor a directory" filename)))))


Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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