Mass file recovery from .swp's

94 views
Skip to first unread message

Yang Zhang

unread,
Oct 6, 2009, 1:52:46 PM10/6/09
to v...@vim.org
Sometimes, when vim or the host OS crashes with many open files, .swp
files are left scattered throughout the system. Currently, I locate
all the .swp files and manually open the corresponding original files
in vim so that I'm prompted with the option to recover what's in the
.swp. 90% of the time, the recovered file is identical to the
original, and 5% of the time, the recovered file is empty. In both
these cases, I simply discard the .swp.

Is there a way (or are there any tools) to automate this highly manual
recovery process? I.e., given a set of .swp files, for each one
compare the recovered file with the original and discard the swp if
it's useless (leaving in place the ones that could not be
automatically pruned out)?
--
Yang Zhang
http://www.mit.edu/~y_z/

Erik Falor

unread,
Oct 6, 2009, 7:01:29 PM10/6/09
to vim...@googlegroups.com
On Tue, Oct 06, 2009 at 10:52:46AM -0700, Yang Zhang wrote:
>
> Sometimes, when vim or the host OS crashes with many open files, .swp
> files are left scattered throughout the system.

Here is one suggestion:
You can use the 'directory' setting to cause Vim to keep these files
in one place. You can read about the tradeoffs at
:help swap-file
:help 'directory'

--
Erik Falor
Registered Linux User #445632 http://counter.li.org

signature.asc

Chris Suter

unread,
Oct 6, 2009, 8:02:48 PM10/6/09
to vim...@googlegroups.com
Assuming you have the standard set of GNU tools at your disposal, something like the following should work (i created a simple test case and it worked for me). The only tricky bits are the sed regex, which assumes all the files are of the form "filename" or "filename.ext" -- if the filenames have any wierdness about them, it might not work, but assuming you've got some normal-ish source tree with *.c files or something, this should do. From the base of the directory in which the concerned swp files exist, you should be able to copy and paste the following directly into the terminal. It's probably not a bad idea to make a full backup of the directory first.


find . -name '*.swp'  |  \
sed 's/\(.*\/\)\.\([^.]\+\(\.[^.]\+\)\?\)\.swp/\1\2/' | \
while read i
do
   cp $i $i".old"
   vim -r $i -e -c 'wq'
   if [[ `diff $i $i".old" | wc -l` -eq 0 ]]
   then
     mv $i".old" $i
   fi
done


Hopefully my indentations come through or that might be kind of hard to read. Let me explain basically what's going on:
 1) "find" generates a list of the swap files under the current directory.
 2) the output of find is piped into sed, which converts the paths to the .swp files into the base filenames to which they correspond
 3) the output of sed is piped into the "while read i" command, which will read each line of input and place it in the shell variable $i within the loop structure
 4) the loop is mostly straightforward -- a copy of each file is made with ".old" appended to the filename, then the file is opened by vim with some command line options that basically force the recovery, write the file and quit vim without ever showing the interface (or interrupting the script to ask you stuff). Say your file was called "foo.c". If there were unsaved modifications to foo.c, then you'll now have "foo.c" and "foo.c.old" sitting next to each other with "foo.c" containing whatever changes were reflected in the swp file, and "foo.c.old" being whatever "foo.c" was at the time of the previous save. What remains is to replace the "recovered" version with the "old" version, only if the number of lines output by $ diff foo.c foo.c.old is zero. If any of this is unclear, read up on bash conditionals, and have a look at
  $ man test
  $ man diff
  $ man wc
as needed.

The end result is that files which were unchanged but left a swp file sitting around will have their swp files deleted. Any files which were actually modified will now show those modifications, with the previously saved version available as "foo.c.old", etc. The swp files corresponding to these modified files will also still be around and can be deleted, if you like, with something like $ find . -name '*.swp' | xargs rm

Hope this helps -- feel free to hit me up if anything is unclear. Thanks for the challenge! This was a fun problem to solve.
--
Christopher Suter
www.grooveshark.com

Chris Suter

unread,
Oct 6, 2009, 8:12:07 PM10/6/09
to vim...@googlegroups.com
oops looks like i was wrong -- the .swp files are left around. the following should fix this:


find . -name '*.swp'  |  \
sed 's/\(.*\/\)\.\([^.]\+\(\.[^.]\+\)\?\)\.swp/\1\2/' | \
while read i
do
   cp $i $i".old"
   vim -r $i -e -c 'wq'
   if [[ `diff $i $i".old" | wc -l` -eq 0 ]]
   then
     mv $i".old" $i
     rm `dirname $i`/"."`basename $i`".swp"
   fi
done

the added rm command just reconstructs the name of the swp file from the name of the original using backticks to pull out the path and the filename. This is now a big ugly hack which could probably be cleaned up quite a bit, but ought to work anyway.
--
Christopher Suter

bill lam

unread,
Oct 6, 2009, 10:46:41 PM10/6/09
to vim...@googlegroups.com
On Tue, 06 Oct 2009, Chris Suter wrote:
> sed 's/\(.*\/\)\.\([^.]\+\(\.[^.]\+\)\?\)\.swp/\1\2/' | \

Just curious, why not
sed 's/\(.*\)\.swp$/\1/' | \

--
regards,
====================================================
GPG key 1024D/4434BAB3 2008-08-24
gpg --keyserver subkeys.pgp.net --recv-keys 4434BAB3

Chris Suter

