Passionate Development From Journeyman to Master

Bash Scripting

One of the tasks that I have to do at work is to perform patches. This is usually means copying a handful of files to the servers. This is not something that we do often and so the usual practice is to sftp the files manually.

But as you can imagine - manually copying files is error prone process - the risk increase with the increase number of files (and servers to copy files to). Ultimately, we want to have a “one click” patch process and of course with a “rollback” feature.

I am sure there are plenty of better ways of doing this - but few days ago I was toying with the idea of scripting the copying of files. I tried Perl which probably would do a decent job with its File library - this library is not installed in our production servers (and we strictly cannot install software without permission) - so Perl is out.

I then looked at Bash and quickly skimmed through few examples on the net with regards to variables, looping and copying files. I’ve got the impression that it is doable.

And so for the next 6 hours - I was slowly building the script. It was actually a quite enjoyable problem solving experience. Luckily I did learn a little bit of Shell Scripting at uni - so I wasn’t completely at lost with this scripting business.

There were gotchas that I found a long the way.

Firstly, cp in Linux (my testing machine) and Solaris (the production server) is different - it’s the POSIX vs GNU thing. The –parent option in Linux / GNU is really handy - it preserves the source directory structure, which solves my problem with same filenames in different directories. However the feature does not exists in POSIX version of cp - so I would have to handle filename duplicates myself.

Another thing that I found is, whitespace has a meaning so the code below won’t execute:

[bash] #!/bin/bash foo = "bar" [/bash]

While this one is ok:

[bash] #!/bin/bash foo="bar" [/bash]

The script that I wrote, does the following:

  • Backing up the destination files
  • In the backup process, because it is being written in the flat directory structure - we need to be cautious with files with the same name - the script takes care of this, by appending a number to the end of the file has the same file name as an existing file on the back up directory.
  • Move files from the source directory to their respective destination

It does not do a roll back, a roll back script should just do the reverse of the above - moving the files back from back up directory to their place, the duplicate workaround will make this tricky.

And here it is the simplified script - it’s probably ugly, but I don’t claim to be a scripting guru.. so:

[bash] #!/usr/bin/bash

SOURCE=(
‘/home/felixt/source/foo.bar’
)

DEST=(
‘/home/felixt/destination/foo.bar’
)

arrayLength=${#SOURCE[@]} arrayLength=$(( $arrayLength - 1)) duplicate_counter=1

backupDir=’backup/’ echo ‘Making backup directory ‘$backupDir mkdir $backupDir

for a in $(eval echo {0..$arrayLength}) do thisfile=${DEST[${a}]} filename=${thisfile##*/}

echo "Backing up original file $filename" 

if [ -e "$backupDir$filename" ] 
then
	# Handling file duplicate 
	# simply append an incrementing number at the end of the file
    echo $filename" exists" 
    filename="${filename}${duplicate_counter}" 
    cp ${DEST[${a}]} $backupDir$filename 
    echo "renamed to "$filename 
    duplicate_counter=$(( $duplicate_counter + 1 )) 
else 
    cp ${DEST[${a}]} $backupDir 
fi 
 
echo "Moving patched file ${SOURCE[${a}]}" 
cp ${SOURCE[${a}]} ${DEST[${a}]} 
 
echo "" 

done [/bash]

generalwork