30

In emacs, I create a file by visiting it with C-x C-f. Let's say I'd like to create /home/myself/new_directory/file.txt.

If new_directory does not exist yet, is there a way to have it get created during the creation of file.txt without any extra steps? (I'm thinking of something like using the -p flag to mkdir in Linux.)

I feel like there's a different keystroke instead of C-x C-f that can do this, but I can't remember what it is.

3
  • There's no equivalent in emacs to lanch a command while editing?? in vi you could :!mkdir -p ~/new_dir/to/some/crazy/path Apr 16, 2010 at 4:28
  • 3
    @DaveParillo: Of course there is, M-! for instance. Oct 29, 2012 at 15:09
  • I use prelude (github.com/bbatsov/prelude) plugin. Whenever I create files like above, it prompts me with "Create directory ...". I can simply select "y". Next, it asks me "File does not exist, create? (y or n)". I select y, which creates a new file. When I save the file it creates the file with above information.
    – Pramod
    Apr 2, 2017 at 13:05

6 Answers 6

27

You can also advise function find-file to transparently create necessary directories.

(defadvice find-file (before make-directory-maybe (filename &optional wildcards) activate)
  "Create parent directory if not exists while visiting file."
  (unless (file-exists-p filename)
    (let ((dir (file-name-directory filename)))
      (unless (file-exists-p dir)
        (make-directory dir t)))))

Simply put this in your .emacs somewhere and use C-x C-f as usual.

3
  • 2
    Wonderful solution. I love how improving small things can open a whole new world of hints to give emacs to do stuff better (yeah, I didn't know about defadvice before). Oct 29, 2012 at 15:15
  • 1
    i suggest changing the last line to: (make-directory dir t), this way any parent dirs that need to be created will be created too.
    – ftravers
    Dec 9, 2019 at 15:26
  • Thanks for the suggestion @ftravers, I've updated the post.
    – viam0Zah
    Dec 13, 2019 at 18:09
18

When I supply a pathname with a nonexistent component, find-file (i.e. C-x C-f), gives me an extra message that says

Use M-x make-directory RET RET to create the directory and its parents

Since the file is not created until you first save the buffer, you can either run make-directory right after your new buffer comes up or you can do it any other time before you need to save the file. Then from the buffer that needs a directory, type M-x make-directory RET RET (it will prompt for the directory to create (the default is derived from the buffer's pathname); the second RET is to accept the default).

14

The Ido mode provides ido-find-file that is a replacement of find-file and gives you much more features. For instance, it allows you to create new directory meanwhile you open the file.

  • Type C-x C-f as usual (which is remapped to ido-find-file),

  • provide the non-existent path,

  • press M-m which will prompt for the new directory to create,

  • and then specify the file name to visit in the newly created directory.

2
  • I don't get it : why press M-m at some point, and C-x C-f at all if that doesn't create everything automagically ? If to be prompted for the directory to create, M-! mkdir or dired will do a fair job too… Oct 29, 2012 at 15:13
  • Is there a way to have ido-find-file automagically create the directory? Or even better, ask me if I want to create it? I tried using the advice in Török Gábor's answer, but I couldn't figure out which function to apply it to (as ido is not calling find-file directly. Jun 30, 2016 at 15:58
1

My solution comes with a bonus: if you kill the buffer without saving it, Emacs will offer to delete those empty directories that were created (but only if they didn't exist before you invoked find-file):

;; Automatically create any nonexistent parent directories when
;; finding a file. If the buffer for the new file is killed without
;; being saved, then offer to delete the created directory or
;; directories.

(defun radian--advice-find-file-automatically-create-directory
    (original-function filename &rest args)
  "Automatically create and delete parent directories of files.
This is an `:override' advice for `find-file' and friends. It
automatically creates the parent directory (or directories) of
the file being visited, if necessary. It also sets a buffer-local
variable so that the user will be prompted to delete the newly
created directories if they kill the buffer without saving it."
  ;; The variable `dirs-to-delete' is a list of the directories that
  ;; will be automatically created by `make-directory'. We will want
  ;; to offer to delete these directories if the user kills the buffer
  ;; without saving it.
  (let ((dirs-to-delete ()))
    ;; If the file already exists, we don't need to worry about
    ;; creating any directories.
    (unless (file-exists-p filename)
      ;; It's easy to figure out how to invoke `make-directory',
      ;; because it will automatically create all parent directories.
      ;; We just need to ask for the directory immediately containing
      ;; the file to be created.
      (let* ((dir-to-create (file-name-directory filename))
             ;; However, to find the exact set of directories that
             ;; might need to be deleted afterward, we need to iterate
             ;; upward through the directory tree until we find a
             ;; directory that already exists, starting at the
             ;; directory containing the new file.
             (current-dir dir-to-create))
        ;; If the directory containing the new file already exists,
        ;; nothing needs to be created, and therefore nothing needs to
        ;; be destroyed, either.
        (while (not (file-exists-p current-dir))
          ;; Otherwise, we'll add that directory onto the list of
          ;; directories that are going to be created.
          (push current-dir dirs-to-delete)
          ;; Now we iterate upwards one directory. The
          ;; `directory-file-name' function removes the trailing slash
          ;; of the current directory, so that it is viewed as a file,
          ;; and then the `file-name-directory' function returns the
          ;; directory component in that path (which means the parent
          ;; directory).
          (setq current-dir (file-name-directory
                             (directory-file-name current-dir))))
        ;; Only bother trying to create a directory if one does not
        ;; already exist.
        (unless (file-exists-p dir-to-create)
          ;; Make the necessary directory and its parents.
          (make-directory dir-to-create 'parents))))
    ;; Call the original `find-file', now that the directory
    ;; containing the file to found exists. We make sure to preserve
    ;; the return value, so as not to mess up any commands relying on
    ;; it.
    (prog1 (apply original-function filename args)
      ;; If there are directories we want to offer to delete later, we
      ;; have more to do.
      (when dirs-to-delete
        ;; Since we already called `find-file', we're now in the buffer
        ;; for the new file. That means we can transfer the list of
        ;; directories to possibly delete later into a buffer-local
        ;; variable. But we pushed new entries onto the beginning of
        ;; `dirs-to-delete', so now we have to reverse it (in order to
        ;; later offer to delete directories from innermost to
        ;; outermost).
        (setq-local radian--dirs-to-delete (reverse dirs-to-delete))
        ;; Now we add a buffer-local hook to offer to delete those
        ;; directories when the buffer is killed, but only if it's
        ;; appropriate to do so (for instance, only if the directories
        ;; still exist and the file still doesn't exist).
        (add-hook 'kill-buffer-hook
                  #'radian--kill-buffer-delete-directory-if-appropriate
                  'append 'local)
        ;; The above hook removes itself when it is run, but that will
        ;; only happen when the buffer is killed (which might never
        ;; happen). Just for cleanliness, we automatically remove it
        ;; when the buffer is saved. This hook also removes itself when
        ;; run, in addition to removing the above hook.
        (add-hook 'after-save-hook
                  #'radian--remove-kill-buffer-delete-directory-hook
                  'append 'local)))))

;; Add the advice that we just defined.
(advice-add #'find-file :around
            #'radian--advice-find-file-automatically-create-directory)

;; Also enable it for `find-alternate-file' (C-x C-v).
(advice-add #'find-alternate-file :around
            #'radian--advice-find-file-automatically-create-directory)

;; Also enable it for `write-file' (C-x C-w).
(advice-add #'write-file :around
            #'radian--advice-find-file-automatically-create-directory)

(defun radian--kill-buffer-delete-directory-if-appropriate ()
  "Delete parent directories if appropriate.
This is a function for `kill-buffer-hook'. If
`radian--advice-find-file-automatically-create-directory' created
the directory containing the file for the current buffer
automatically, then offer to delete it. Otherwise, do nothing.
Also clean up related hooks."
  (when (and
         ;; Stop if there aren't any directories to delete (shouldn't
         ;; happen).
         radian--dirs-to-delete
         ;; Stop if `radian--dirs-to-delete' somehow got set to
         ;; something other than a list (shouldn't happen).
         (listp radian--dirs-to-delete)
         ;; Stop if the current buffer doesn't represent a
         ;; file (shouldn't happen).
         buffer-file-name
         ;; Stop if the buffer has been saved, so that the file
         ;; actually exists now. This might happen if the buffer were
         ;; saved without `after-save-hook' running, or if the
         ;; `find-file'-like function called was `write-file'.
         (not (file-exists-p buffer-file-name)))
    (cl-dolist (dir-to-delete radian--dirs-to-delete)
      ;; Ignore any directories that no longer exist or are malformed.
      ;; We don't return immediately if there's a nonexistent
      ;; directory, because it might still be useful to offer to
      ;; delete other (parent) directories that should be deleted. But
      ;; this is an edge case.
      (when (and (stringp dir-to-delete)
                 (file-exists-p dir-to-delete))
        ;; Only delete a directory if the user is OK with it.
        (if (y-or-n-p (format "Also delete directory `%s'? "
                              ;; The `directory-file-name' function
                              ;; removes the trailing slash.
                              (directory-file-name dir-to-delete)))
            (delete-directory dir-to-delete)
          ;; If the user doesn't want to delete a directory, then they
          ;; obviously don't want to delete any of its parent
          ;; directories, either.
          (cl-return)))))
  ;; It shouldn't be necessary to remove this hook, since the buffer
  ;; is getting killed anyway, but just in case...
  (radian--remove-kill-buffer-delete-directory-hook))

(defun radian--remove-kill-buffer-delete-directory-hook ()
  "Clean up directory-deletion hooks, if necessary.
This is a function for `after-save-hook'. Remove
`radian--kill-buffer-delete-directory-if-appropriate' from
`kill-buffer-hook', and also remove this function from
`after-save-hook'."
  (remove-hook 'kill-buffer-hook
               #'radian--kill-buffer-delete-directory-if-appropriate
               'local)
  (remove-hook 'after-save-hook
               #'radian--remove-kill-buffer-delete-directory-hook
               'local))

Canonical version here.

0

In addition to @Chris' suggestion of M-x make-directory, you can write a short elisp function that will do find-file and then make-directory... You can try this:

(defun bp-find-file(filename &optional wildcards)
  "finds a file, and then creates the folder if it doesn't exist"

  (interactive (find-file-read-args "Find file: " nil))
  (let ((value (find-file-noselect filename nil nil wildcards)))
    (if (listp value)
    (mapcar 'switch-to-buffer (nreverse value))
      (switch-to-buffer value)))
  (when (not (file-exists-p default-directory))
       (message (format "Creating  %s" default-directory))
       (make-directory default-directory t))
  )

It's not pretty, and it flashes up the "it M-X make-directory...." before saying "Creating directory..." but if nothing else, it should give you a start.

3
  • 2
    In case of this approach, it's better to advice the original find-file function instead of defining a new one so that other functions calling find-file directly can even benefit of the modified behavior.
    – viam0Zah
    Apr 16, 2010 at 14:44
  • but find-file doesn't seem to take any arguments that can tell it to do this... Unless there's something useful that you can do in find-file-hooks... Apr 16, 2010 at 15:29
  • I meant this: superuser.com/questions/131538/…
    – viam0Zah
    Apr 20, 2010 at 8:27
0
(make-directory "~/bunch/of/dirs" t)

If your directories already exist, it will just raise a warning.

From the (C-h f make-directory RET) manual:

make-directory is an interactive compiled Lisp function.

(make-directory DIR &optional PARENTS)

Create the directory DIR and optionally any nonexistent parent dirs. If DIR already exists as a directory, signal an error, unless PARENTS is non-nil.

Interactively, the default choice of directory to create is the current buffer’s default directory. That is useful when you have visited a file in a nonexistent directory.

Noninteractively, the second (optional) argument PARENTS, if non-nil, says whether to create parent directories that don’t exist. Interactively, this happens by default.

If creating the directory or directories fail, an error will be raised.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .