1

I just learned how to write simple shell scripts. One of them is that simply duplicate certain files on my system that happen to be crucial to me.

cp /dir/dir/file1 /samedir/samedir/file1.bak

My scripts gets executed every time I sign on my system. However it always overwrites file1.bak. I would like to find a way to keep all created files. Maybe something like file1(1).bak, file1(2).bak or something of the like.

VMS was great for certain things, namely that every time one would write to a file called file.bak, it would keep all previous versions by adding a tailing ':' at the end of the file indicating the version number ie: file.bak:1, file.bak:2, file.bak:521, etc...

We used to get rid of previous versions with the purge command purge file.bak 1-520.

  • You found nothing. Where did you look? What about e.g. this? – Run CMD Jan 8 '17 at 7:17
  • You may want to rephrase your text as a question, ultimately you never ask anything - rather you talk about something you'd like to happen.... – nerdwaller Jan 14 '17 at 2:22
1

You can use some SCM software (also known as version control) such as git, mercurial or fossil.

This way you will avoid huge amount of bak files in the working directory and keep track of all previous versions in the same time.

IMHO simplest one would be fossil-scm It is statically complied single file that can handle such jobs. (By the way created by the same programmer who created SQLite, so expect quality)

Your workflow could be like that:

  1. cd to directory where files need to be tracked and init repository with command fossil init (it will prepare a single database file)
  2. Tell fossil to track particular directory: fossil open
  3. Add files/directories recursively to repository fossil addremove --dotfiles
  4. Save current status of directory to repository: fossil commit or if you care about integrity of files then: fossil commit --sha1sum

All subsequent calls of fossil is just fossil commit (If you need to add more new files or remove some from tracking then just issue before commit fossil addremove --dotfiles)

This way all changes to files will be kept in timeline and can be previewed or compared(diff) or extracted (checkout), from repository ether file by file or restore completely previous state of the whole directory.

You can use fossil extras --dotfiles to reveal new non tracked files or spot only files that were changed fossil changes --abs-paths --sha1sum --header -v

If you need to exclude some files or directory, fossil support it too.

You can see timeline of changes over nice web interface produced by the same single file or even use internal WiKi to make a nice annotation to changes.

If you need to remove some content from repository, fossil provides a mechanism called "shunning" to do that.

0

Solution

If you do not wish to use an actual version control system (as suggested by @Alex & @Class Stacker) then the following bash script should do the job:

#!/bin/bash

#############
# Variables #
#############
FILE_LIST="foo.txt bar.doc"  # Space separated list of files to backup (include full path to file)
KEEP_OLD="5"  # How many previous versions to keep

#############
# Functions #
#############
function shift_backups {
        for num in $(seq $KEEP_OLD -1 1) ; do
                old_backup="$file.bak$num"
                if [[ -e $old_backup && $num == $KEEP_OLD ]] ; then
                                echo "    removing oldest file ($old_backup)"
                                rm -f $old_backup
                elif [[ -e $old_backup ]] ; then
                                new_name="$file.bak$(expr $num + 1)"
                                echo "    moving $old_backup to $new_name"
                                mv $old_backup $new_name
                fi
        done
}

################
# Backup Files #
################
for file in $FILE_LIST ; do
        count=1
        while [[ $count -le $KEEP_OLD ]] ; do
                backup_file="$file.bak$count"
                if [[ -e $backup_file ]] ; then
                        echo "$backup_file exists, shifting backups"
                        shift_backups
                        cp $file $backup_file
                        break
                else
                        cp $file $backup_file
                        break
                fi
                count=$(expr $count + 1)
        done
done

Explanation

To explain this, I'm going to break it down and show you through the logic.

Variables

  • The FILE_LIST variable holds a space-separated list of all files to be included in the backup (ensure that full file paths are used)
  • The KEEP_OLD variable indicates when .bak files should be deleted. In the example it is set to 5 meaning that your backups won't go back further than 5 versions (with foo.txt.bak5 being the oldest version)

Note: Depending on how many files you are doing this for, it may be worth tweaking the FILE_LIST variable to reference a file and loop through every line in there. [I have not tested that idea]

Functions

The function shift_backups does the following:

  • Loops through backup files from oldest to newest

    for num in $(seq $KEEP_OLD -1 1) ; do
    
  • If the backup file exists and is the oldest count, then it is deleted

    if [[ -e $old_backup && $num == $KEEP_OLD ]]
    
  • Or if the backup file only exists, it will be shifted to backup number + 1

    elif [[ -e $old_backup ]]
    

Backup Loop

  • Iterate through each file in the variable FILE_LIST

     for file in $FILE_LIST ; do
    
  • Continue to loop through backup checks while the value of the variable count is 1

    while [[ $count -le $KEEP_OLD ]] ; do
    
  • Store the name of the destination file name in a variable

    backup_file="$file.bak$count"
    
  • If the backup file exists, the shift_backups function is called and then the file is copied (breaking the while loop when this happens)

    echo "$backup_file exists, shifting backups"
    shift_backups
    cp $file $backup_file
    break
    
  • If the backup file doesn't exist, it is only copied & the while loop is exited

    else
    cp $file $backup_file
    break
    
  • Increments the variable count

    count=$(expr $count + 1)
    

Output

The script has a few echoes in it to help understand the steps it has taken. For example, below is the output of the script when run in a directory where the latest versions of backup files were .bak3:

foo.txt.bak1 exists, shifting backups
    moving foo.txt.bak3 to foo.txt.bak4
    moving foo.txt.bak2 to foo.txt.bak3
    moving foo.txt.bak1 to foo.txt.bak2
bar.doc.bak1 exists, shifting backups
    moving bar.doc.bak3 to bar.doc.bak4
    moving bar.doc.bak2 to bar.doc.bak3
    moving bar.doc.bak1 to bar.doc.bak2

Should you require the script to be quiet, remove the echoes and it will run the same.

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.