unread,
Oct 6, 2009, 10:52:11 PM10/6/09
to vim...@googlegroups.com
the swap file for "somedir/foo.c" is "somedir/.foo.c.swp" -- not "somedir/foo.c.swp" so i had to catch the "." after the path to exclude it. there is probably a much better way of doing this, especially since i turn around and pipe into a while. setting some auxillary variable in the loop like j=`dirname $i`"/"`${i:1:${#i}-4} would be simpler and would keep access to the original swp file name around so i wouldn't have to reconstruct it in the rm at the end of the loop.

sed expressions are definitely not the prettiest, but god i love 'em :)
--
Christopher Suter

Chris Suter

unread,
Oct 6, 2009, 10:52:31 PM10/6/09
to vim...@googlegroups.com
oy...sorry for top-post...gmail...

--
Christopher Suter

bill lam

unread,
Oct 6, 2009, 11:03:43 PM10/6/09
to vim...@googlegroups.com
Chris, thank you for explanation. It was a blind-spot. I usually
just delete them all. And btw vim does not crash so often (under linux
of course).

Chris Suter

unread,
Oct 6, 2009, 11:25:41 PM10/6/09
to vim...@googlegroups.com
My pleasure. Yeah i usually don't worry about it too much. Interestingly, I had to send a SIGKILL to vim in order for an open but unchanged buffer to leave the .swp file around. SIGTERM gave it a chance to clean up after itself.

--
Christopher Suter

James Michael Fultz

unread,
Oct 7, 2009, 12:43:48 AM10/7/09
to vim...@googlegroups.com
* Chris Suter <cgs...@gmail.com> [2009-10-06 20:12 -0400]:

> oops looks like i was wrong -- the .swp files are left around. the following
> should fix this:
>
> find . -name '*.swp' | \
> sed 's/\(.*\/\)\.\([^.]\+\(\.[^.]\+\)\?\)\.swp/\1\2/' | \
> while read i
> do
> cp $i $i".old"
> vim -r $i -e -c 'wq'
> if [[ `diff $i $i".old" | wc -l` -eq 0 ]]
> then
> mv $i".old" $i
> rm `dirname $i`/"."`basename $i`".swp"
> fi
> done
>
> the added rm command just reconstructs the name of the swp file from the
> name of the original using backticks to pull out the path and the filename.
> This is now a big ugly hack which could probably be cleaned up quite a bit,
> but ought to work anyway.

Sorry, couldn't help it since shell scripting is a hobby for me... :->

#! /bin/sh

find . -name '*.swp' -type f -exec sh -c '
swap=$1
dir=${swap%/*} # dirname
tmp=${swap##*/.} # trim leading path and dot
file=${dir}/${tmp%.swp} # original file, trim .swp
ofile=$file.tmp$$ # temporary "old file"

cp "$file" "$ofile"

vim -r "$file" -e -c wq

if cmp -s "$file" "$ofile"
then
mv "$ofile" "$file"
rm -f "$swap"
fi' sh {} \;

jorges

unread,
Oct 7, 2009, 6:28:20 AM10/7/09
to vim_use


On Oct 6, 7:52 pm, Yang Zhang <yanghates...@gmail.com> wrote:
< snip >
> Is there a way (or are there any tools) to automate this highly manual
> recovery process? I.e., given a set of .swp files, for each one
> compare the recovered file with the original and discard the swp if
> it's useless (leaving in place the ones that could not be
> automatically pruned out)?

I asked myself the same question some time ago. Then, I came across
bash script (this makes it more usefull in UNIX-like OSes) which I
modified only slightly. Unfortunately, I can't recall where I got it
from to give the author due credit. It was posted somewhere, so I take
the liberty to re-post it here (with my tiny modification). Basically,
you run it from the directory where the .swp files are and it will
compare the .swp with the original file and present you a vimdiff
window if the differ. Test it before production use to get familiar
with it.

jorge

cleanswap:

#!/bin/bash

TMPDIR=$(mktemp -d) || exit 1
RECTXT="$TMPDIR/vim.recovery.$USER.txt"
RECFN="$TMPDIR/vim.recovery.$USER.fn"
trap 'rm -f "$RECTXT" "$RECFN"; rmdir "$TMPDIR"' 0 1 2 3 15
#for q in ~/.vim/swap/.*sw? ~/.vim/swap/*; do
for q in ./.*sw?; do
[[ -f $q ]] || continue
rm -f "$RECTXT" "$RECFN"
vim -X -r "$q" \
-c "w! $RECTXT" \
-c "let fn=expand('%')" \
-c "new $RECFN" \
-c "exec setline( 1, fn )" \
-c w\! \
-c "qa"
if [[ ! -f $RECFN ]]; then
echo "nothing to recover from $q"
rm -f "$q"
continue
fi
CRNT="$(cat $RECFN)"
if diff --strip-trailing-cr --brief "$CRNT" "$RECTXT"; then
echo "removing redundant $q"
echo " for $CRNT"
rm -f "$q"
else
echo $q contains changes
vim -n -d "$CRNT" "$RECTXT"
rm -i "$q" || exit
fi
done

jorges

unread,
Oct 7, 2009, 6:35:28 AM10/7/09
to vim_use


On Oct 6, 7:52 pm, Yang Zhang <yanghates...@gmail.com> wrote:
< snip >
> Is there a way (or are there any tools) to automate this highly manual
> recovery process? I.e., given a set of .swp files, for each one
> compare the recovered file with the original and discard the swp if
> it's useless (leaving in place the ones that could not be
> automatically pruned out)?

I have already posted a response, but I don't see it appearing.
Anyway, in the meantime I found the source of the script I use (and
that I posted in the missing response):
http://stackoverflow.com/questions/63104/smarter-vim-recovery

I hope somebody finds it useful, as it is for me.

jorge
Reply all
Reply to author
Forward
0 new messages