Από τη θεωρία στην πράξη


Δε λέω, καλά όσα είδαμε στις προηγούμενες ενότητες.
Θα μπορούσα να τα αποκαλέσω σαν μια συμπυκνωμένη ύλη από θεωρητικές γνώσεις.
Βέβαια εσάς μπορεί να μη σας φαίνεται τόσο συμπυκνωμένη. Αρκεί να σας πω ότι βασίστηκε σε 3-4 βιβλία των 600 σελίδων το καθένα. Πάντως σε καμία περίπτωση δεν θα πρέπει να θεωρηθεί ένας πλήρες οδηγός για "bash scripting". Μάλλον για μια καλή αρχή

Για τους διαβαστερούς παραθέτω τα ακόλουθα links από το "The Linux Documentation Project (TLDP)":
http://tldp.org/LDP/Bash-Beginners-Guide/html/
http://tldp.org/LDP/abs/html/

Είναι όμως καιρός να περάσουμε από τη θεωρία στην πράξη και να φτιάξουμε κάτι χρήσιμο:

Η αλήθεια είναι ότι ποτέ μα ποτέ δεν κατάφερα να βρω ένα ολοκληρωμένο πρόγραμμα για backup που να ικανοποιεί όλες μου τις απαιτήσεις. Έτσι είπα να αξιοποιήσω το πολύ δυνατό εργαλείο rsync και να το φέρω στα μέτρα μου γράφοντας ένα scriptάκι

Κάπου το Μάρτιο του 2008 λοιπόν ήταν έτοιμη η πρώτη του έκδοση (0.1) η οποία φιλοξενείται μέχρι και σήμερα στην ιστοδελίδα του περιοδικού "Ελληνικό Linux Format" (thank u dimitris ):
http://www.linuxformat.gr/?q=forum/backup-%CE%A4%CE%A9%CE%A1%CE%91
Σήμερα (08 Αυγ. 08 - κι ακόμα να πάω διακοπές ), το σουλούπωσα λίγο (του άλλαξα τα φώτα δηλαδή !!) και βουαλά η έκδοση 0.2.
Παραθέτω μερικά γραφόμενα μου από το Ελληνικό LXF για να μπείτε στο πνεύμα (Μπορείτε βέβαια με ένα κλικ να πάτε εκεί και να το διαβάσετε ολόκληρο !!):

μια ματιά σε μερικές αλήθειες:

- Αχρείαστο να' ναι το backup, αλλά όταν έρθει εκείνη η ώρα πρέπει να είναι εύχρηστο.
- Το backup ΔΕΝ πρέπει να είναι μια επίπονη και πολύωρη διαδικασία που αποτρέπει τη συχνή χρήση της.
- Οι εκφράσεις "στήνω έναν backup server" ή "είσαι σίγουρος ότι τρέχει o daemon?" ή "μόνο με ένα ssh tunel θα σωθείς" είναι σαν να βγαίνουν από το στόμα του αρχιμηχανικού του galactica. Εγώ θέλω απλά μια στο τόσο να δίνω μια εντολή και ως δια μαγείας να δημιουργούνται αντίγραφα ασφαλείας από τα πολύτιμα αρχειάκια μου.

...και σε μερικές απαιτήσεις:

- Τα αντίγραφα ασφαλείας πρέπει να διατηρούν ακριβώς τις ίδιες ιδιότητες με τα αυθεντικά αρχεία (permissions, owner, group, time κτλ).
- Δεν θέλω κάθε φορά που κάνω backup να αντιγράφεται εξ' ολοκλήρου η διακοσίων gigabyte συλλογή με mp3 που έχω, αλλά μόνο οι αλλαγές.
- Αν σβήσω έναν υποκατάλογο ή αρχείο ή το μεταφέρω κάπου αλλού, θέλω να έχει την ίδια επίπτωση και στο αντίγραφο ώστε να μην καταντήσει μπάχαλο
- Αν έχω ξεχάσει να κάνω mount τον usb σκληρό μου ή κατά λάθος δώσω ένα όνομα υποκαταλόγου που δεν υπάρχει, θέλω αυτό να γίνεται αντιληπτό και να προειδοποιούμαι.
- Αν τροποποιήσω ένα αρχείο θέλω να γίνει το ίδιο και στο αντίγραφο του.
- Έχω έναν υποκατάλογο στο σκληρό, αλλά τα αρχεία του τα δουλεύω και με flashάκι σε άλλους υπολογιστές ή με το laptop μου. Πριν το backup θέλω να συγχρονίσω το directory αυτό με το flashάκι ή το laptop και να μείνουν τα πιο καινούργια αρχεία και στα δυο.
- Κάθε υποκατάλογος πρέπει να δημιουργεί ένα αντίγραφο ασφαλείας εκεί ακριβώς που θα του πω εγώ κι όχι όλα χύμα στο '/' ενός σκληρού.
- Να γίνονται όλες οι διαδικασίες με ασφάλεια - προστατεύοντάς τα original αρχεία μου αλλά και τα ήδη ευμεγέθη αντίγραφα που έχω κάνει παλιότερα.

Για να φέρουμε σε πέρας την αποστολή "backup" Θα χρησιμοποιήσουμε το ισχυρότατο εργαλείο - εντολή rsync.
Περισσότερες λεπτομέρειες γι' αυτήν, θ' ακολουθήσουν παρακάτω


Τι θα χρειαστούμε:

Φυσικά το rsync.
Εγκαταστήστε το πακέτο (πχ. ένα 'apt-get install rsync' αρκεί)

Και τώρα (μη τρομάξετε) ακολουθεί ο κώδικας της νέας έκδοσης του "luckyBackup.sh" script :


#!/bin/bash
 
# filename            : luckyBabkup.sh
# Date of creation        : March 2008
# Date of last modification    : 08 August 2008
# Creator            : luckyb
# modified by            : luckyb on 08 August 2008
 
# "luckyBuckup.sh" syncs and backups all the directories that all declared in the "user input section" below.
 
# Syncing means that in each of a directory couple, files that have most recently modified will be kept.
# e.g. Directory "A" contains 2 (file1, file2) files and directory "B" contains 3 files (file1, file2, file3)
# if file1 in dir "A" is modified later than file1 in dir "B" and file2 in dir "A" is older than file2 in dir "B" then
# at the end of syncing both dir "A" & dir "B" will contain the same files:
# file1 from dir "A", file2 form dir "B", file3 from dir "B"
# Syncing is done using the "sync_stuff ()" function
 
# backing up means that a source directory will create an exact image of itself in a destination directory.
# Source directory is left untouched, while destination directory is modified so as to be exactly the same as the source.
# Syncing is done using the "backup_stuff ()" function
 
# Before anything is done the script checks if it is run with root privileges with function "check_user ()"
# This is important because we're gonna move-copy-delete files that probably have different permissions.
# and the simple user might not authorised to handle all of them.
 
