Vim Tips Wiki
Advertisement

Occasionally I use Vim to search for filenames in a directory listing rather than relying on windows search or some other search tool that might not be available at the time. This has the advantage of allowing me to use vim commands to filter and modify the results as needed. The main drawback with this approach is that dos directory listings don't list the filename and path on a single line thus requiring more advanced searches to be constructed to find anything. The following commands will take a dos directory listing created with dir /s and turn it into a line by line listing containing the full path to each file or directory. Note: This entry is of primary relevance to windows/dos users but the ideas outlined might be useful to users of other systems.

Desired Result

Turn this:

 Directory of C:\WINNT

20/08/2010  09:31 AM    <DIR>          .
20/08/2010  09:31 AM    <DIR>          ..
30/08/2010  09:13 AM                 0 0.log
29/12/2006  12:31 AM            19,569 003199_.tmp
03/06/2008  07:04 PM    <DIR>          addins
30/07/2010  08:43 PM    <DIR>          AppPatch
04/08/2004  10:00 PM             1,272 Blue Lace 16.bmp
24/02/2009  03:28 PM    <DIR>          bruce lee!
04/08/2004  10:00 PM            82,944 clock.avi
30/07/2010  08:16 PM               373 cmsetacl.log
04/08/2004  10:00 PM            17,062 Coffee Bean.bmp
19/08/2010  09:17 PM           324,805 comsetup.log

into this:

C:\WINNT\
C:\WINNT\0.log
C:\WINNT\003199_.tmp
C:\WINNT\Blue Lace 16.bmp
C:\WINNT\clock.avi
C:\WINNT\cmsetacl.log
C:\WINNT\Coffee Bean.bmp
C:\WINNT\comsetup.log
C:\WINNT\addins\
C:\WINNT\AppPatch\
C:\WINNT\AppPatch\acadproc.dll
C:\WINNT\AppPatch\acgenral.dll
C:\WINNT\AppPatch\aclayers.dll

Getting the directory listing

To get a dos directory listing of your c drive try typing:

c:\>dir /s > dirlist.txt

to edit this listing in Vim:

vim dirlist.txt

now we can reformat the listing to be line based:

" remove blank lines
g/^\s*$/d

" remove header
g/^ Volume/d

" remove footer
g/^\s\+Total Files Listed/.,+2d

" remove directory entries
g/\s<DIR>\s/d

" replace file entries with filename followed by delimiter
%s/^\d\d\/\d\d\/\d\d\d\d  \d\d:\d\d [AP]M \s*[0-9,]\+ /``````::::::

" replace backslash path separators with forward slashes
%s/\\/\//g

" create a copy of the directory path and append /. One copy is left alone the other gets prepended to all files inside.
%s/^ Directory of \(.*\)\s*$/\1\/\r@@\1\/@@/

" join together files in a directory
g/^@@/,/File(s)/j

" remove folder info at end
%s/ [0-9,]\+ File(s)\s\+[0-9,]\+ bytes\s*$//

" prepend path to each file
%s/^@@\(.\{-}\)@@\(.*\)/\=substitute(submatch(2),"::::::",submatch(1),"g")/

" split into separate lines
%s/``````/\r/g

" remove blank lines
g/^\s*$/d

" convert forward slashes to backslashes
%s/\//\\/g

" remove blanks at line end
%s/\s*$

if you put this into a file and call it reformat.vim you can then source it on a directory listing using:

:so reformat.vim

Searching the result

now if you want to see only doc files you can do:

:v/\c\.doc$/d

or you want to show all the folders which contain .vim files:

:v/\c\.vim$/d
:%s/\c\\[^\\]\+\.vim$//
:sort u

Further Processing

To take this one step further we can now convert this filtered directory listing into a batch file to operate on each line.

Delete each file:

:%s/^\(.*\)/del "\1"/

Rename each file:

%s/^\(.*\)\\\([^\\]\+\)\.txt$/rename "\1\\\2.txt" "\1\\\2.txt.old"/

now save as a dos batch file

:w renamefiles.bat

and run.

Of course don't run this unless you are **absolutely sure** the commands do what you want. There are some edge cases that will cause the reformatting code to trip up if you have strange characters in your filenames.

References

Comments

On Unix you can use find /some/directory | vim -. Windows users could install GnuWin to obtain a version of find utility to use the same procedure. The GnuWin package is updated from an earlier and obsolete UnxUtils package.

I refactored the above comments to remove the link to UnxUtils which I am fairly sure is obsolete. See here for info on signing comments. JohnBeckett 10:16, September 2, 2010 (UTC)

A simple Windows procedure to list file paths is to enter the following at command prompt, replacing DIRECTORY with the path of the directory you want:

xcopy DIRECTORY\* \ /s /h /l

That produces a list of the full path of each file in and under DIRECTORY. If you replace the * with *.c the list will include only *.c files. JohnBeckett 10:16, September 2, 2010 (UTC)


The following script (which could be in your vimrc) defines a command to list files in a scratch buffer. There are several good tools for finding files and navigating (for example, :e . explores the current directory). However, it can be useful to generate a list of file paths which you can edit. With the cursor in a file path, press gf to jump to that file.

" Create a scratch buffer with a list of files (full path names).
" Argument is a specification like '*.c' to list *.c files (default is '*').
" Can use '*.[ch]' to find *.c and *.h (see :help wildcard).
" If command uses !, list includes matching files in all subdirectories.
" If filespec contains a slash or backslash, the path in filespec is used;
" otherwise, start searching in directory of current file.
function! s:Listfiles(bang, filespec)
  if a:filespec =~ '[/\\]'  " if contains path separator (slash or backslash)
    let dir = fnamemodify(a:filespec, ':p:h')
    let fnm = fnamemodify(a:filespec, ':p:t')
  else
    let dir = expand('%:p:h')  " directory of current file
    let fnm = a:filespec
  endif
  if empty(fnm)
    let fnm = '*'
  endif
  if !empty(a:bang)
    let fnm = '**/' . fnm
  endif
  let files = filter(split(globpath(dir, fnm), '\n'), '!isdirectory(v:val)')
  echo 'dir=' dir ' fnm=' fnm ' len(files)=' len(files)
  if empty(files)
    echo 'No matching files'
    return
  endif
  new
  setlocal buftype=nofile bufhidden=hide noswapfile
  call append(line('$'), files)
  1d  " delete initial empty line
  " sort i  " sort, ignoring case
endfunction
command! -bang -nargs=? Listfiles call <SID>Listfiles('<bang>', '<args>')

Usage examples (these use forward slashes which work on Unix and Windows systems):

:Listfile                 " list all files in directory of current file
:Listfile!                " same, but also include subdirectories
:Listfile! *.c            " list all *.c files in tree of current file
:Listfile /my/path/*.c    " list *.c in given path
:Listfile! /my/path/*.c   " list *.c in given tree

Depending on what commands you have defined on your system, you may be able to abbreviate :Listfile to simply :L.

Todo Could make a buffer-local mapping so that pressing Enter will execute gf on the current line. Use command 0vg_gf to select the current line before the gf so that it works on paths that contain spaces.

I have just created the above script. It should work on all Vim 7 systems, and might be worth mentioning in a tip, perhaps here. If anyone has some comments, please add them below. JohnBeckett 10:16, September 2, 2010 (UTC)

Advertisement