Bash Script to Interface with Rsync Command



I created this Bash script as a project for the system administration course I’m taking for the summer. I’m sure there are bugs in it, so let me know if you find any.

It basically uses an XML configuration file, which includes the source, destination, and any excludes from the transfer. You then pass either -u or -d (upload or download) and the options -x (delete if not in source) and -f (force). Destination can be local or remote, but source must be local.

Here is the code:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
#!/bin/bash
## Transfer Script
## By David Drager
## CSC586: Summer II 2008
## Requires: xml2, rsync

## Settings
tempdir="/tmp/"
rsynccommand="/usr/bin/rsync"

## Usage command
usage="usage: transfer.sh [options] <config-file>.xmlnoptions:n  -d or -u:t download or upload, resp. one and only one must be presentn  -f:ttforce transfer regardless of 'newness' of filen  -x:ttdelete items in target not present in source"


# Check to make sure temporary directory exists
[ -d $tempdir ] || { echo -e "Error: Could not locate a temporary directory. See file settings."; exit 1; }

# Usage Command
[ $# -eq 0 ] && { echo -e "ERROR: Needs at least the -d or -u flag plus a config file.nn$usage"; exit 1; }


## Get options passed to the program
while getopts "dufx" flag
do
  [ "$flag" = "?" ] && echo -e "ERROR: Flag(s) not valid.nn$usage" && exit 1;
  eval "opt_$flag=1"
done
shift $((OPTIND-1))
configfile="$1"

if [ "$opt_d" = 1 ] && [ "$opt_u" = 1 ]
then
  echo -e "Error: Use either -u Upload or -d Download, but not bothn$usage"; exit 1;
elif  [ "$opt_u" = "" ] && [ "$opt_d" = "" ]
then
    echo -e "Error: Use either -u Upload or -d Download, but not bothn$usage"; exit 1;
fi

if [ "$opt_f" = 1 ]; then
  force=1
fi

if [ "$opt_x" = 1 ]; then
  deltar=1
fi

## Make sure config file is located
if [ "$configfile" != "" ]
then
  [ -f "$configfile" ] || { echo -e "Error: Config file not found.n$usage"; exit 1; }
else
   echo -e "Error: Config file not specified.n$usage"; exit 1;
fi

## End of swich verification

# echo Action: "$action"
# echo Force: "$force"
# echo Delete Target Files: "$deltar"
# echo Config: "$configfile"

## Now read config file

## Set temp file for XML parsing
tempfile="$tempdir"transferscript_$$
tempexcludes="$tempdir"transferscriptexcludes_$$
# echo -e "Temp:$tempfile"

# Run config file through xml2
xml2 < "$configfile" > "$tempfile"

[ -f "$tempfile" ] || { echo -e "There was a problem processing the XML file."; exit 1; }

# Make sure that source and destination are found
excludelist=""
while read line; do
    key=$(echo $line | awk -F '=' '{print $1}')
    value=$(echo $line | awk -F '=' '{print $2}')
    if [ "$key" = "/sync/@src" ]; then
      source="$value";
    elif [ "$key" = "/sync/@dst" ]; then
      destination="$value"
      # Add any exclude options to array
    elif [ "$key" = "/sync/exclude" ]; then
      tempexcl=("$value")
      excludelist=("${excludelist[@]}" "${tempexcl[@]}")
    fi
done < "$tempfile"

[ "$source" ] || { echo "Error: Config file must include a source"; exit 1; }
[ "$destination" ] || { echo "Error: Config file must include a destination"; exit 1; }

source="$source/"
destination="$destination/"

[ -d "$source" ] || { echo "Error: Source must be a local directory"; exit 1; }

excludes=""
excludes=${excludelist[@]}

# echo Excludes: "(${#excludelist[@]}): $excludes"

## Clean up temp file from xml process
rm "$tempfile"



## Build up the command line

[ -f "$rsynccommand" ] || { echo -e "Error: Could not locate rsync command."; exit 1; }

fullcommand="$rsynccommand"
fulltestcommand="$rsynccommand"

if [ "$opt_f" == "1" ]
then
    fulltestcommand="$fulltestcommand -nva"
    fullcommand="$fullcommand -a"
else
    fulltestcommand="$fulltestcommand -nvau"
    fullcommand="$fullcommand -au"
fi

if [ "$opt_u" == "1" ]
then
  fileorder="$source $destination"
  echo "Evaluating transfer from $source ==> $destination"
elif [ "$opt_d" == "1" ]
then
  fileorder="$destination $source"
  echo "Evaluating transfer from $destination ==> $source"
fi

[ "$opt_x" == "1" ] && { fulltestcommand="$fulltestcommand --delete"; fullcommand="$fullcommand --delete"; }


## Move exclude list to a file.
## This duplicates as above but I originally thought we were passing it to rsync as a list and not as a file.

[ "${#excludelist[@]}" -gt 0 ] && {
    tempfile="$tempdir"transferscript_$$
    touch "$tempfile"
    echo "$excludes" | tr " " "n" > "$tempfile"
    fulltestcommand="$fulltestcommand"" --exclude-from=$tempfile"
    fullcommand="$fullcommand"" --exclude-from=$tempfile"
}

fulltestcommand="$fulltestcommand $fileorder"
fullcommand="$fullcommand $fileorder"

# We only want lines minus top line and bottom 3 lines
testresult=`$fulltestcommand | tail --lines=+2 | head --lines=-3`

# If this is empty, then notify that no files would be transferred.
 [ "$testresult" ] || { echo "This command would not transfer any files. Script exiting."; rm "$tempfile"; exit 1; }

# Display changes
echo -e "Changes to be made:n--------------------n$testresultn--------------------"

# Check to see if we really want to run the command.
echo "Do you wish to perform this command? $fullcommand (y/n)[y]"
read confirm
if [ "$confirm" = "y" ] || [ "$confirm" = "" ]
then
    echo "Performing transfer..."
    # Without -v this will not output anything.
    $fullcommand
    echo "Transfer complete."
    rm "$tempfile"
else
    echo "Cancelling transfer..."
    rm "$tempfile"
fi

exit 0

Here is the sample config file:


1
2
3
4
5
6
<sync src="/home/dir/bash-prog/dir1/" dst="login@some.machine:/home/dir/dir2">
        <exclude>
          .test3
          another.*
        </exclude>
</sync>