A Shell Script to Perform Backups
Constructing a script to perform incremental backups using rsync
Let us construct a script to perform incremental backups using rsync. The script takes one or more directory paths as input, checks to make sure the files exist, and runs the rsync command for each directory path. This command incrementally backs up the contents of the directories to a USB drive mounted at /mnt/usb-hardrive/.
- The first line as always contains a directive that indicates that the file should be executed by the shell.
#!/bin/sh
- Next is the declaration of a variable named SOURCES. This variable is set to a space-separated list of directories to back up. Note that inline comments that start with '#' explain each segment of the script. We are backing up just the home directories of the users, the mail spools, and the contents of the /etc directory
# Directories to backup SOURCES="/home /var/mail /etc"
- Next, the TARGET variable is set to correspond to the place where the usb drive is mounted.
# Directory to backup to. # Filesystem Reference to mounted usb drive TARGET="/mnt/usb-harddrive/"
- We request a verbose output; all status and error messages should be displayed.
VERBOSE="-v"
- The following is an example of a conditional loop within a shell script. Any commands between a line that contains 'if [<condition>]; then' and 'fi' (that's if spelt backwards) is executed only if <condition> is true. Here, we check to see if the current user/process has execute permissions on the directory of the mounted USB Drive using '-x $TARGET'. The files within may only be accessed if execute permission for the directory is available. If not, an error is printed and the script stops execution and exits with the status 2. Note that the TARGET variable is referred to just like an environmental variable (with a $ prefix).
if [ ! -x $TARGET ]; then echo "Backup target does not exist" echo "or you don't have permission!" echo "Exiting..." exit 2 fi
- Following is a 'for loop'; that checks to see if the current user and process may access the files within each of the directories in the SOURCE variable. The 'source' variable refers to each item; the shell creates this array automatically and each directory in the space-separated string is an item. The block of commands between the for statement and the matching 'done' statement are executed once for each value of the counter next to the for statement (once for each 'source' item in the $SOURCES variable - three times in our example)
for source in $SOURCES; do echo "Checking $source..." if [ ! -x $source ]; then echo "Error: $source!" echo "Directory does not exist" echo "or you do not have permissions." exit 2 fi done
- The inner if - fi block checks to see if the user executing the script has access to all the directories specified in the source variable. If access is not available for any of the directories, the script exits. A message that displays the current operation (checking sources) is displayed to the user once for each directory.
echo "Checking $source..." if [ ! -x $source ]; then echo "Error: $source!" echo "Directory does not exist" echo "or you do not have permissions." exit 2 fi
- Next, another for loop processes each directory in $SOURCES. A directory tree is created in the USB drive for items in $SOURCES if such a tree cannot be found via '-d $TARGET/$source'. For example, if the tree /mnt/usb-harddrive/etc does not exist, it is created via 'mkdir -p /mnt/usb-harddrive/etc'. The structure of the created tree corresponds exactly to the structure of the directory in the filesystem.
echo "Running rsync" for source in $SOURCES; do # Create directories tree $TARGET # identical to source if not already present if [ ! -d $TARGET/$source ]; then mkdir -p $TARGET/$source fi
- The rsync command is also run within the for loop. The rsync command brings the files that correspond to the directory being backed up up-to-date if they already exist. Full copies are performed only for directories being backed up for the first time. That is, only directories for which the tree was created in the previous step are fully copied. The 'done' statement ends the for loop.
rsync $VERBOSE -a --delete $source/ $TARGET/$source/ done
- If the entire script ran successfully, it exits with status flag 0, indicating successful execution.
exit 0
Certain steps should be taken once this script is saved with the name 'backup.sh' to make it ready for execution:
- The $TARGET variable and $SOURCE variable should be customized to fit the system.
- The command 'chmod ugo+x backup.sh' should be issued to make this file executable.
- The file may be run at the command prompt by issuing the command './backup.sh'. The script will run for a long time initially as rsync makes a copy of all files rather than an update of changed files. The second time around and onwards, backups of the same drives will be incremental and therefore much faster. Upon completion of the script, a replica of each directory in $SOURCES will be in $TARGET.
- Alternatively, this script may be placed in a crontab and executed automatically once a day or so. The following entry executes the script (moved to the /bin directory) once every day at 5:00 am:
00 5 * * * backup.sh
We have not yet addressed a key task to be completed before backup.sh becomes operational - the actual creation, edition, and saving of a text file with the script contents. This may be accomplished using a Linux text editor.
A text editor is a program used to edit text files such as letters, source code for any programming language, shell scripts etc. Many such editors are available for the Linux OS. The vi editor is not intuitive and its commands are not self explanatory. However, it is a Linux standard and useful to know. Many users prefer the more intuitively grasp-able and powerful emacs. Emacs has its own built-in dialect of the LISP programming language and many extensions including an artificial intelligence program. However, the Emacs application is quite large and the emacs process takes up a lot of CPU. Vi, on the other hand, is also powerful but relatively small (although more difficult to learn)