Vim Tips Wiki
Register
Advertisement
Tip 1669 Printable Monobook Previous Next

created 2010 · complexity basic · author MisterW · version 7.0


See the comments section; this tip is useful, but is either broken in some way, overly complex, or based on faulty information/assumptions. Someone has suggested a better alternative. Please merge the alternatives discussed in the comments into the tip itself if you have time!

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.

This tip 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.

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 comment 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)

Wow, this is really slick, and gets rid of most of the scripting in the tip. Thanks! I was going to mention the /b option for the dir command, but this is even better! --Fritzophrenic 13:39, September 2, 2010 (UTC)

Great info about getting directory listings for dos. I didn't know of either of these methods and I can't think why I didn't try the /b switch. I would not have written the vim script if I was aware of these alternatives and saved myself some time.

It may be interesting to note that the 3 mentioned methods of generating a dir listing all differ slightly in what they produce and one or other may be better suited depending on what you intend to do with the output. The xcopy command does not produce directory entries and gives a one line file summary at the end. dir /s /b lists directories and lists them first but doesn't distinguish between a directory and a file. dir /s and vim script produces a directory listing and marks directories with a trailing slash. I have a feeling it might not list empty directories and it also produces double slashes for the drive root, if included - probably an easy fix.

A simple modification to the vim script can be made to include file sizes at the end of each line. That way you could easily list the 10 biggest movie files on your disk and delete or move them to another location. (code pending). --MisterW 23:29, September 2, 2010 (UTC)

There is also the /a option: dir /s /b /a-d is files only ("attributes not directory"), and dir /s /b /ad is directories only. JohnBeckett 05:23, September 3, 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 s: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)

Looks pretty interesting, though it probably needs some implementation comments. I'm not so sure about the Enter mapping. It could be useful but I'm worried if we present it without a way to do the same thing in netrw people will just use it without ever knowing about builtin features. I'm pretty sure it's possible to filter the netrw window for a certain file name pattern. I do know it's possible to set it up so you have a tree view instead of a single directory at a time. Something to look into. --Fritzophrenic 13:39, September 2, 2010 (UTC)
Very useful John. --MisterW 23:34, September 2, 2010 (UTC)
Advertisement