13

I have simple script that monitors file for changes and rsyncs it with remote copy:

#!/bin/bash

while inotifywait -e close_write somefile
do
    rsync somefile user@host.domain:./somefile
done

It works just fine with nano, but fails with vim. When i use nano, it outputs :

somefile CLOSE_WRITE,CLOSE   

and starts next loop waiting for another edition.

When i use vim, there is no output, script just closes with exit code 0.

I did some research and found out that close_write is right parameter for uing initofywait along with vim (first i wanted to use modify event), yet for some reason it fails for me.

13

Editors can follow several strategies to save a file. The two major variants are to overwrite the existing file, or to write to a new file and move it in place. Writing to a new file and moving it in place has the nice property that at any point in time, reading from the file gives you a complete version of the file (one instant the old one, the next instant the new one). If the file is overwritten in place, there's a time during which it is incomplete, which is problematic if some other program accesses it just then or if the system crashes.

Nano apparently overwrites the existing file. Your script detects the point when it's finished writing (the close_write event) and runs rsync at that point. Note that it is possible for rsync to grab an incomplete version of the file, if you save twice in quick succession, before rsync has completed its job from the first save.

Vim, on the other hand, uses the write-then-move strategy — something to the effect of

echo 'new content' >somefile.new
mv -f somefile.new somefile

What happens to the old version of the file is that it gets deleted at the point where the new version is moved into place. At this point, the inotifywait command returns, because the file it's been told to watch no longer exists. (The new somefile is a different file with the same name.) If Vim had been configured to make a backup file, what would happen is something like

echo 'new content' >somefile.new
ln somefile somefile.old
mv -f somefile.new somefile

and inotifywait would now be watching the backup.

For more information on file save strategies, see How is it possible to do a live update while a program is running? and File permissions and saving

Vim can be told to use the overwrite strategy: turn off the backupcopy option (:set nobackupcopy). This is risky, as indicated above.

To handle both save strategies, watch the directory and filter both close_write and moved_to events for somefile.

inotifywait -m -e close_write,moved_to --format %e/%f . |
while IFS=/ read -r events file; do
  if [ "$file" = "somefile" ]; then
    …
  fi
done
  • The drawback to the method proposed here is that when you write several files "at once", your commands will be run once for each file. I'm editing code, so I may modify a header and a couple other translation units and :wa. Then my build is run once for every file written. – Limited Atonement Jul 29 at 18:50
  • @LimitedAtonement That's a much more complicates use case than the one in this question. For your use case, you'd need to wait a bit after one file is saved. Files are never really modified “at once”. If you save multiple files with :wa, you get successive inotify events. You'd need to wait after the first one to see if others are coming. But you can use the code presented here: the additional complexity would go inside the . – Gilles 'SO- stop being evil' Jul 29 at 19:12
  • @Gilles I decided on while true; do inotifywait ... [no -m]; make; sleep .1; done; or so. There are some gotchas, but I've arrived at something quite workable. – Limited Atonement Jul 30 at 20:04

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.