Basic Backup Script in BASH


Introduction
Everyone should be backing up their data. This just doesn't go towards sysadmins, but even people at home who never even think of it. Just like everything else in I.T., hard drives were built to fail. If you do not make efficient back ups, you are at your own mercy when your drive will no longer spin up, meaning all that data is now gone.

I try to make as many of my tasks as easy as possible simply by using Bash scripting. Its the most portable language meaning 99.9% of the time, you aren't going to need bash to run the script...and even if so, most of the code I write can work with other shells as well.

I'm going to copy/paste parts of my script, and give details about each block of code. At the end, I'll also provide a link to the full script.

What WIll This (Not) Do?
This script will do the following:



  • Back up the specified directories
  • Create a directory based on month & date
  • Create a separate backup for each given device
  • E-mail you the results (this assumes a working SSMTP, which is out of the scope of this particular guide)
  • Pretty much everything else is not going to be done. This is a quick and dirty backup solution, and isn't intended to be the absolute answer. I wrote this originally for my own needs and my needs only.

    Lets Begin
    DAY=`date +%d`
    
    MONTH=`date +%m`
    
    YEAR=`date +%Y`

    Here we get various information needed for storing our backup information. Pretty straight forward with a smile at the end if I do say so myself. Pretty easy, eh?

    # Backup directory to use (2011/08/31 for 08.31.2011)
    BKDIR="/backups/$YEAR/$MONTH/$DAY"
    
    if [ ! -d "$BKDIR" ]; then
            mkdir -p $BKDIR
    fi

    We make the directory where the backup files will be stored (-p ensures any missing directories will be made). The script must have write permissions to the BKDIR (in this case /backups/), or else it will fail.

    BKLOG="/backups/$YEAR/$MONTH/$DAY.log"

    Log of information (will make more sense later). I like things being consistent.

    ARRPOS=0

    I'll be honest, I can't really explain this, but it is used, and should make more sense when you actually see its use. Its like trying to explain to someone new to computers how a keyboard makes a letter appear...you just tell them "just press the key to see its power" if you want to keep them interested in you.

    DRIVE=('sda' 'sdb')

    This backup solution is device-based, and my server has two devices (one main, another with misc. data). The end result will basically be $BKDIR/$DRIVE[$ARRPOS].tar.gz (i.e.: /backups/2012/01/26/sda.tar.gz)

    BACKUP=('/' '/pub')

    What to back up on each device (for me, this is backing up everything).

    SDAEX=('/media' '/tmp' '/dev' '/proc' '/sys' '/mnt' '/pub' '/var/cache' '/backups')

    A lot of these aren't needed, and we also do not want to back up our back ups by default.

    touch $BKLOG

    Create an empty file for the log file, making sure it can be made.

    echo "To: someone@somewhere.com" > $BKLOG
    echo "From:Backups " >> $BKLOG
    echo -e "Subject: Generated backup report for `hostname` on $YEAR.$MONTH.$DAYn" >> $BKLOG
    
    echo -e ">> Backup for: $YEAR.$MONTH.$DAY started @ `date +%H:%M:%S`n" >> $BKLOG

    The purpose of $BKLOG is to log the status of the back up process. When it is done, we will be e-mailing the report out (see "To:" field). You can change this however you want, this is how I did it for myself.

    # Checks to see if day = 1, and if so, backs up the last month's backups
    if [ "$DAY" == "01" ]; then
            M=`echo -n $MONTH | awk '{printf substr($1,2)}'`
            let OLD=M-1
    
            echo "- New month detected.  Backing up previous month's ($OLD) backups." >> $BKLOG
            echo "   + Backup file: /backups/$YEAR/$OLD.tar.gz" >> $BKLOG
            SD=$( { time tar -cpPzf /backups/$YEAR/$OLD.tar.gz /backups/$YEAR/$OLD/; } 2>&1 )
    
            # Got stats, delete folder
            rm -rf /backups/$YEAR/$OLD
    
            SD=`echo -n "$SD" | grep real`
            MIN=`echo -n "$SD" | awk '{printf substr($2,0,2)}'`
            SEC=`echo -n "$SD" | awk '{printf substr($2,3)}'`
            echo -e "- done [ $MIN $SEC ].n" >> $BKLOG
    fi

    As the comment block states, this is a monthly backup that occurs. It backs up the previous month's backups before starting one for the current day. This, combined with other routines put into the system keeps backups for a lengthy amount of time. This is also why we excluded /backups from our routine...WAY too many back ups of back ups if you ask me.

    One thing I want to talk about, since its the meat of the actual back up routine, is this line:
    tar -cpPzf /backups/$YEAR/$OLD.tar.gz /backups/$YEAR/$OLD/

    This is basically telling tar to create (-c) a gunzipped (-z) back up file (-f) named /backups/$YEAR/$OLD.tar.gz containing the data found in /backups/$YEAR/$OLD/ directory, preserving permissions (-p), using absolute file names (-P) basically not stripping "/" from the beginning of the file name. The -P switch is used because it makes the output ugly, and it can lead to a broken back up.

    Continuing on...

    # Cycle through each drive and back up each
    for d in "${DRIVE[@]}"; do
            echo "- Backing up drive $d" >> $BKLOG
    
            # By default, at least don't backup lost+found directories
            EX="--exclude=lost+found"
    
            # If we are backing up drive 1 (/dev/sda), there's to exclude
            if [ $d == "sda" ]; then
                    for e in "${SDAEX[@]}"; do
                            EX="`echo -n $EX` --exclude=$e"
                    done
            fi
    
            # Do the magic work and display some cool info
            SD=$( { time tar -cpPzf $BKDIR/$d.tar.gz $EX ${BACKUP[$ARRPOS]}; } 2>&1 )
            SD=`echo -n "$SD" | grep real`
            MIN=`echo -n "$SD" | awk '{printf substr($2,0,2)}'`
            SEC=`echo -n "$SD" | awk '{printf substr($2,3)}'`
            SD=$(ls -liha $BKDIR/$d.tar.gz)
            SIZE=`echo -n $SD | awk '{printf $6}'`
    
            let ARRPOS++
    done

    This is the code that does the backing up of current data. This is also where we need ARRPOS. This is all pretty much self explanatory as well to be honest. Biggest change here is, besides the array wrapped in a for block, we get the file size of the created back up file. So, lets re-wind a little bit here and look at the for block...

    Another block of code I didn't discuss earlier (since its in a couple of spots) is this:
            SD=$( { time tar -cpPzf $BKDIR/$d.tar.gz $EX ${BACKUP[$ARRPOS]}; } 2>&1 )
            SD=`echo -n "$SD" | grep real`
            MIN=`echo -n "$SD" | awk '{printf substr($2,0,2)}'`
            SEC=`echo -n "$SD" | awk '{printf substr($2,3)}'`

    If you run the time command, you'll get an output like this:
    [ehansen@sfu ~]$ time
    
    real    0m0.000s
    user    0m0.000s
    sys    0m0.000s

    What the block of code does is measure how long it takes to create the back up file (tar command), and then we only measure the "real" time. The reason why this is done is because even in a multithreaded environment like Linux lets you have, a process may have to stop its thread for a moment to either let another user's program (or system action like signal handling) occur. The "real" time is the actual time it took for a command to execute. We use my best friend, Mr. awk, to parse the data from the information.

    for d in "${DRIVE[@]}"; do

    d will be whatever value is currently at $DRIVE[$ARRPOS]. For example, the first time around, d will be sda, second time it will be sdb.

            # By default, at least don't backup lost+found directories
            EX="--exclude=lost+found"
    
            # If we are backing up drive 1 (/dev/sda), there's to exclude
            if [ $d == "sda" ]; then
                    for e in "${SDAEX[@]}"; do
                            EX="`echo -n $EX` --exclude=$e"
                    done
            fi

    ext3 & 4 file systems create this wonderful file called lost+found. Personally, I'm not a fan of it, because every time I try to restore corrupted data from it, I just get my bottom handed to me, but besides this, its a folder that really should be pointless to include in a routine back up measure. If we're working on the first partition (where /boot, /home, /var, etc... are stored), we exclude some of the more minor files that really mean nothing when the system is shut down. The reason why there's the line "EX="`echo -n $EX` --exclude=$e" is its basically the same as, for example, in PHP or Perl where you can do EX .= " --exclude=$e". Bash, however, is not as friendly with strings and concating.

    # Mail this script out...ssmtp for GMail accounts, otherwise change for 
    #  appropriate MTA
    /usr/sbin/ssmtp -t < $BKLOG

    This is the last of the back up script, just giving out some generic details, and then using ssmtp to send out the report. Nothing to really discuss here.

    Howto set-up a crontab file


    In Linux, Cron is a daemon/service that executes shell commands periodically on a given schedule. Cron is driven by a crontab, a configuration file that holds details of what commands are to be run along with a timetable of when to run them.

    Creating a crontab file

    You can create a crontab file by entering the following terminal command:
    crontab -e
    
    Entering the above command will open a terminal editor with a new blank crontab file, or it will open an existing crontab if you already have one. You can now enter the commands to be executed, see syntax below, before saving the file and exiting the editor. As long as your entries were entered correctly your commands should now be executed at the times/dates you specified. You can see a list of active crontab entries by entering the following terminal command:
    crontab -l
    

    Crontab syntax

    A crontab file has six fields for specifying minute, hour, day of month, month, day of week and the command to be run at that interval. See below:
    *     *     *     *     *  command to be executed
    -     -     -     -     -
    |     |     |     |     |
    |     |     |     |     +----- day of week (0 - 6) (Sunday=0)
    |     |     |     +------- month (1 - 12)
    |     |     +--------- day of month (1 - 31)
    |     +----------- hour (0 - 23)
    +------------- min (0 - 59)
    

    Crontab examples

    Writing a crontab file can be a somewhat confusing for first time users, therefore I have listed below some crontab examples:
    * * * * *  #Runs every minute
    30 * * * *  #Runs at 30 minutes past the hour
    45 6 * * *  #Runs at 6:45 am every day
    45 18 * * *  #Runs at 6:45 pm every day
    00 1 * * 0  #Runs at 1:00 am every Sunday
    00 1 * * 7  #Runs at 1:00 am every Sunday
    00 1 * * Sun  #Runs at 1:00 am every Sunday
    30 8 1 * *  #Runs at 8:30 am on the first day of every month
    00 0-23/2 02 07 *  #Runs every other hour on the 2nd of July
    
    As well as the above there are also special strings that can be used:
    @reboot  #Runs at boot
    @yearly  #Runs once a year [0 0 1 1 *]
    @annually  #Runs once a year [0 0 1 1 *]
    @monthly  #Runs once a month [0 0 1 * *]
    @weekly  #Runs once a week [0 0 * * 0]
    @daily  #Runs once a day [0 0 * * *]
    @midnight  #Runs once a day [0 0 * * *]
    @hourly  #Runs once an hour [0 * * * *]
    

    Multiple commands

    A double-ampersand “&&” can be used to run multiple commands consecutively. The following example would runcommand_01 and then command_02 once a day:
    @daily  && 
    

    Disabling email notifications

    By default a cron job will send an email to the user account executing the cronjob. If this is not needed put the following command at the end of the cron job line:
    >/dev/null 2>&1
    

    Specifying a crontab file to use

    As mentioned at the top of this post, you can create a new crontab file with the “crontab -e” command. However, you may already have a crontab file, if you do you can set it to be used with the following command:
    crontab -u  
    
    Therefore the following command…
    crontab -u tux ~/crontab
    
    …would set Tux’s crontab file to that of the file named “crontab” residing in Tux’s home directory.

    Removing a crontab file

    To remove your crontab file simply enter the following terminal command:
    crontab -r
    

    Further information

    Refer to the man page for further information about crontab. Enter the terminal command:
    man crontab
    

    create-large-colorful-text-banner-on-screen

    know how to use old banner command to print a large, high quality banner on the screen or printer. Recently, I saw colorful banner on friends laptop. How do I display large colourful characters on screen, especially on terminal? How do I create colorful text banners on screen?

    You can use following two programs to create colourful text banner:
    a] figlet - Display large characters made up of ordinary screen characters.
    b] toilet - Prints text using large characters made of smaller characters. It is similar in many ways to FIGlet with additional features such as Unicode handling, colour fonts, filters and various export formats.

    Install figlet and toilet

    Use yum or apt-get to install the tools:
    $ sudo apt-get update
    $ sudo apt-get install toilet figlet

    How do I use figlet?

    Simply use it as follows:
    $ figlet nixCraft
    Sample output:
    _ ____ __ _
    _ __ (_)_ __/ ___|_ __ __ _ / _| |_
    | '_ \| \ \/ / | | '__/ _` | |_| __|
    | | | | |> <| |___| | | (_| | _| |_
    |_| |_|_/_/\_\\____|_| \__,_|_| \__|

    figlet fonts

    The -f option can be used to specify font:
    $ figlet -f big "nixCraft"
    Sample output:
    _ _____ __ _
    (_) / ____| / _| |
    _ __ ___ _| | _ __ __ _| |_| |_
    | '_ \| \ \/ / | | '__/ _` | _| __|
    | | | | |> <| |____| | | (_| | | | |_
    |_| |_|_/_/\_\\_____|_| \__,_|_| \__|
    $ figlet -f slant "nixCraft"
    The -ct options centers the text and makes it take up the full width of the terminal:
    $ figlet -ctf slant "nixCraft"
    If you don’t want FIGlet to smush FIGcharacters into each other, use the -k (kerning) option:
    $ figlet -k "cyberciti"
    If figlet gets its input from a file, it is often a good idea to use -p:
    $ figlet -p < /path/to/file

    To lists figlet fonts and control files, enter:

    $ figlist
    Sample output
    Default font: standard
    Font directory: /usr/share/figlet
    Figlet fonts in this directory:
    ivrit
    banner
    big
    k bub
    blo cble
    al lean
    digi t
    ll s
    mini mnemonic script shadow slant sm amscript smshadow smslant standard term
    ....
    ..

    How do I use toilet command?

    Use command as follows:
    $ toilet -f bigmono9 -F gay nixCraft
    $ toilet -f mono12 -F metal nixCraft

    Sample output:
    toilet UNIX / Linux command screenshot
    toilet UNIX / Linux command screenshot

    Where,
    • -F filter-name: Set filter
    • -f fontname : Set font name
    To list installed filter, type:
    $ toilet -F list
    Sample output:
    Available filters:
    "crop": crop unused blanks
    "gay": add a rainbow colour effect
    "metal": add a metallic colour effect
    "flip": flip horizontally
    otate": perform a 180 d
    "flop": flip vertically
    "
    regrees rotation

    Running figlet / toilet automatically

    Add command to your ~/.bashrc command so that banner get displayed every time you open terminal or console:
    echo 'echo "Welcome to"' >> ~/.bashrc
    echo 'toilet -f mono12 -F metal nixCraft' >> ~/.bashrc
    ((http://www.cyberciti.biz/faq/create-large-colorful-text-banner-on-screen/))

    Creating Command Aliases on Cisco Router


    Router#configure terminal
    Enter configuration commands, one per line. End with CNTL/Z.
    Router(config) #alias exec rt show ip route
    Router(config) #alias exec on show ip ospf neighbor
    Router(config) #end
    Router#

    Router#show aliases
    Exec mode aliases:
    h help
    lo logout
    p ping
    r resume
    s show
    u undebug
    un undebug
    w where
    rt show ip route
    on show ip ospf neighbor
    Router#