1

emacs 26.1

dired+

I have dir my_dir. This dir contains 10 subdirs. In every of those dirs there are 2 subdirs:

  • src
  • build

I need to copy in DIRED MODE all dirs and files from my_dir to my_dir2 and exclude all subdirs with name build.

Is it possible?

P.S. subdirs build content thousands files. So copy without this folders is much faster.

P.P.S. Here approach how I can do this in Total Commander.

I use file types like this:

*.* | build\

enter image description here

As result while copying , then copy all dirs exclude all subdirs build. It's very simple and comfortably.

Is it possible this approach same on Emacs ?

2 Answers 2

1

First a method that works with vanilla dired+:

  1. Go to my_dir.
  2. Mark all by placing point on the first line and pressing m in the dired buffer.
  3. Copy the marked files to the destination my_dir2 with C and giving the right target directory.
  4. Go to my_dir2.
  5. Filter the unwanted directories by C-x d with filter */build.
  6. Mark all shown directories and delete them with C-x h D.

Comment

The clear disadvantage of this method is that copy the unwanted directories and delete them afterwards. That can be really bad if those directories are big and you are working on SSD drives.

The problem with selecting all */src directories in my_dir and copying them is that the source file names are stripped off their path in dired-do-create-files by file-name-nondirectory. If 0, ..., 9 are the sub-directories of my_dir then src of 0/src, src of 1/src ... all end up as my_dir2/src.

2
  • I need exclude subdirs "build" while copy dir. Because without many subdirs "build" the copy of dir "my_dir" is much faster.
    – Alex
    Jul 21, 2019 at 9:26
  • 1
    @Alex In that case you should take the alternative approach. Just copy the Elisp stuff to your init file, reload Emacs, and go through the steps given below the code afterwards.
    – Tobias
    Jul 21, 2019 at 13:46
1

The second method I propose bases on tweaking dired(+) such that copying of relative paths like 0/src, ..., 9/src from my_dir to my_dir2 becomes possible.

The main problem with vanilla dired+ is already described in my other answer.

The following Elisp source code optionally replaces file-name-nondirectory with file-relative-name in dired-do-create-files. One can switch back to the original behavior by setting the customization option dired-copy-relative-src to nil. One also needs to give copy-directory in dired-copy-file-recursive a non-nil PARENT argument to accept the relative paths passed from dired-do-create-files.

(defcustom dired-copy-relative-src t
  "Use the relative source file name as target for copying."
  :group 'dired
  :type 'boolean)

(require 'dired-aux)

(defun dired-do-create-files (op-symbol file-creator operation arg
                    &optional marker-char op1
                    how-to)
  "Create a new file for each marked file.
Prompt user for a target directory in which to create the new
  files.  The target may also be a non-directory file, if only
  one file is marked.  The initial suggestion for target is the
  Dired buffer's current directory (or, if `dired-dwim-target' is
  non-nil, the current directory of a neighboring Dired window).
OP-SYMBOL is the symbol for the operation.  Function `dired-mark-pop-up'
  will determine whether pop-ups are appropriate for this OP-SYMBOL.
FILE-CREATOR and OPERATION as in `dired-create-files'.
ARG as in `dired-get-marked-files'.
Optional arg MARKER-CHAR as in `dired-create-files'.
Optional arg OP1 is an alternate form for OPERATION if there is
  only one file.
Optional arg HOW-TO determines how to treat the target.
  If HOW-TO is nil, use `file-directory-p' to determine if the
   target is a directory.  If so, the marked file(s) are created
   inside that directory.  Otherwise, the target is a plain file;
   an error is raised unless there is exactly one marked file.
  If HOW-TO is t, target is always treated as a plain file.
  Otherwise, HOW-TO should be a function of one argument, TARGET.
   If its return value is nil, TARGET is regarded as a plain file.
   If it return value is a list, TARGET is a generalized
    directory (e.g. some sort of archive).  The first element of
    this list must be a function with at least four arguments:
      operation - as OPERATION above.
      rfn-list  - list of the relative names for the marked files.
      fn-list   - list of the absolute names for the marked files.
      target    - the name of the target itself.
    The rest of elements of the list returned by HOW-TO are optional
    arguments for the function that is the first element of the list.
   For any other return value, TARGET is treated as a directory."
  (or op1 (setq op1 operation))
  (let* ((fn-list (dired-get-marked-files nil arg))
     (rfn-list (mapcar #'dired-make-relative fn-list))
     (dired-one-file    ; fluid variable inside dired-create-files
      (and (consp fn-list) (null (cdr fn-list)) (car fn-list)))
     (target-dir (dired-dwim-target-directory))
     (default (and dired-one-file
               (not dired-dwim-target) ; Bug#25609
               (expand-file-name (file-name-nondirectory (car fn-list))
                     target-dir)))
     (defaults (dired-dwim-target-defaults fn-list target-dir))
     (target (expand-file-name ; fluid variable inside dired-create-files
          (minibuffer-with-setup-hook
              (lambda ()
            (set (make-local-variable 'minibuffer-default-add-function) nil)
            (setq minibuffer-default defaults))
            (dired-mark-read-file-name
             (concat (if dired-one-file op1 operation) " %s to: ")
             target-dir op-symbol arg rfn-list default))))
     (into-dir (cond ((null how-to)
              ;; Allow users to change the letter case of
              ;; a directory on a case-insensitive
              ;; filesystem.  If we don't test these
              ;; conditions up front, file-directory-p
              ;; below will return t on a case-insensitive
              ;; filesystem, and Emacs will try to move
              ;; foo -> foo/foo, which fails.
              (if (and (file-name-case-insensitive-p (car fn-list))
                   (eq op-symbol 'move)
                   dired-one-file
                   (string= (downcase
                         (expand-file-name (car fn-list)))
                        (downcase
                         (expand-file-name target)))
                   (not (string=
                     (file-name-nondirectory (car fn-list))
                     (file-name-nondirectory target))))
                  nil
                (file-directory-p target)))
             ((eq how-to t) nil)
             (t (funcall how-to target)))))
    (if (and (consp into-dir) (functionp (car into-dir)))
    (apply (car into-dir) operation rfn-list fn-list target (cdr into-dir))
      (if (not (or dired-one-file into-dir))
      (error "Marked %s: target must be a directory: %s" operation target))
      ;; rename-file bombs when moving directories unless we do this:
      (or into-dir (setq target (directory-file-name target)))
      (dired-create-files
       file-creator operation fn-list
       (if into-dir         ; target is a directory
       ;; This function uses fluid variable target when called
       ;; inside dired-create-files:
       `(lambda (from)
          (expand-file-name ,(if dired-copy-relative-src
                     `(file-relative-name from ,default-directory) ;; CHANGE: Use `file-relative-name' instead of `file-name-nondirectory'
                   '(file-name-nondirectory from))
                target))
     (lambda (_from) target))
       marker-char))))