# Then, using the function "check_dirs ()", the script checks if all the directories that were declared in the
# user input section exist or are empty.
# If one of the sync dirs does not exist the specific procedure is going to be skiped
# If one destination directory does not exist it's going to be created
# If on3 source directory is empty or doesn't exist then the procedure is going to be skipped to protect our backup files
 
 
 
IFS=$','         # set IFS to treat spaces as spaces - DO NOT touch this
 
# ==============================================================================================================
# ------------------------------------------ user input --------------------------------------------------------
# ==============================================================================================================
 
# Sync ---------------------------------------------------------------------------------------------------------
# Please define directories/files to be synced first :
 
syncdirA[${#syncdirA[*]}]="/home/luckyb//UCL/diplomatiki/"
syncdirB[${#syncdirB[*]}]="/media/LUCKYUSB/UCL/diplomatiki/"
 
#syncdirA[${#syncdirA[*]}]="path/to/2nd/syncdirA"
#syncdirB[${#syncdirB[*]}]="path/to/2nd/syncdirB/"
# 
# . . .
 
 
# Backup -------------------------------------------------------------------------------------------------------
# Please define directories to be backed up as well as the destination of the backups :
 
sourcedir[${#sourcedir[*]}]="/home/luckyb/mp3/"    
destdir[${#destdir[*]}]="/media/backups/mp3/"
 
sourcedir[${#sourcedir[*]}]="/home/luckyb/photos/"
destdir[${#destdir[*]}]="/media/backups/photos/"
 
sourcedir[${#sourcedir[*]}]]="/home/luckyb/docsNstuff"
destdir[${#destdir[*]}]="/media/XoraoTaPanta"
 
#sourcedir[${#sourcedir[*]}]="/path/to/4rth/source/dir/"
#destdir[${#destdir[*]}]="/path/to/4th/dest/dir/"
#
# . . .
 
 
# sourcedir[x] is the source directory and destdir[x] is the destination backup directory
# 'x' is an integer starting from 0 and increasing by 1. It must be the same for the sourcedir and the destdir
# use '/' at the end of the sourcedir and destdir not to copy 'root' source but only subfolders and files
 
# Example -----------------------------------------------------------------------------------------------------
# The following will backup /home/luckyb/mp3 to /media/backups/ - a directory /media/backups/mp3 will be Created
# The directory /home/luckyb/photos will be backed up to /media/backups/luckyb_photos - All contents of the /home/luckyb/photos directory will be backedup but no photos directory will be created in /media/backups/luckyb_photos
 
# sourcedir[0]="/home/luckyb/mp3"
# destdir[0]="/media/backups"
#
# sourcedir[1]="/home/luckyb/photos/"
# destdir[1]="/media/backups/luckyb_photos/"
 
 
# user input end-----------------------------------------------------------------------------------------------
 
# ==============================================================================================================
# main script (this is where all is done)
# Do not modify this part unless you really know what you are doing (You have been warned)
# ==============================================================================================================
# color variables
W="[1;31m" # red     - Warning
E="[1;33m" # yellow     - info
S="[1;32m" # green     - info 2
Q="[1;36m" # CYAN     - User Action
M="[1;34m" # BLUE     - Info 3
C="[1;35m" # MAGENTA     - data
N="[0;39m" # default system console color: Normal
 
# ==============================================================================================================
# intro () function
# just prints stuff on the screen
# ==============================================================================================================
intro()
{
    clear
    echo
    echo "${M}            luckybackup script  -  version 0.2"
    echo "============================================================================================"
    echo
    echo "${S}This script will (hopefully) sync and backup everything you need in no time${N}"
    echo "(Well, it might take a little longer if run for the first time ;-)"
    echo
    echo "${E}Make sure that:"
    echo "You have already declared and mounted all the directories that need to be synced/backed-up"
    echo "If you haven't, don't worry. I will check everything before doing something  stupid !!"
    echo
    echo "Hit ${Q}'enter' to continue${E} otherwise press ${Q}'ctrl+c' to exit NOW${N}"
    read
    echo
}
 
# ==============================================================================================================
# check_user () function
# Checks if the script is run with su privileges and asks for confirmation to go on if not
# ==============================================================================================================
check_user()
{
    declare -a options
    options[${#options[*]}]="Just continue with the script, I know what I'm doing"
    options[${#options[*]}]="Quit now. I'll try again as su"
 
    if [ "$USER" != "root" ]
    then
        echo
        echo "${S}$USER,"
        echo "${W}Having in mind that ${C}you are not root${W}, some of the operations you have asked for, might produce errors"
        echo
        echo "${Q}What would you like me to do??${N}"
        select opt in "${options[@]}";
        do
            case ${opt} in
                ${options[0]})
                    echo "${E}ok, but you have been WARNED${N}"; ;;
                *)
                    echo "${E}exiting now...${N}";
                    exit 0; ;;
            esac;
            break
        done
    fi
}
 
# ==============================================================================================================
# check_dirs () function
# Checks if the directories declared are valid (they exist or are empty) and asks for confirmation to go on
# ==============================================================================================================
check_dirs()
{
    ask=0            # Boolean variable. TRUE if an error occurs and user confirmation is mandatory    
 
    clear
    echo "=============================================================================================================="
    echo "${S}            Checking if the directories you specified exist or are empty${N}"
    echo "=============================================================================================================="
    echo
    sleep 0.5        # a small sleep just cause I don't like things happening too fast
 
    for ((count="0"; count <${#syncdirA[*]}; count++))    # Check sync dirs
    do
        sleep 0.2    # a small sleep just cause I don't like things happening too fast
        echo "--------------------------------------------------------------------------------------------------------------"
        # Do both sync directories exist and are not empty ??
        if [ -d ${syncdirA[$count]} ] && [ -d ${syncdirB[$count]} ] && [ "$(ls -A ${syncdirA[$count]})" ] && [ "$(ls -A ${syncdirB[$count]})" ]
        then
            echo "[ ${M}ok${N} ]        ->    Sync directory ${M}${syncdirA[$count]}${N}"
            echo "[ ${M}ok${N} ]        ->    Sync directory ${M}${syncdirB[$count]}${N}"
            false
 
        # Is one or both of the sync dirs not existent ??
        elif [ ! -d ${syncdirA[$count]} ] || [ ! -d ${syncdirB[$count]} ] 
        then
            echo "[ ${E}WARNING${N} ]    ->    Sync directory ${E}${syncdirA[$count]}${N}"
            echo "                and-or ${E}${syncdirB[$count]}${N} does NOT exist"
            echo
            echo "I will ${E}skip${N} this operation"
            ask=1
 
        # is one or both of the sync dirs empty ??
        else
            echo "[ ${W}CRITICAL${N} ]    ->    Sync directory ${W}${syncdirA[$count]}${N}"
            echo "                and-or ${E}${syncdirB[$count]}${N} is Empty"
            echo
            echo "I will ${W}NOT skip${N} this operation. The directories are going to be synced anyway !!"
            ask=1
        fi
    done
    count=0
 
    for ((count="0"; count <${#sourcedir[*]}; count++))    # Check source & destination directories
    do
        sleep 0.2    # a small sleep just cause I don't like things happening too fast
        echo "--------------------------------------------------------------------------------------------------------------"
 
        # If the source directory exists and it is not empty (ok)
        if [ -d ${sourcedir[$count]} ] && [ "$(ls -A ${sourcedir[$count]})" ] 
        then
            echo "[ ${M}ok${N} ]        ->    Source directory ${M}${sourcedir[$count]}${N}"
            false
 
            # If the destination directory exists and it is not empty (ok)
            if [ -d ${destdir[$count]} ] && [ "$(ls -A ${destdir[$count]})" ] 
            then
                echo "[ ${M}ok${N} ]        ->    Destination directory ${M}${destdir[$count]}${N}"
                false
 
            # If the destination directory does not exist or it is empty
            else
                echo "[ ${W}CRITICAL${N} ]    ->    Destination directory ${W}${destdir[$count]}${N} "
                echo "            does NOT exist or it is empty (Check if you mount something there !!)"
                echo
                echo "I will ${W}NOT skip${N} this operation."
                echo "The Destination Directory will be created if it doesn't exist and filled with new backup data"
                echo "Beware if it is not the first time your run the script for the specific backup operation"
                ask=1
            fi
 
        # If the source directory does not exist or it is empty
        else
            echo "[ ${E}WARNING${N} ]    ->    Source directory ${E}${sourcedir[$count]}${N}"
            echo "            does NOT exist or it is empty (Check if you mount something there !!)"
            echo
            echo "I will ${E}skip${N} this operation to protect your backup data (if any)"
            ask=1
        fi
    done
    echo "--------------------------------------------------------------------------------------------------------------"
 
    if [ $ask == 1 ]    # if something went wrong in the previous directories checks
    then
        declare -a options
        options[${#options[*]}]="Just continue with the script, I know what I'm doing !!"
        options[${#options[*]}]="Quit now. I'll fix the problems and try again"
        echo
        echo 
        echo "${S}It is advised that you quit now and fix any problems"
        echo "To keep your data safe I will take all the actions mentioned in the above Warning messages"
 
        echo 
        echo "${Q}What would you like me to do??${N}"
        select opt in "${options[@]}";
        do
              case ${opt} in
                  ${options[0]})
                    echo "${E}ok, continuing with the script...${N}";
                    sleep 1; ;;
                *)
                    echo "${E}exiting now...${N}";
                    exit 0; ;;
              esac;
            break
        done
 
    else    # if all went ok in the previous directories checks
        echo
        echo "${S}All directories valid :-)"
        echo "You are ready to go${N}"
        echo
        echo "Hit enter to continue..."
        read
    fi
}
 
# ==============================================================================================================
# sync_stuff () function
# Syncs all of the directories in the syncdirA array with the directories in the syncdirB array
# ==============================================================================================================
sync_stuff()
{
echo "${S}------------------------------------------------ Syncing stuff -----------------------------------------------  ${N}"
echo
 
syncno=${#syncdirA[*]}        #No. of dirs to be synced
count=0                #typical count variable
while [ $count -le $((syncno-1)) ]
do
    echo "now syncing directory $((count+1)) of $syncno:"
    echo "${C} ${syncdirA[$count]} ${N}"
    echo "with"
    echo "${C} ${syncdirB[$count]} ${N}"
    echo
    if [ -d ${syncdirA[$count]} ] && [ -d ${syncdirB[$count]} ]    #check if both sync directories exist
    then
        #echo "Executing rsync NOW !!"
        rsync -h --archive --progress --stats --update ${syncdirB[$count]} ${syncdirA[$count]}
        rsync -h --archive --progress --stats --update ${syncdirA[$count]} ${syncdirB[$count]}
        echo "...done"
        echo
    else
        echo
        echo "${W}One or both directories are missing...${N}" 
        echo "...skipping"
    fi
    count=$((count+1))
    echo "--------------------------------------------------------------------------------------------------------------"
    echo
done
}
 
# ==============================================================================================================
# backup_stuff () function
# Backs-up all of the directories in the sourcedir array to the directories in the destdir array
# ==============================================================================================================
backup_stuff()
{
 
    dirno=${#sourcedir[*]}        #No. of dirs to be backed up
 
    echo "${S}----------------------------------------------- Backing up stuff ---------------------------------------------  ${N}"
    echo
    count=0
    while [ $count -le $((dirno-1)) ]
    do
        echo "backing up directory $((count+1)) of $dirno:"
        echo "${C} ${sourcedir[$count]} ${N}"
        echo "to"
        echo "${C} ${destdir[$count]} ${N}"
        echo 
        if [ -d ${sourcedir[$count]} ] && [ "$(ls -A ${sourcedir[$count]})" ]    #check if sourcedir exists or empty
        then
            #echo "Executing rsync NOW !!"
            rsync -h --archive --progress --stats --update --delete-after  ${sourcedir[$count]} ${destdir[$count]}
            echo "...done"
            echo
        else
            echo
            echo "${W}${sourcedir[$count]}${N} is missing" 
            echo "...skipping"
        fi
        count=$((count+1))
        echo "--------------------------------------------------------------------------------------------------------------"
        echo
    done
}
 
# ==============================================================================================================
# thats_all () function
# Just prints a message to the screen
# ==============================================================================================================
thats_all()
{
    echo "=============================================================================================================="
    echo "${Q}                Syncing and backing up is finished"
    echo "                hope everything went ok ;-)${N}"
    echo "=============================================================================================================="
echo
}
 
# ==============================================================================================================
# main code
# ==============================================================================================================
intro
check_user
check_dirs
sync_stuff
backup_stuff
thats_all
 
unset IFS # IFS back to normal
exit 0

Παίρνω βαθιά ανάσα και προχωράω στην ανάλυσή:

Αρκετές από τις πρώτες γραμμές, όπως βλέπετε είναι σχόλια και χρησιμοποιούνται για να δηλώσουμε την τυπική επικεφαλίδα με το όνομα του αρχείου, τον δημιουργό του κλπ (βλέπε "Το πρώτο μου scriptάκι).
επίσης εδώ γράφω λίγα λόγια για το τι κάνει αυτό το script και πως.

Έπειτα, ξεκινάει το μοναδικό τμήμα του κώδικα το οποίο μπορεί ο κάθε χρήστης που θα πέσει το script στα χέρια του να τροποποιήσει ανάλογα με τις δικές του ανάγκες.
Το τμήμα αυτό περιλαμβάνει τη δήλωση 6 συστοιχιών (arrays) μεταβλητών (3 ζευγάρια θα μπορούσαμε να πούμε) στις οποίες κάθε μέλος είναι και το όνομα ενός υποκαταλόγου, ως εξής:

Α. syncdirA και syncdirB
Τα μέλη των arrays αυτών περιέχουν τα ζευγάρια των υποκαταλόγων που θέλουμε να συγχρονίσουμε.
Πολλές φορές κρατάμε τα ίδια αρχεία στο σταθερό pc μας και ίσως σ'ένα φλασάκι ώστε να τα επεξεργαζόμαστε είτε από το σπίτι, είτε από οποιονδήποτε Η/Υ. Είναι φρόνιμο, επειδή ποτέ δεν θυμόμαστε πότε επεξεργαστήκαμε ποιο αρχείο και από που, να συγχρονίσουμε τους υποκαταλόγους μας ώστε στο τέλος να περιέχουν και οι δυο τα πιο πρόσφατα αρχεία.

Δηλώνουμε λοιπόν:

syncdirA[${#syncdirA[*]}]="/home/luckyb//UCL/diplomatiki/"
syncdirB[${#syncdirB[*]}]="/media/LUCKYUSB/UCL/diplomatiki/"

και όσα άλλα ζευγάρια θέλουμε ως επόμενα μέλη των arrays:

syncdirA[${#syncdirA[*]}]="path/to/2nd/syncdirA"
syncdirB[${#syncdirB[*]}]="path/to/2nd/syncdirB/"
 
syncdirA[${#syncdirA[*]}]="path/to/2nd/syncdirA"
syncdirB[${#syncdirB[*]}]="path/to/2nd/syncdirB/"
. . .

ώστε να συγχρονιστούν ο υποκατάλογος "/home/luckyb//UCL/diplomatiki/" με τον "/media/LUCKYUSB/UCL/diplomatiki/"
επίσης ο "/path/to/2nd/syncdirA" με τον "/path/to/2nd/syncdirB" κ.ο.κ.

και τώρα θα μου πείτε "τί είναι αυτό το:

syncdirA[${#syncdirA[*]}]

;; Γιατί δεν χρησιμοποιεί τη γνωστή σύνταξη syncdirA[0]=.... και syncdirA[1]=.... κλπ ;;"

Κολπάκι: Έστω ότι έχουμε ένα array με όνομα ARRAY. Το πόσα μέλη έχει το ARRAY μας το δίνει η έκφραση

${#ARRAY[*]}
Δηλαδή αν αρχικά η συστοιχία μας έχει μηδέν (0) μέλη γράφοντας:

ARRAY[${#ARRAY[*]}]=Τιμή
είναι σα να γράφουμε ARRAY[0]=Τιμή. Τώρα το ARRAY έχει ένα μέλος, οπότε γράφοντας:

ARRAY[${#ARRAY[*]}]=Τιμή 
είναι σα να γράφουμε ARRAY[1]=Τιμή.
Έτσι προσθέτουμε καινούργια μέλη στο ARRAY χωρίς να μας αποασχολούν τα INDEX (οι αριθμοί μέσα στις αγγύλες), τα οποία ξεκινούν από το μηδέν (0) και αυξάνονται κατά ένα (δηλ. 0,1,2,3,4,...) για κάθε νέο μέλος


Β. sourcedir και destdir
Τα μέλη των arrays αυτών περιέχουν τα ονόματα των υποκαταλόγων που θα κάνουμε backup (sourcedir) και των αντίστοιχων στους οποίους θα δημιουργηθεί το backup (destdir).

Στον συγκεκριμένο κώδικα ο υποκατάλογος "/home/luckyb/mp3/" θα δημιουργήσει ένα αντίγραφο ασφαλείας στον υποκατάλογο "/media/backups/mp3/" (Το "/media/backups" μπορεί να είναι κάλλιστα το σημείο που γίνεται mount ένας εξωτερικός σκληρός δίσκος ) κ.ο.κ.

ΠΡΟΣΟΧΗ στη χρήση των καθέτων στο τέλος των υποκαταλόγων !!
πχ Με την δήλωση:
sourcedir[0]="/home/luckyb/mp3" (χωρίς κάθετο στο τέλος)
destdir[0]="/media/backups"
θα αντιγραφεί ολόκληρος ο υποκατάλογος "mp3" από το "/home/luckyb".
Στον υποκατάλογο "/media/backups" θα δημιουργηθεί ξεχωριστός υποκατάλογος "mp3"

ενώ με την δήλωση:
sourcedir[0]="/home/luckyb/mp3/" (με κάθετο στο τέλος)
destdir[0]="/media/backups/"
δεν θα δημιουργηθεί υποκατάλογος "mp3" μέσα στον "/media/backups". Όλα τα περιεχόμενα του "/home/luckyb/mp3" θα γραφούν χύμα μέσα στον "/media/backups/"

Επίσης στις αρχικές δηλώσεις (πριν αρχίσει ο τζερτζελές !!) μεταβλητών θα δούμε τα εξής:

# color variables
W="[1;31m" # red     - Warning
E="[1;33m" # yellow     - info
S="[1;32m" # green     - info 2
Q="[1;36m" # CYAN     - User Action
M="[1;34m" # BLUE     - Info 3
C="[1;35m" # MAGENTA     - data
N="[0;39m" # default system console color: Normal
Οι τιμές αυτών των μεταβλητών θα δώσουν χρώμα στο άχαρο τερματικό μας.

...και πάμε στο ζουμί:

Θα κατηγοριοποιήσουμε τις ξεχωριστές διεργασίες που κάνει το script ως κατωτέρω:

1. Εμφάνιση κάποιων τυπικών εισαγωγικών μηνυμάτων στην οθόνη.
2. Έλεγχος αν αυτός που τρέχει το script είναι ένας απλός χρήστης ή ο root. Επειδή υπάρχει περίπτωση κάποια από τα αρχεία που θα δημιουργήσουμε αντίγραφα ασφαλείας να έχουν ιδιότητες που δεν θα επιτρέψουν την αντιγραφή τους (με διατήρηση των ιδιοτήτων αυτών) από έναν απλό χρήστη, είναι σοφό να εκτελούμε το script ως υπερχρήστης (su). Επίσης θα μας δίνεται τη δυνατότητα να συνεχίσουμε ή όχι αν είμαστε απλός χρήστης.
3. Έλεγχος αν οι υποκατάλογοι που δηλώσαμε προηγουμένως υπάρχουν ή αν είναι κενοί (για ν'αποφύγουμε τις πατάτες !!. Φανταστείτε να έχετε μια συλλογή μουσικής 200GB σε εξωτερικό σκληρό και να έχετε δημιουργήσει ήδη ένα backup σε έναν δίσκο. Αν έχετε ξεχάσει να κάνετε mount τον δίσκο με τα mp3, τότε θα σβηστεί και το backup του !!). Επίσης θα μας δίνεται την επιλογή αν υπάρχουν λάθη να μη συνεχίσουμε.
4. Συγχρονισμός των υποκαταλόγων που δηλώσαμε (μόνο αν υπάρχουν και οι δυο).
5. Δημιουργία backup των υποκαταλόγων που δηλώσαμε (μόνο αν υπάρχουν οι source υποκατάλογοι και δεν είναι άδειοι).
6. Εμφάνιση ενός μηνύματος ότι όλα πήγαν καλά (μάλλον )

Για να κάνουμε όσο πιο σαφή τη δομή του κώδικά μας, για κάθε μια από τις 6 αυτές διεργασίες θα δημιουργήσουμε μια συνάρτηση (function) και θα τις καλούμε στο τέλος μια μια.
Ορίστε και τα αντίστοιχα ονόματα των συναρτήσεων (θα τα βρείτε να καλούνται και στο "κυρίως τμήμα" του κώδικα έτσι ακριβώς):

intro
check_user
check_dirs
sync_stuff
backup_stuff
thats_all

1. intro () function

Η μια από τις δυο πιο απλές συναρτήσεις μας.
Απλά με μια ακολουθία από "echo" μας εμφανίζει ένα αρχικό μύνημα στην οθόνη που λέει τι είναι αυτό που μόλις τρέξαμε και άλλες χρήσιμες (?) πληροφορίες.


2. check_user() function

Αρχικά δηλώνουμε το array "options" (με δυο μέλη) το οποίο περιέχει τις επιλογές που θα μας δοθούν αν δεν είμαστε root (με την εντολή "select"):


    declare -a options
    options[${#options[*]}]="Just continue with the script, I know what I'm doing"
    options[${#options[*]}]="Quit now. I'll try again as su"


Η μεταβλητή "$USER" είναι μεταβλητή του bash και η τιμή της είναι ίδια με το όνομα του τρέχων χρήστη. Μπορείτε να το επιβεβαιώσετε δίνοντας την εντολή:

echo $USER

Οπότε με τη συνθήκη "if [ "$USER" != "root" ]" (δηλαδή αν ο τρέχων χρήστης ($USER) ΔΕΝ είναι (!=) ο "root") θα εκτελέσουμε τον κώδικα που ακολουθεί μέχρι το "fi", διαφορετικά η συνάρτηση θα φτάσει στο τέλος της και το script θα συνεχίσει κανονικά.

Στον κώδικα της συνθήκης, εκτός από διάφορα μηνύματα (δεν είσαι ο root, τι πας να κάνεις εκεί;; κλπ) χρησιμοποιούμε την εντολή "select" σε συνδυασμό με την "case" για να δημιουργήσουμε ένα μενού και να δώσουμε την επιλογή στον χρήστη να συνεχίσει κανονικά ή να βγει από το script (exit (0)):

if [ "$USER" != "root" ]
    then
        echo
        echo "${S}$USER,"
        echo "${W}Having in mind that ${C}you are not root${W}, some of the operations you have asked for, might produce errors"
        echo
        echo "${Q}What would you like me to do??${N}"
        select opt in "${options[@]}";
        do
            case ${opt} in
                ${options[0]})
                    echo "${E}ok, but you have been WARNED${N}"; ;;
                *)
                    echo "${E}exiting now...${N}";
                    exit 0; ;;
            esac;
            break
        done
    fi

3. check_dirs () function

Αρχίζουν τα δύσκολα !!
Το ζητούμενο εδώ είναι να ελέγξουμε όλους τους υποκαταλόγους που έχουν δηλωθεί για συγχρονισμό ή backup και αφενός να προειδοποιήσουμε τον χρήστη για κινδύνους, αφετέρου να τον ενημερώσουμε ότι μερικές από τις εργασίες που έχει ζητήσει δεν θα πραγματοποιηθούν για να προστατευτούν τα δεδομένα του.

Συγκεκριμένα ελέγχουμε:
Α. Αν οι υποκατάλογοι που είναι για συγχρονισμό υπάρχουν κι αν ΝΑΙ τότε αν είναι άδειοι.

Αν κάποιος υποκατάλογος δεν υπάρχει τότε η συγκεκριμένη διεργασία δεν θα πραγματοποιηθεί.
Αν κάποιος υποκατάλογος είναι κενός τότε απλά θα προειδοποιήσουμε

Β. Αν τόσο οι υποκατάλογοι που θα γίνουν backup (source), όσο και οι υποκατάλογοι στους οποίους θα δημιουργηθεί το backup (destination), υπάρχουν και αν είναι κενοί

Αν κάποιος από τους destination υποκαταλόγους δεν υπάρχει ή είναι κενός, τότε θα προειδοποιήσουμε ότι θα δημιουργηθεί και/ή θα γεμίσει με δεδομένα.
Αφενός αυτό είναι χρήσιμο για κάποιον που κάνει backup για πρώτη φορά γιατί επιβεβαιώνει τις επιλογές του πριν ξενινήσουν οι αντιγραφές αρχείων.
Αφετέρου, φανταστείτε εκείνον που κάνει το backup του, σε έναν εξωτερικό σκληρό δίσκο τον οποίο κάνει mount στο πχ /media/backups. Αν έχει ξεχάσει να προσαρτήσει τον εξωτερικό σκληρό του, τότε το script θα δημιουργήσει κανονικά το αντίγραφο ασφαλείας στο τρέχον partition που έχουμε τη διανομή μας, στον υποκατάλογο /media/backups. ...και άντε να βγάλεις άκρη μετά !!

Αν κάποιος από τους source υποκαταλόγους δεν υπάρχει ή είναι κενός, τότε θα αναφέρουμε ότι η συγκεκριμένη διεργασία δεν θα πραγματοποιηθεί.
Καταρχήν είναι τελείως άσκοπο να κάνουμε backup σ' έναν ανύπαρκτο ή κενό υποκατάλογο
Ο βασικός σκοπός όμως αυτού του ελέγχου είναι να προστατέψουμε τα ήδη ευμεγέθη αντίγραφα ασφαλείας που πιθανόν να υπάρχουν. Φανατστείτε ο source υποκατάλογος να είναι η 200GB συλλογή μας με μουσική και να βρίσκεται σε κάποιον αφαιρούμενο δίσκο. Αν έχουμε πάρει ήδη μια φορά αντίγραφο ασφαλείας (παίρνει μια-δυο μέρες την πρώτη φορά ), τότε κάθε φορά που ξαναεκτελούμε τη διεργασία, θα αντιγράφονται στο backup μόνο οι αλλαγές που έχουμε κάνει. Αυτό συμπεριλαμβάνει και το σβήσιμο αρχείων αν συνέβη κάτι τέτοιο στα αυθεντικά δεδομένα (source). Αν τώρα για κάποιο λόγο ξεχάσουμε να κάνουμε mount τον σκληρό που περιέχει τα source δεδομένα μας τότε θα θέσω το εξής (ρητορικό) ερώτημα: "Τι θα συμβεί στο 200GB backup που ήδη έχουμε ;;"

Γ. Ανάλογα αν τ' αποτελέσματα των ελέγχων (Α) & (Β) ήταν όλα επιτυχή ή όχι:
θα δίνουμε τη δυνατότητα να σταματήσει ο χρήστης την εκτέλεση του script ή απλά θα τυπώνουμε ένα μήνυμα επιτυχίας !!

Για να δούμε πως θα γίνουν όλ'αυτά στην πράξη:

Α. Ξεκινάμε ελέγχοντας τους υποκαταλόγους που είναι για συγχρονισμό.

Τα βάζουμε όλα σ' ένα "for loop" το οποίο θα εκετελεστεί τόσες φορές όσα και τα μέλη της συστοιχίας "syncdirA". Χρησιμοποιούμε την μεταβλητή "count" η οποία θα παίρνει διαφορετικές τιμές για κάθε φορά που τρέχει το loop, ξεκινώντας από το μηδέν (0), θ' αυξάνεται κατά ένα (1) και θα καταλήγει με τιμή ένα μικρότερο από το σύνολο των μελών του array "syncdirA". Δηλαδή αν το "syncdirA" έχει 4 μέλη, η count θα πάρει τις τιμές 0, 1, 2 και 3.:
for ((count="0"; count <${#syncdirA[*]}; count++))
Έτσι για κάθε φορά που θα τρέχει το loop θα χρησιμοποιούμε την count για να αναφερθούμε σε τιμές συγκεκριμένων μελών των arrays με την έκφραση "${syncdirA[$count]}" και "${syncdirB[$count]}"

Μέσα στο for loop θα ελέγξουμε με μια συνθήκη "if" αν τα μέλη των συστοιχιών syncdirA και syncdirB είναι υποκατάλογοι που υπάρχουν και δεν είναι κενοί. Για να δούμε αν είναι κενοί χρησιμοποιούμε την έξοδο της εντολής "ls" η οποία μας βγάζει ως έξοδο τα περιεχόμενα ενός υποκαταλόγου. Αν βγάλει οποιαδήποτε έξοδο τότε ο υποκατάλογος στον οποίο αναφέρεται δεν είναι κενός. Αν δεν βγάλει έξοδο ή μάλλον αν βγάλει μηδενική (ή FALSE) έξοδο τότε ο υποκατάλογος στον οποίο αναφέρεται είναι άδειος:

if [ -d ${syncdirA[$count]} ] && [ -d ${syncdirB[$count]} ] && [ "$(ls -A ${syncdirA[$count]})" ] && [ "$(ls -A ${syncdirB[$count]})" ]

Έχουμε λοιπόν μέσα σε ένα if, 4 συνθήκες οι οποίες πρέπει να ισχύουν όλες. Αυτό το πετυχαίνουμε με τα σύμβολα "&&" που σημαίνουν ΚΑΙ (AND).
Τα αντίστοιχα σύμβολα για να πούμε "αν ισχύει αυτό Ή (OR) το άλλο (το ένα από τα δυο τέλος πάντων)" είναι τα "||"
Αν λοπόν ισχύει το σύνολο των συνθηκών της if τότε θα μας βγάλει μήνυμα ότι οι υποκατάλογοι μας είναι ok:

if [ -d ${syncdirA[$count]} ] && [ -d ${syncdirB[$count]} ] && [ "$(ls -A ${syncdirA[$count]})" ] && [ "$(ls -A ${syncdirB[$count]})" ]
        then
            echo "[ ${M}ok${N} ]        ->    Sync directory ${M}${syncdirA[$count]}${N}"
            echo "[ ${M}ok${N} ]        ->    Sync directory ${M}${syncdirB[$count]}${N}"


Αν τώρα δεν ισχύει έστω και μια από τις ανωτέρω συνθήκες αρχίζουμε τις αλχημείες.
Με μια elif ελέγχουμε αν ένα από τα syncdirA[xx] και syncdirB[xx] δεν (προσέξτε το θαυμαστικό !) υπάρχουν. Αν όντως ένα από τα δυο δεν υπάρχει τότε θα μας προειδοποιεί ότι δεν θα εκτελεστεί αυτή η διεργασία:
        elif [ ! -d ${syncdirA[$count]} ] || [ ! -d ${syncdirB[$count]} ] 
        then
            echo "[ ${E}WARNING${N} ]    ->    Sync directory ${E}${syncdirA[$count]}${N}"
            echo "                and-or ${E}${syncdirB[$count]}${N} does NOT exist"
            echo
            echo "I will ${E}skip${N} this operation"
            ask=1


Τέλος αν φτάσαμε μέχρι εδώ και δεν ισχύουν οι συνθήκες του if και του elif τότε ισχύει το ότι ο ένας ή και οι δυο υποκατάλογοι προς συγχρονισμό είναι κενοί (σκεφτείτε το λίγο, βγαίνει !!). Οπότε απλά ενημερώνουμε τον χρήστη έτσι για να ξέρει ότι εκεί που είχε έναν γεμάτο υποκατάλογο κι έναν κενό, θα καταλήξει με δυο γεμάτους που θα περιέχουν τα ίδια πράγματα:


        else
            echo "[ ${W}CRITICAL${N} ]    ->    Sync directory ${W}${syncdirA[$count]}${N}"
            echo "                and-or ${E}${syncdirB[$count]}${N} is Empty"
            echo
            echo "I will ${W}NOT skip${N} this operation. The directories are going to be synced anyway !!"
            ask=1
 

Προσέξτε ότι όταν έχουμε ένα μήνυμα λάθους τότε θέτουμε την τιμή της μεταβλητής ask=1. Αυτή θα μας χρειαστεί αργότερα για να προσδιορίσουμε το αν θα πρέπει να ρωτήσουμε το χρήστη αν θέλει να συνεχίσει την εκτέλεση του script ή όχι.



Β. Συνεχίζουμε ελέγχοντας τους υποκαταλόγους που θα γίνουν backup και τους προορισμούς τους.

Όμοια με πριν τα βάζουμε όλα σ' ένα for loop χρησιμοποιώντας τη μεταβλητή count η οποία παίρνει τιμές από μηδέν (0) έως το σύνολο των μελών της συστοιχίας "sourcedir[xx]" ελαττωμένη κατά ένα (1):
for ((count="0"; count <${#sourcedir[*]}; count++))

Μέσα στο loop λοιπόν αρχίζουμε τα "αν" για να βγάλουμε κάτι χρήσιμο.
Παραθέτω τον ακόλουθο εικονικό κώδικο για καλύτερη κατανόηση:
|Για κάθε count με τιμή από 0 έως και το πλήθος του array "sourcedir[xx]" ελαττωμένο κατά ένα
|κάνε τα κατωτέρω:
|1->    |Έλεγξε το sourcedir[$count]. ΑΝ υπάρχει  ΚΑΙ  δεν είναι άδειο
|    |ΤΟΤΕ:
|    |1->    Εμφάνισε το μήνυμα "Το sourcedir[$count] είναι ok"
|    |2->    |Έλεγξε το destdir[$count]. ΑΝ υπάρχει ΚΑΙ δεν είναι άδειο
|    |    |TOTE:
|    |    |1->    Εμφάνισε το μήνυμα "Το destdir[$count] είναι ok"
|    |    |ΑΛΛΙΩΣ:
|    |    |1->    Εμφάνισε το μήνυμα ότι το destdir έχει πρόβλημα, αλλά η διεργασία θα γίνει κανονικά
|    |    |2->    Θέσε στη μεταβλητή ask την τιμή "1"
|    |    
|    |ΑΛΛΙΩΣ:
|    |1->    Εμφάνισε το μήνυμα ότι το sourcedir έχει πρόβλημα και η διεργασία δεν θα γίνει
|    |2->    Θέσε στη μεταβλητή ask την τιμή "1"

και τον κανονικό κώδικα για σύγκριση:
 
for ((count="0"; count <${#sourcedir[*]}; count++))    # Check source & destination directories
    do
        sleep 0.2    # a small sleep just cause I don't like things happening too fast
        echo "--------------------------------------------------------------------------------------------------------------"
 
        # If the source directory exists and it is not empty (ok)
        if [ -d ${sourcedir[$count]} ] && [ "$(ls -A ${sourcedir[$count]})" ] 
        then
            echo "[ ${M}ok${N} ]        ->    Source directory ${M}${sourcedir[$count]}${N}"
            false
 
            # If the destination directory exists and it is not empty (ok)
            if [ -d ${destdir[$count]} ] && [ "$(ls -A ${destdir[$count]})" ] 
            then
                echo "[ ${M}ok${N} ]        ->    Destination directory ${M}${destdir[$count]}${N}"
                false
 
            # If the destination directory does not exist or it is empty
            else
                echo "[ ${W}CRITICAL${N} ]    ->    Destination directory ${W}${destdir[$count]}${N} "
                echo "            does NOT exist or it is empty (Check if you mount something there !!)"
                echo
                echo "I will ${W}NOT skip${N} this operation."
                echo "The Destination Directory will be created if it doesn't exist and filled with new backup data"
                echo "Beware if it is not the first time your run the script for the specific backup operation"
                ask=1
            fi
 
        # If the source directory does not exist or it is empty
        else
            echo "[ ${E}WARNING${N} ]    ->    Source directory ${E}${sourcedir[$count]}${N}"
            echo "            does NOT exist or it is empty (Check if you mount something there !!)"
            echo
            echo "I will ${E}skip${N} this operation to protect your backup data (if any)"
            ask=1
        fi
    done

Γ. ...και τλειώνουμε με την εμφάνιση ενός μενού επιλογών του στυλ "συνεχίζουμε ή όχι ??"

Αν ισχύει λοιπόν η συνθήκη [ $ask == 1] (το οποίο σημαίνει ότι κάτι πήγε στραβά στους ελέγχους υποκαταλόγων και η ask πήρε την τιμή "1")
τότε με τον γνωστό τρόπο (select και case) δημιουργούμε ένα μενού που θα μας ρωτάει αν θέλουμε να συνεχίσουμε ή όχι την εκτέλεση του script.
Αν δεν ισχύει το [ $ask == 1] τότε απλά θα μας εμφανίζει ένα μήνυμα ότι όλοι οι έλεγχοι είναι ok και θα μας παροτρύνει να πατήσουμε ένα πλήκτρο για να γίνουν επιτέλους οι διεργασίες συγχρονισμού και backup:
if [ $ask == 1 ]    # if something went wrong in the previous directories checks
    then
        declare -a options
        options[${#options[*]}]="Just continue with the script, I know what I'm doing !!"
        options[${#options[*]}]="Quit now. I'll fix the problems and try again"
        echo
        echo 
        echo "${S}It is advised that you quit now and fix any problems"
        echo "To keep your data safe I will take all the actions mentioned in the above Warning messages"
 
        echo 
        echo "${Q}What would you like me to do??${N}"
        select opt in "${options[@]}";
        do
              case ${opt} in
                  ${options[0]})
                    echo "${E}ok, continuing with the script...${N}";
                    sleep 1; ;;
                *)
                    echo "${E}exiting now...${N}";
                    exit 0; ;;
              esac;
            break
        done
 
    else    # if all went ok in the previous directories checks
        echo
        echo "${S}All directories valid :-)"
        echo "You are ready to go${N}"
        echo
        echo "Hit enter to continue..."
        read
    fi

4. sync_stuff () function

Τέρμα με τα μπλαμπλα και τις ερωτήσεις. Ήρθε επιτέλους η ώρα να κάνουμε αυτό για το οποίο ξεκινήσαμε το γράψιμο αυτού του scipt.
...και ξεκινάμε με τον συγχρονισμό υποκαταλόγων.

Τα Βάζουμε λοιπόν όλα σ' ένα while loop με παρόμοιο τρόπο με τα προαναφερθέντα for loops. Δηλαδή εκτελούμε κάτι τόσες φορές όσες και το σύνολο των μελών ενός array. Επίσης χρησιμοποιούμε τη μεταβλητή count η οποία θα παίρνει τιμές από 0 έως ... τα γνωστά. Τώρα θα μου πείτε "και γιατί δεν χρησιμοποιούμε το for, που το μάθαμε κι όλας;;". Έτσι για ποικιλία :p :
count=0
while [ $count -le $((syncno-1)) ]

Μέσα στο loop (μετά από λίγο ακόμα μπλα μπλα), βάζουμε τη συνθήκη "ΑΝ τα δυο μέλη των array syncdirA και syncdirB είναι υποκατάλογοι, και υπάρχουν" και αν ναι, τότε εκτελούμε διαδοχικά δυο όμοιες εντολές "rsync". Αν όχι, παραλείπουμε αυτή τη διεργασία μ' ένα μήνυμα:
syncno=${#syncdirA[*]}        #No. of dirs to be synced
count=0                #typical count variable
while [ $count -le $((syncno-1)) ]
do
    echo "now syncing directory $((count+1)) of $syncno:"
    echo "${C} ${syncdirA[$count]} ${N}"
    echo "with"
    echo "${C} ${syncdirB[$count]} ${N}"
    echo
    if [ -d ${syncdirA[$count]} ] && [ -d ${syncdirB[$count]} ]    #check if both sync directories exist
    then
        #echo "Executing rsync NOW !!"
        rsync -h --archive --progress --stats --update ${syncdirB[$count]} ${syncdirA[$count]}
        rsync -h --archive --progress --stats --update ${syncdirA[$count]} ${syncdirB[$count]}
        echo "...done"
        echo
    else
        echo
        echo "${W}One or both directories are missing...${N}" 
        echo "...skipping"
    fi
    count=$((count+1))
    echo "--------------------------------------------------------------------------------------------------------------"
    echo
done

Αν και δεν είναι σκοπός του tutorial τούτου να εξηγούμε τις εντολές bash, αλλά πως τις χρησιμοποιούμε για να γράψουμε script, θα δώσω λίγη έκταση στη λειτουργία και τις επιλογές της "rsync".
Η εντολή "rsync" λοιπόν είναι ένα πολύ ισχυρό εργαλείο για ...ν' αντιγράφουμε αρχεία !!
Η δύναμη της βρίσκεται στα options που μπορούμε να χρησιμοποιήσουμε

η rsync λοιπόν ακολουθεί την παρακάτω σύνταξη:
rsync [options] [source] [destination]

Για περισσότερες πληροφορίες δείτε τη σελίδα man

man rsync

ή σε konqueror που βολεύει:
man:rsync

Όπου source είναι ο υποκατάλογος που θέλουμε ν' αντιγράψουμε.
Destination είναι ο υποκατάλογος στον οποίο θέλουμε ν' αντιγραφεί ο source.
Options είναι διάφορες επιλογές που τελικά θα κάνουν τη δουλειά έτσι ακριβώς όπως τη θέλουμε. Συγκεκριμένα (στη συνάρτηση sync_stuff) θα χρησιμοποιήσουμε τα:

-h --archive --progress --stats --update

Τα οποία σημαίνουν :
--archive: Μπες και στους υποκαταλόγους του source directory, αντέγραψε τα symlinks ως symlinks, διατήρησε τις ιδιότητες, τους χρόνους τελευταίας επεξεργασίας, τον κάτοχο και το γκρουπ όλων των αρχείων
-- update: Αν ένα αρχείο στον destination υποκατάλογο είναι πιο καινούργιο από το αντίστοιχο στον source υποκατάλογο, τότε μην εκτελέσεις τη μεταφορά
--progress & --stats: Αυτά μας δείχνουν πολλές πληροφορίες κατά τη μεταφορά ώστε να έχουμε κάτι να κοιτάμε και να μη βαριόμαστε !!

Στη συνάρτηση μας τώρα, βλέπουμε ότι εκτελούμε δυο διαδοχικά rsync με τις ίδιες παραμέτρους με τη διαφορά ότι αλλάζουμε τη θέση στα syncdirA και syncdirB. Έτσι, πρώτα θα αντιγραφούν τα πιο πρόσφατα αρχεία από το syncdirA στο syncdirB και μετά το αντίθετο.

5. backup_stuff () function

Δεν θα πούμε πολλά εδώ. Ακολουθείται παρόμοια τεχνική με την sync_stuff, δηλαδή χρήση ενός while loop και μιας συνθήκης για να επιβεβαιώσουμε ότι υπάρχουν τα source directories και δεν είναι άδεια. Το ξέρω, δεν είναι και πολύ καλό να ειπωθεί κάτι τέτοιο σ' ένα tutorial, αλλά "σας δίνω τον κώδικα ξερό για να βγάλετε άκρη μόνοι σας" :

count=0
    while [ $count -le $((dirno-1)) ]
    do
        echo "backing up directory $((count+1)) of $dirno:"
        echo "${C} ${sourcedir[$count]} ${N}"
        echo "to"
        echo "${C} ${destdir[$count]} ${N}"
        echo 
        if [ -d ${sourcedir[$count]} ] && [ "$(ls -A ${sourcedir[$count]})" ]    #check if sourcedir exists or empty
        then
            #echo "Executing rsync NOW !!"
            rsync -h --archive --progress --stats --update --delete-after  ${sourcedir[$count]} ${destdir[$count]}
            echo "...done"
            echo
        else
            echo
            echo "${W}${sourcedir[$count]}${N} is missing" 
            echo "...skipping"
        fi
        count=$((count+1))
        echo "--------------------------------------------------------------------------------------------------------------"
        echo
    done

Βασική διαφορά στις παραμέτρους της "rsync" σε σχέση με την sync_stuff είναι το "--delete-after". Αυτό σημαίνει "σβήσε όλα τα αρχεία που δεν υπάρχουν πια στον υποκατάλογο source, από τον υποκατάλογο destination, αφού τελειώσει η απαραίτητη μεταφορά"


tip: Μπορούμε να πειραματιστούμε κατά βούληση με το script έχοντας την ασφάλεια ότι δεν θα εκτελείται ποτέ καμία εντολή rsync. Απλά κάντε τις κατωτέρω μετατροπές.
Στη συνάρτηση "sync_stuff" αλλάξτε τις γραμμές από:
#echo "Executing rsync NOW !!"
rsync -h --archive --progress --stats --update ${syncdirB[$count]} ${syncdirA[$count]}
rsync -h --archive --progress --stats --update ${syncdirA[$count]} ${syncdirB[$count]}
σε
echo "Executing rsync NOW !!"
rsync -h --archive --progress --stats --update ${syncdirB[$count]} ${syncdirA[$count]}
rsync -h --archive --progress --stats --update ${syncdirA[$count]} ${syncdirB[$count]}

και στη συνάρτηση "backup_stuff" αλλάξτε τις γραμμές από:
#echo "Executing rsync NOW !!"
rsync -h --archive --progress --stats --update ${syncdirB[$count]} ${syncdirA[$count]}
rsync -h --archive --progress --stats --update ${syncdirA[$count]} ${syncdirB[$count]}
σε
echo "Executing rsync NOW !!"
#rsync -h --archive --progress --stats --update ${syncdirB[$count]} ${syncdirA[$count]}
#rsync -h --archive --progress --stats --update ${syncdirA[$count]} ${syncdirB[$count]}


6. thats_all () function

Πληροφοριακά :
Για να λέμε και του στραβού το δίκιο, αν οι διεργασίες (1), (2), (3) και (6) δεν υπήρχαν καθόλου, δεν τρέχει και τίποτα.
Θα μπορούσαμε να έχουμε μόνο τις διεργασίες (4) και (5) και να γλιτώσουμε πόσο κώδικα.
Απλά προτιμώ ένα πρόγραμμα να δίνει συνεχώς πληροφορίες και δυνατότητες επιλογής στο χρήστη.
Είναι αφενός πιο όμορφο και αφετέρου δίνει το συναίσθημα του ελέγχου και της ασφάλειας.
Φανταστείτε κάτι να έτρεχε απλά και να περιμέναμε πόση ώρα χωρίς να έχουμε ιδέα τι γίνεται !!
Αρχίζουν οι φιλοσοφικές αναζητήσεις του στυλ "Τι κάνει τώρα, κόλησε ??", "μήπως μου σβήνει τη συλλογή μου που κατέβαζα δέκα χρόνια τώρα !!!", "ελπίζω να έγραψα σωστά εκείνα τα destdir-κάτι που μου είπανε γιατί αλλιώς ..." κλπ κλπ

Πάντως αν θελήσουμε να βάλουμε το sciptάκι μας μέσα σε cron ώστε να εκτελείται αυτόματα κατά συγκεκριμένες περιόδους, αυτό είναι έτσι δομημένο ώστε να μπορούμε να αφαιρέσουμε εύκολα τις πολλές ερωτήσεις και το μπλαμπλα.


Επίλογος
Ελπίζω με αυτή τη σειρά από tutorials να βοηθηθούν όσοι θέλουν να ξεκινήσουν να γράφουν κώδικα σε bash.
Σκοπός ήταν να μπει κάποιος αμέσως στο παιχνίδι χωρίς να χρειαστεί να διαβάσει βιβλία μερικών εκατοντάδν σελίδων.

Φυσικά και μπορείτε να ρωτήσετε οτιδήποτε θέλετε (μη φανταστείτε ότι είμαι και ο guru του bash, αλλά θα βγάλουμε άκρη) ή να κάνετε τις παρατηρήσεις σας για να βελτιώσουμε όσο γίνεται αυτό το tutorial.

Σας εύχομαι καλό scripting
luckyb
:-)





Πίνακας περιεχομένων
Προηγούμενο (Συναρτήσεις)