I found this function by xah to do this but it also looks at the parent directories. Ideally, this would just look at the filename not including the parent name or parent directories. Something along the lines of this regex: ^\\(.+\\)*(.+)\.(.+)$ where it would just take group \2 for replacing the spaces with hyphens instead of the whole path.

(defun xah-dired-rename-space-to-underscore ()
  "In dired, rename current or marked files by replacing space to underscore _.
If not in `dired', do nothing.
URL `http://ergoemacs.org/emacs/elisp_dired_rename_space_to_underscore.html'
Version 2017-01-02"
  (require 'dired-aux)
  (if (equal major-mode 'dired-mode)
        (mapc (lambda (x)
                (when (string-match " " x )
                  (dired-rename-file x (replace-regexp-in-string " " "_" x) nil)))
              (dired-get-marked-files ))
    (user-error "Not in dired.")))


I'd use wdired (writable dired) for this. In a dired buffer, type C-x C-q to enter wdired mode, change the names of the files as desired using normal emacs editing commands, then C-c C-c to apply the changes, which will cause the files to be renamed. See Wdired for more details.

If you want to continue using your function as-is, pass the 'no-dir argument to dired-get-marked-files, i.e.

(dired-get-marked-files 'no-dir)
  • I know about writeable dired but it's quite convenient and I am used to using this function now. I just realized it doesn't work with spaces in the directory names.
    – J Spen
    Jan 31 '19 at 11:42
  • 1
    Use (dired-get-marked-files 'no-dir) in that case
    – rpluim
    Jan 31 '19 at 12:51
  • @rpluim: Please post your comment as a second answer or as a second part of your current answer. State that all you need to do is put that sexp in place of (dired-get-marked-files) in the OP's code.
    – Drew
    Jan 31 '19 at 15:40
  • @Drew @rpluim - I have tried the (dired-get-marked files 'no-dir) which works but not if you've inserted subdirectories as only the top-level path of all the subdirectories is taken. So again you will get an error that it can't find the path in the sub-directories. Also, even if it works correctly in the primary directory, this error comes up: dired-fun-in-all-buffers: Wrong type argument: stringp, nil
    – J Spen
    Feb 1 '19 at 4:03
  • @Drew @rpluim I think I can use string-match: (string-match "^.*?/" str) but not sure how to call this with match-end or another function to just operate the replacement on the file portion.
    – J Spen
    Feb 1 '19 at 7:26

This not only fixes the function to correctly rename files in both windows and unix based systems but also keeps the rename files that were marked still marked after renaming them. Also only moves forward to the next line if no files were marked or stays at the point where the command was run.

(defun jj/dired-rename-space-to-underscore ()
  "In dired, rename current or marked files by replacing space to underscore _.
If not in `dired', do nothing. Adapted from:
URL `http://ergoemacs.org/emacs/elisp_dired_rename_space_to_underscore.html'
Version 2016-12-22"
  (require 'f)
  (require 'dired-aux)
  (if (equal major-mode 'dired-mode)
    (let ((string-not-matched t)
          (new-names '())
          (p (point))
          (number-marked-files (length (dired-get-marked-files nil nil nil t))))
      (mapc (lambda (x)
          (let* ((dired-rename-file-split (f-split x))
             (just-file-name (car (last dired-rename-file-split))))
            (when (string-match " " just-file-name)
              ;; replace last element in file-name-split with the replaced regex
              (setcar (last dired-rename-file-split) (replace-regexp-in-string " " "_" just-file-name))
              ;; rejoin the file-name-split with / and the new file name
              (dired-rename-file x (apply 'f-join dired-rename-file-split)  nil)
              (push (apply 'f-join dired-rename-file-split) new-names)
              ;; if filename matched won't go forward line as already does this
              (setq string-not-matched nil))))
      (when (not (eql 1 number-marked-files))
        (loop for f in new-names do
          (dired-goto-file f)
          (dired-mark nil)))
      ;; move forward one line if dired didn't do rename (this is done automatically if rename was performed)
      ;; only do the below when length of marked files is 1 or doesn't make sense to move forward by lines
      (goto-char p)
      (when (eql 1  number-marked-files)
        (dired-next-line 1))))
    (user-error "Not in dired")))

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.