Make a backup file: Difference between revisions

From Rosetta Code
Content added Content deleted
(Go solution)
(→‎{{header|Go}}: modify to require existing file)
Line 51: Line 51:
bx := ".backup"
bx := ".backup"
// see if it's a link
// see if it's a link
var err error
if tf, err := os.Readlink(fn); err == nil {
if tf, err := os.Readlink(fn); err == nil {
fn = tf
fn = tf
}
}
// stat to preserve permissions.
// default permissions, used if file doesn't exist
perm := os.FileMode(0666)
var fi os.FileInfo
if fi, err = os.Stat(fn); err != nil {
// now see if file exists.
if fi, err := os.Stat(fn); err == nil {
fmt.Println(err)
return
// it does. use existing permissions instead of default
}
// and rename the file, (losing any existing backup.)
// attemp rename
perm = fi.Mode().Perm()
if err := os.Rename(fn, fn+bx); err != nil {
if err = os.Rename(fn, fn+bx); err != nil {
fmt.Println(err)
// if rename of known existing file fails, we don't continue.
fmt.Println(err)
return
return
}
} else {
// stat failed. only allowable error is "no such file..."
// any other error is fatal.
pErr, ok := err.(*os.PathError)
if !ok || pErr.Err.Error() != "no such file or directory" {
fmt.Println(err)
return
}
}
}
// create new file
// create new file
err := ioutil.WriteFile(fn, []byte("you too!\n"), perm)
err = ioutil.WriteFile(fn, []byte("you too!\n"), fi.Mode().Perm())
if err != nil {
if err != nil {
fmt.Println(err)
fmt.Println(err)
Line 92: Line 83:


func main() {
func main() {
err := updateWithBackup("myth", ".backup", 0666,
err := updateWithBackup("myth", ".backup", func(f *os.File) (err error) {
func(f *os.File) (err error) {
if _, err = f.Seek(0, os.SEEK_SET); err != nil {
if _, err = f.Seek(0, os.SEEK_SET); err != nil {
return
}
if err = f.Truncate(0); err != nil {
return
}
_, err = f.WriteString("you too!\n")
return
return
})
}
if err = f.Truncate(0); err != nil {
return
}
_, err = f.WriteString("you too!\n")
return
})
if err != nil {
if err != nil {
fmt.Println(err)
fmt.Println(err)
Line 112: Line 102:
// update the file as needed and return any error, but should not close
// update the file as needed and return any error, but should not close
// the file. updateWithBackup will then close the file and return any error.
// the file. updateWithBackup will then close the file and return any error.
func updateWithBackup(fn, bx string, perm os.FileMode,
func updateWithBackup(fn, bx string, up func(*os.File) error) (err error) {
up func(*os.File) error) (err error) {
var f *os.File
var f *os.File
if f, err = openWithBackup(fn, bx, perm); err != nil {
if f, err = openWithBackup(fn, bx); err != nil {
return
return
}
}
Line 130: Line 119:
// destination file + bx. Any error encountered is returned. tf will be
// destination file + bx. Any error encountered is returned. tf will be
// an open file if and only if err == nil.
// an open file if and only if err == nil.
func openWithBackup(fn, bx string, perm os.FileMode) (tf *os.File, err error) {
func openWithBackup(fn, bx string) (tf *os.File, err error) {
// follow symlink.
// follow symlink.
if target, err := os.Readlink(fn); err == nil {
if target, err := os.Readlink(fn); err == nil {
Line 137: Line 126:
// open the target file for exclusive access.
// open the target file for exclusive access.
if tf, err = os.OpenFile(fn, os.O_RDWR, 0); err != nil {
if tf, err = os.OpenFile(fn, os.O_RDWR, 0); err != nil {
return
// oops, that didn't work. see if it's simply missing.
pErr, ok := err.(*os.PathError)
if ok && pErr.Err.Error() == "no such file or directory" {
// yep, that's all it was. return a new file.
return os.OpenFile(fn, os.O_RDWR|os.O_CREATE|os.O_EXCL, perm)
}
return // no, it was some other error. fail.
}
}
// at this point an existing target file has been opened.
// deferred function closes target file if an error happens
// deferred function closes target file if an error happens
// during the backup operation.
// during the backup operation.

Revision as of 03:33, 17 February 2012

Make a backup file is a draft programming task. It is not yet considered ready to be promoted as a complete task, for reasons that should be found in its talk page.

Before writing to a file it is often advisable to make a backup of the original. Creating such a backup file is however also not without pitfalls.

In this task you should create a backup file from an existing file and then write new text to the old file. The following issues should be handled:

  • avoid making a copy of the file but instead rename the original and then write a new file with the original filename.
  • if a copy needs to be made, please explain why rename is not possible.
  • keep in mind symlinks, and do not rename or copy the link but the target. (If there is a link foo -> bar/baz, then bar/baz should be renamed to bar/baz.backup and then the new text should be written to bar/baz.)
  • it is assumed that you have permission to write in the target location, thus permission errors need not be handled.
  • you may choose the backup filename per preference or given limitations. (It should somehow include the original filename however.)
  • please try to avoid executing external commands, and especially avoid calling a shell script.

Some examples on this page assume that the original file already exists. They might fail if some user is trying to create a new file.

Common Lisp

Appends a version number and increments it on each backup <lang lisp>(defun parse-integer-quietly (&rest args)

 (ignore-errors (apply #'parse-integer args)))

(defun get-next-version (basename)

 (flet ((parse-version (pathname)
                       (or (parse-integer-quietly 
                             (string-left-trim (file-namestring basename) 
                                               (file-namestring pathname))
                             :start 1) 0)))
   (let* ((files (directory (format nil "~A,*" (namestring basename))))
          (max (reduce #'max files :key #'parse-version)))
     (merge-pathnames (format nil "~a,~d" (file-namestring basename) (1+ max))
                      basename))))

(defun save-with-backup (filename data)

 (let ((file (probe-file filename)))
   (rename-file file (get-next-version file))
   (with-open-file (out file :direction :output)
     (print data out))))</lang>

Go

Rename

This is the technique of the task description. <lang go>package main

import (

   "fmt"
   "io/ioutil"
   "os"

)

func main() {

   fn := "myth"
   bx := ".backup"
   // see if it's a link
   var err error
   if tf, err := os.Readlink(fn); err == nil {
       fn = tf
   }
   // stat to preserve permissions.
   var fi os.FileInfo
   if fi, err = os.Stat(fn); err != nil {
       fmt.Println(err)
       return
   }
   // attemp rename
   if err = os.Rename(fn, fn+bx); err != nil {
       fmt.Println(err)
       return
   }
   // create new file
   err = ioutil.WriteFile(fn, []byte("you too!\n"), fi.Mode().Perm())
   if err != nil {
       fmt.Println(err)
   }

}</lang>

Copy

Alternative technique copies an existing file to make the backup copy, then updates the origial file. In an attempt to keep operations atomic, the original file is opened as the first operation and is not closed until all operations are complete. In an attempt to avoid data loss, the original file is not modified until the backup file is closed. <lang go>package main

import (

   "fmt"
   "io"
   "os"

)

func main() {

   err := updateWithBackup("myth", ".backup", func(f *os.File) (err error) {
       if _, err = f.Seek(0, os.SEEK_SET); err != nil {
           return
       }
       if err = f.Truncate(0); err != nil {
           return
       }
       _, err = f.WriteString("you too!\n")
       return
   })
   if err != nil {
       fmt.Println(err)
   }

}

// updateWithBackup opens fn, creates a backup copy named fn+bx, then passes // the still-open original file to the supplied function up. up should // update the file as needed and return any error, but should not close // the file. updateWithBackup will then close the file and return any error. func updateWithBackup(fn, bx string, up func(*os.File) error) (err error) {

   var f *os.File
   if f, err = openWithBackup(fn, bx); err != nil {
       return
   }
   err = up(f)
   if cErr := f.Close(); err == nil {
       err = cErr
   }
   return

}

// openWithBackup opens fn, creates a backup copy, and returns fn still open. // If fn is a symlink, the destination file is opened instead. The name of // the backup file will be fn+bx, or if fn was a symlink, the name of the // destination file + bx. Any error encountered is returned. tf will be // an open file if and only if err == nil. func openWithBackup(fn, bx string) (tf *os.File, err error) {

   // follow symlink.
   if target, err := os.Readlink(fn); err == nil {
       fn = target
   }
   // open the target file for exclusive access.
   if tf, err = os.OpenFile(fn, os.O_RDWR, 0); err != nil {
       return
   }
   // deferred function closes target file if an error happens
   // during the backup operation.
   defer func() {
       if err != nil {
           tf.Close()
       }
   }()
   // stat to preserve permissions.
   var fi os.FileInfo
   if fi, err = tf.Stat(); err != nil {
       return
   }
   // create backup file, silently droping any existing backup.
   var bf *os.File
   if bf, err = os.OpenFile(fn+bx, os.O_RDWR|os.O_CREATE|os.O_TRUNC,
       fi.Mode().Perm()); err != nil {
       return
   }
   // copy contents.  hold on to any error.
   _, err = io.Copy(bf, tf)
   // do your best to close backup file whether copy worked or not.
   if cErr := bf.Close(); err == nil {
       err = cErr // return close error if there has been no other error.
   }
   // backup complete (as long as err == nil)
   return

}</lang>

Java

This example is untested. Please check that it's correct, debug it as necessary, and remove this message.


Works with: Java version 7+

<lang java5>import java.io.PrintWriter; import java.nio.file.*;

public class Backup { public static void saveWithBackup(String filename, String... data){ //toRealPath() follows symlinks to their ends Path file = Paths.get(filename).toRealPath(); Path back = Paths.get(filename + ".backup").toRealPath(); Files.move(file, back, StandardCopyOption.REPLACE_EXISTING); try(PrintWriter out = new PrintWriter(file.toFile())){ for(String datum : data){ out.println(datum); } } } }</lang>

Works with: Java version 1.5+

<lang java5>import java.io.File; import java.io.IOException; import java.io.PrintWriter;

public class Backup{ public static void saveWithBackup(String filename, String... data) throws IOException{ File orig = new File(filename); //getCanonicalPath() follows symlinks to their ends File backup = new File(orig.getCanonicalPath() + ".backup");

orig.renameTo(backup); PrintWriter output = new PrintWriter(orig); for(String datum : data){ output.println(datum); } output.close(); } }</lang>

Locomotive Basic

AMSDOS has automatic one-level backups which also work from Locomotive BASIC: If e.g. the file test.bas is saved, the data gets written to test.$$$. When the file is closed a preexisting test.bas gets renamed to test.bak and finally test.$$$ is renamed to test.bas. (These backups affect all file types, not just BASIC source code.)

Pike

<lang Pike>string targetfile = "pycon-china"; targetfile = System.resolvepath(targetfile); mv(targetfile, targetfile+"~"); Stdio.write_file(targetfile, "this task was solved at the pycon china 2011");</lang>

Python

<lang Python> import os targetfile = "pycon-china" os.rename(os.path.realpath(targetfile), os.path.realpath(targetfile)+".bak") f = open(os.path.realpath(targetfile), "w") f.write("this task was solved during a talk about rosettacode at the PyCon China in 2011") f.close() </lang>

Ruby

This version does not overwrite the backup file if it exists. <lang ruby>def backup_and_open(filename)

 filename = File.realpath(filename)
 bkup = filename + ".backup"
 backup_files = Dir.glob(bkup + "*").sort_by do |f|
   f.match(/\d+$/)
   $&.nil? ? 0 : $&.to_i
 end
 backup_files.reverse.each do |fname|
   if m = fname.match(/\.backup\.(\d+)$/)
     File.rename(fname, "%s.%d" % [bkup, m[1].to_i + 1])
   elsif fname == bkup
     File.rename(bkup, bkup + ".1")
   end
 end
 File.rename(filename, bkup)
 File.open(filename, "w") {|handle| yield handle}

end

1.upto(12) {|i| backup_and_open(ARGV[0]) {|fh| fh.puts "backup #{i}"}}</lang>

Example:

$ echo "original" > original
$ ln -s original linkfile
$ ruby backup.rb linkfile
$ ls -l linkfile* original*
lrwxrwxrwx  1 glennj mkgroup-l-d   8 Nov 11 11:22 linkfile -> original
-rw-rw-rw-+ 1 glennj mkgroup-l-d  10 Nov 11 11:41 original
-rw-rw-rw-+ 1 glennj mkgroup-l-d  10 Nov 11 11:41 original.backup
-rw-rw-rw-+ 1 glennj mkgroup-l-d  10 Nov 11 11:41 original.backup.1
-rw-rw-rw-+ 1 glennj mkgroup-l-d   9 Nov 11 11:41 original.backup.10
-rw-rw-rw-+ 1 glennj mkgroup-l-d   9 Nov 11 11:37 original.backup.11
-rw-rw-rw-+ 1 glennj mkgroup-l-d   9 Nov 11 11:41 original.backup.2
-rw-rw-rw-+ 1 glennj mkgroup-l-d   9 Nov 11 11:41 original.backup.3
-rw-rw-rw-+ 1 glennj mkgroup-l-d   9 Nov 11 11:41 original.backup.4
-rw-rw-rw-+ 1 glennj mkgroup-l-d   9 Nov 11 11:41 original.backup.5
-rw-rw-rw-+ 1 glennj mkgroup-l-d   9 Nov 11 11:41 original.backup.6
-rw-rw-rw-+ 1 glennj mkgroup-l-d   9 Nov 11 11:41 original.backup.7
-rw-rw-rw-+ 1 glennj mkgroup-l-d   9 Nov 11 11:41 original.backup.8
-rw-rw-rw-+ 1 glennj mkgroup-l-d   9 Nov 11 11:41 original.backup.9
$ cat original original.backup original.backup.1 original.backup.2 original.backup.3 original.backup.4 original.backup.5 original.backup.6 original.backup.7 original.backup.8 original.backup.9 original.backup.10 original.backup.11
backup 12
backup 11
backup 10
backup 9
backup 8
backup 7
backup 6
backup 5
backup 4
backup 3
backup 2
backup 1
original

Tcl

<lang tcl>package require Tcl 8.5

proc backupopen {filename mode} {

   set filename [file normalize $filename]
   if {[file exists $filename]} {

set backups [glob -nocomplain -path $filename ,*] set backups [lsort -dictionary \ [lsearch -all -inline -regexp $backups {,\d+$}]] if {![llength $backups]} { set n 0 } else { set n [regexp -inline {\d+$} [lindex $backups end]] } while 1 { set backup $filename,[incr n] if {![catch {file copy $filename $backup}]} { break } }

   }
   return [open $filename $mode]

}</lang>