4

Let's say I have the following buffer:

foo.txt
bar buzz.txt
fizz/foo.txt
fizz/fuzz/fozz.txt

Representing an arborescence that I need to create.

How can I easily create it from Vim? I.e. How can I visually select the list of files and execute a function which would create the directories and files in the current folder?

  • 2
    vim can invoke external cmd, so it can be done with/in vim for sure. However I don't think vim is the best way to do it. I would do it in shell way. – Kent Jun 29 '16 at 15:22
  • @Kent: I would tend to agree with you, that's not what Vim was designed to do (maybe it would be a job for emacs), I was just curious of an efficient way to do it, you know something like a challenge maybe :-) – statox Jun 29 '16 at 15:24
6

You can take advantage of the way vim execute command when applied over a range.

Select your lines and do:

:'<,'>call system('mkdir -p '. fnamemodify(fnameescape(getline('.')),':h:p').' && touch '.fnamemodify(fnameescape(getline('.')),':p'))

This will apply the command

call system(...)

over every line you've selected.

The command is calling two external tools, mkdir and touch (you may change this if you work in windows).

  • mkdir -p will create the directory (and subdirectories if necessary). The argument is the escaped current line modified to show only the folder path (not the filename)

  • touch will create the file The argument is the escaped current line modified to show the path and the filename

You need to add the shellescape function so that the filename is correctly escaped for the system call.

The fnamemodify is handy if you want to modify the filename and path.


Note:

Alternatively to shellescape, you can use one of the following:

  • the :S modifier in fnamemodify
  • enclose the string with double quotes " (thanks @Tommy\ A for the suggestion)

See:

  • :h v_:
  • :h system
  • :h getline()
  • :h shellescape()
  • :h fnamemodify()
  • :h ::S
  • 1
    Adding double quotes around the path names should make it safe enough without shellescape(). – Tommy A Jun 29 '16 at 19:09
4

If you must do it with Vim:

function! Touch() range abort                       
    for fn in getline(a:firstline, a:lastline)
        if !filereadable(fn)
            let base = fnamemodify(fn, ':h')
            if !isdirectory(base)
                call mkdir(base, 'p')
            endif 
            call writefile([], fn, 'a')
        endif     
    endfor        
endfunction       

command! -range Touch <line1>,<line2>call Touch()

Beware that this doesn't try to be the most efficient, and only does minimal error checking.

Same thing with a shell script:

#! /bin/sh                                            
while IFS= read -r FN; do
    mkdir -p "$(dirname "$FN")" && touch "$FN" || exit 1
done

You can pipe the relevant range of lines to it.

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.