Vim: Creating parent directories on save
Solution 1
augroup BWCCreateDir
autocmd!
autocmd BufWritePre * if expand("<afile>")!~#'^\w\+:/' && !isdirectory(expand("%:h")) | execute "silent! !mkdir -p ".shellescape(expand('%:h'), 1) | redraw! | endif
augroup END
Note the conditions: expand("<afile>")!~#'^\w\+:/'
will prevent vim from creating directories for files like ftp://*
and !isdirectory
will prevent expensive mkdir call.
Update: sligtly better solution that also checks for non-empty buftype and uses mkdir()
:
function s:MkNonExDir(file, buf)
if empty(getbufvar(a:buf, '&buftype')) && a:file!~#'\v^\w+\:\/'
let dir=fnamemodify(a:file, ':h')
if !isdirectory(dir)
call mkdir(dir, 'p')
endif
endif
endfunction
augroup BWCCreateDir
autocmd!
autocmd BufWritePre * :call s:MkNonExDir(expand('<afile>'), +expand('<abuf>'))
augroup END
Solution 2
Based on the suggestions to my question, here's what I ended up with:
function WriteCreatingDirs()
execute ':silent !mkdir -p %:h'
write
endfunction
command W call WriteCreatingDirs()
This defines the :W
command. Ideally, I'd like to have all of :w!
, :wq
, :wq!
, :wall
etc work the same, but I'm not sure if it's possible without basically reimplementing them all with custom functions.
Solution 3
This code will prompt you to create the directory with :w
, or just do it with :w!
:
augroup vimrc-auto-mkdir
autocmd!
autocmd BufWritePre * call s:auto_mkdir(expand('<afile>:p:h'), v:cmdbang)
function! s:auto_mkdir(dir, force)
if !isdirectory(a:dir)
\ && (a:force
\ || input("'" . a:dir . "' does not exist. Create? [y/N]") =~? '^y\%[es]$')
call mkdir(iconv(a:dir, &encoding, &termencoding), 'p')
endif
endfunction
augroup END
Solution 4
I added this to my ~/.vimrc
cnoremap mk. !mkdir -p <c-r>=expand("%:h")<cr>/
If I need to create the directory I'm in I type :mk.
and it replaces that with "!mkdir -p /path/to/my/file/" and allows me to review the command before I invoke it.
Solution 5
I think I managed to do this in three lines, combining what others are saying on this answer.
This seems to do the trick:
if has("autocmd")
autocmd BufWritePre * :silent !mkdir -p %:p:h
end
It attempts to create the folder automatically when saving a buffer. If anything bad happens (i.e. permission issues) it will just shut up and let the file write fail.
If anyone sees any obvious flaws, please post a comment. I'm not very versed in vimscript.
EDIT: Notes thanks to ZyX
- This will not work if your folders have spaces on them (apparently they are not properly escaped or something)
- Or if you are doing pseudo files.
- Or if you are sourcing your vimrc.
- But son, it is short.
Damien Pollet
Associate professor, researcher in reengineering and programming languages. Frequently used languages: Smalltalk (Pharo), Ruby, LaTeX…
Updated on March 18, 2020Comments
-
Damien Pollet over 4 years
If I invoke
vim foo/bar/somefile
butfoo/bar
don't already exist, Vim refuses to save.I know I could switch to a shell or do
:!mkdir foo/bar
from Vim but I'm lazy :) Is there a way to make Vim do that automatically when it saves the buffer? -
Damien Pollet over 13 yearsThanks, seems much cleaner than what I guess-hacked :)
-
Marius Gedminas almost 12 years
call mkdir(expand('%:h'), 'p')
might be more portable. -
kikito almost 12 years@MariusGedminas I'd like to see the complete code with that change. Could you please post it as an answer / upload it somewhere?
-
ZyX almost 12 years@kikito See my answer. It was edited a few hours after that comment.
-
kikito almost 12 years@Zyx thanks! I ended up doing something a bit shorter (my answer is currently the last one) but it seems to do the trick nicely.
-
ZyX almost 12 yearsNever use
%
in such scripts. Vim is not going to escape any special symbols: for example, if you are editing a file named/mnt/windows/Documents and Settings/User/_vimrc
you will end up having four new directories:/mnt/windows/Documents
,./and
,./Settings
and./Settings/User
. And, by the way, you don’t need:execute
here. -
ZyX almost 12 yearsThere is
system()
function for completely silent shell calls, but you don’t need both:execute
and%:p:h
::silent !mkdir -p %:p:h
works exactly as what you have wrote (though you may need:redraw!
at the end, in this case:execute
comes handy), but it is better to usecall system('mkdir -p '.shellescape(expand('%:p:h')))
. Do use:execute '!command' shellescape(arg, 1)
(with the second argument to shellescape) if you have to use bangs instead ofsystem()
. Do use bangs if escaped argument contains newlines. -
ZyX almost 12 yearsAnd you don’t avoid other troubles I am avoiding in my first code snippet: launching shell one additional time after every vimrc sourcing (supposing you pull in vimrc updates by doing
:source ~/.vimrc
) (this is whataugroup
andautocmd!
are for), scrapped view after launching shell commands (that is whatredraw!
is for), creating garbage directories in case of using pseudo-files (in first code snipped it is checked by only matching filename against a pattern, but in second one I also check&buftype
) and useless shell call in case directory exists (isdirectory()
condition). -
kikito almost 12 years@ZyX Thanks for your feedback. I don't want to solve problems I don't have. I never use special chars (i.e. spaces) on my folders, so %:p:h does just fine for me. I never source vimrc (I kill and reopen vim instead) and I don't even know what pseudofiles are. redraw! doesn't seem to do anything at all to me. But I like your suggestion of removing execute to make everything shorter. Cheers!
-
ZyX almost 12 yearsIt does not matter whether or not you do have special characters, it is the thing you should care about. There are too much problems with
%
expansion to ever suggest using it to anybody. Pseudo files are used in a big bunch of plugins (e.g. fugitive or my aurum), thus they are worth caring about. Resourcing vimrc is also a common practice. You can have whatever you want in the vimrc, just do not suggest it as an answer. Using:silent! call mkdir(expand('%:p:h'), 'p')
variant solves two of the points I mentioned and third I did not mention:!mkdir
is not going to work on windows. -
ZyX almost 12 years
redraw!
will make difference if you are using terminal vim, it is blanking the screen otherwise. OP almost definitely has terminal vim: command in the question isvim …
. -
kikito almost 12 yearsMe too. Still no difference. Different systems, I guess (I'm on a Mac)
-
ZyX almost 12 yearsI would say this is something related to alternate screens and how terminal handles them: when you launch a shell command vim switches to the first of the screens (non-alternate), terminal shows it (blanking almost everything), vim runs command, then switches back to alternate screen. But, unfortunately, terminal does not remember the contents of the screen (unlike previous sentence, this is only my guess). Or remembers, in your case. I am on linux though.
-
Mu Mind almost 11 yearsWatch out! Vim has a bug with trailing slashes in
mkdir('foo/', 'p')
where it creates the directory and then errors out that it failed to create it: groups.google.com/d/topic/vim_dev/rFT67RzKMfU/discussion. Make sure you strip any trailing slashes before passing to mkdir(). -
ZyX almost 11 years@MuMind It is impossible to have trailing slashes after using
fnamemodify(, ':h')
::h
is “Head of the file name (the last component and any separators removed).” The only exception is/
(root), but I doubt thatisdirectory('/')
may return anything but 1. -
iamnewton over 9 years@ZyX, using your solution works beautifully, but i get an error, which appears to be due to a plugin I have. Can you shed light on it so I can either open a bug with the creator or fix myself?
-
iamnewton over 9 yearsFailed to execute "python /Users/cnewton/.vim/bundle/editorconfig-vim/plugin/editorconfig-core-py/main.py 'test/foo/bar/test.md'". Exit code: 1 Message: ['Traceback (most recent call last):', ' File "/Users/cnewton/.vim/bundle/editorconfig-vim/plugin/editorconfig-core-py/main.py", line 8, in <module>', ' main()', ' File "/usr/local/opt/dotfiles/vim/bundle/editorconfig-vim/plugin/editorconfig-core-py/editorconfig/main.py", line 73, in main', ' print(str( e))', 'UnboundLocalError: local variable ''e'' referenced before assignment']
-
ZyX over 9 yearsYou should direct this bug to
editorconfig-vim
bug tracker. I do not suggest using plugins like Vundle without actually understanding what they do:/Users/cnewton/.vim/bundle/…
is clearly a package manager path where…
gives you enough information about the plugin in which error occurred. It looks like they have forgot to remove a debuggingprint
or to addas e
. -
moebius_eye almost 8 yearsI've tried this same command and everytime I use
:W
, my screen becomes almost blank. I'll try and remove my previous options and give feedback. -
Tri Nguyen over 7 yearsNot sure what I'm doing wrong, but using the code provided does not work for me (same E212 error). if anyone has any suggestion, would appreciate it.
-
ZyX over 7 years@TriNguyen How did you use it? If “put it into the vimrc”, did you restart Vim afterwards?
-
Tri Nguyen over 7 years@ZyX yes, I did both of those things.
-
Tom Hale over 7 yearsThe issue with this is that this will always create the leading directories without asking, which may may be unwanted in the case of a typo when you know the directory should exist. See my answer for a solution.
-
Stephen Crosby over 5 yearsI'd recommend using
function!
instead offunction
so that your .vimrc file can be reloaded. I'd edit the answer to add it, but I don't have enough privileges on SO yet. -
superhawk610 about 3 yearsChanging
write
toexecute ':write'
fixed it for me.