Make a backup file

From Rosetta Code
Revision as of 09:29, 9 December 2011 by rosettacode>EMBee (category file handling)
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>

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>