(defun dired-copy-file-recursive (from to ok-flag &optional
                       preserve-time top recursive)
  (when (and (eq t (car (file-attributes from)))
         (file-in-directory-p to from))
    (error "Cannot copy `%s' into its subdirectory `%s'" from to))
  (let ((attrs (file-attributes from)))
    (if (and recursive
         (eq t (car attrs))
         (or (eq recursive 'always)
         (yes-or-no-p (format "Recursive copies of %s? " from))))
    (copy-directory from to preserve-time 'parents) ;; CHANGE: added 'parents
      (or top (dired-handle-overwrite to))
      (condition-case err
      (if (stringp (car attrs))
          ;; It is a symlink
          (make-symbolic-link (car attrs) to ok-flag)
        (copy-file from to ok-flag preserve-time))
    (file-date-error
     (push (dired-make-relative from)
           dired-create-files-failures)
     (dired-log "Can't set date on %s:\n%s\n" from err))))))

If you have installed the above Elisp code you can copy what you want as follows:

  1. Go to my_dir
  2. Copy all regular files by the following steps:
    1. Mark all by C-x h *m
    2. Unmark directories with C-u */
    3. Copy the marked files with C into my_dir2
  3. Copy the directories without */build from my_dir to my_dir2 as follows:
    1. Filter files in sub-directories by C-x d */*
    2. Mark all by C-x h *m
    3. Unmark unwanted directories by C-u %m /build
    4. Copy the marked stuff by C

Test conditions:

  • emacs-version: GNU Emacs 26.2 (build 2, i686-pc-linux-gnu, GTK+ Version 3.22.30) of 2019-04-12
  • started with emacs -Q and M-x package-initialize
  • dired+.Version: 21.2 but dired-copy-file-recursive and dired-do-create-files are from vanilla dired
3
  • @Drew Hi Drew, Does dired+ already have the possibility to copy the source recursive (inclusively the directory structure) and thereby leaving out some subfolders? Is something like I describe possible? I.e., listing the files and directories with their relative paths w.r.t. default-directory, marking some files there and copying the marked stuff inclusively their relative paths into another base-directory?
    – Tobias
    Jul 22, 2019 at 8:22
  • (Replied to the same question in your email, this morning.) No, it doesn't currently. Would maybe be good to add an optional PREDICATE arg to some more of the Dired functions.
    – Drew
    Jul 23, 2019 at 1:13
  • @Tobias I updated my post. Is it possible same approach on Emacs?
    – Alex
    Jul 23, 2019 at 7:15

Your Answer

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

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