Skip to content

Instantly share code, notes, and snippets.

@zach-klippenstein
Created February 1, 2010 01:51
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save zach-klippenstein/291387 to your computer and use it in GitHub Desktop.
Save zach-klippenstein/291387 to your computer and use it in GitHub Desktop.
This script takes a local directory and a set of options specifying how/where to connect to a remote Samba server, and a newline-delimited list of paths on standard input. The paths should be relative to the local directory and the remote directory. They
#!/bin/bash
HELP="
Created on: 20/01/2010
By: Zachary Klippenstein
This script takes a local directory and a set of options
specifying how/where to connect to a remote Samba server,
and a newline-delimited list of paths on standard input.
The paths should be relative to the local directory and
the remote directory. They will be downloaded from the remote
server into the local directory, with all components of the
path converted to lowercase.
If files are specified on stdin, each file is backed up
before attempting to synchronize, and if synchronization fails,
the original file is restored. This does NOT happen if --all is
specified. See the note in the option description below for more
information.
usage: smb-sync.sh
smb-sync.sh localdir smburl [--all [--overwrite]] [--ignore-file-case]
[--quiet] [--chmod localpermissions] [--chown localownership] [--verbose]
smburl Remote location to download from. Format:
smb://[[[domain;]user[:password@]]server[/share[/path[/file]]]]
localpermissions Octal permissions to set downloaded files to after
download
localownership 'user', ':group', or 'user:group' to chown files to
--all If specified, the remote directory will be recursively downloaded into localdir.
NOTE: This options has the following effects:
- individual files are not backed up. If sync fails, restore manually
- --chmod and --chown options are ignored
--overwrite Overwrite pre-existing files. If this is not specified, such files will just be left alone.
--ignore-file-case Doesn't bother to convert files specified on stdin to lowercase.
NOTE: Running the script with no arguments prints this message.
"
# Session variables, defaults should be set here
script_name="$0"
local_dir=
smb_url=
local_perms=
local_ownership=
verbose=""
quiet=""
all_recursive=""
overwrite=""
ignore_file_case=""
# Options passed to smbget
SMBGET_OPTIONS="--nonprompt"
# Process-specific backup suffix
BACKUP_SUFFIX=".$$.presync"
function print_usage()
{
echo "$HELP"
exit 1
}
# Strips a trailing slash off it's first argument
function deslash()
{
var="$1"
str="${!var}"
# Check for trailing slash
if (echo "$str" | egrep '/$' &>/dev/null); then
# Remove the last character
str=${str:0:${#str}-1}
fi
# Set the original variable back
eval "${var}='$str'"
}
# Cleans an input line and returns true iff the line
# should be processed. Returns false if line is blank.
function clean()
{
var="$1"
str="${!var}"
processLine=0
# Remove leading/trailing whitespace
str=`echo "$str" | awk '{gsub(/[[:space:]]*/,"",$0); print $0}'`
# Remove trailing slash
deslash str
# Check for blank/empty line
[ -z "$str" ] && processLine=1
# Set original variable to the cleaned version
eval "${var}='$str'"
return $processLine
}
function lowercase()
{
var="$1"
str=`echo ${!var} | awk '{print tolower(\$0)}'`
eval "${var}='$str'"
}
# Reads a list of output from smbget, detects errors due to
# pre-existing files, removes them, and prints the file names to
# stdout.
function removeExistingFiles()
{
#egrep "^Can't open .\+: File exists$" 2>/dev/null |
while read existingFileLine; do
if (echo "$existingFileLine" | egrep "^Can't open .+: File exists\$" &>/dev/null); then
# Parse the actual file name from the error message
existingFile=${existingFileLine:11:${#existingFileLine}-(11+13)}
rm -f "$existingFile"
[ $verbose ] && echo "Overwriting: $existingFile" 1>&2
echo "$existingFile"
fi
done
}
# If --all was specified, deals with output from smbget
# concerning existing files.
# If --overwrite was specified, overwrites them, else does nothing
function handleExistingFiles()
{
if [ $overwrite ]; then
# If any of the files already exist, remove them and download them separately
removeExistingFiles |
"$script_name" "$local_dir" "$smb_url" --ignore-file-case --quiet
fi
}
# Parse arguments
local_dir="$1"
smb_url="$2"
shift; shift
# Validate mandatory arguments
if [ -z "$local_dir" -o -z "$smb_url" ]; then
print_usage
fi
if [ ! -d "$local_dir" ]; then
echo "Local directory does not exist: $local_dir"
exit 1
fi
# Check for optional arguments
while [ "$1" ]; do
case "$1" in
--all)
all_recursive="true"
;;
--overwrite)
overwrite="true"
;;
--ignore-file-case)
ignore_file_case="true"
;;
--chmod)
local_perms="$2"
[ -z "$local_perms" ] && print_usage
shift
;;
--chown)
local_ownership="$2"
[ -z "$local_ownership" ] && print_usage
shift
;;
--verbose)
verbose="true"
;;
--quiet)
quiet="true"
;;
esac
shift
done
# Normalize formatting of local/remote paths
deslash local_dir
deslash smb_url
# Blindly download all files in remote directory
if [ $all_recursive ]; then
# Save the current directory
origDir="`pwd`"
# -o options to smbget isn't compatible with -R: i.e. all files will be
# downloaded into current working directory, so we have to enter localdir
# manually before downloading
cd "$local_dir"
# Recursively download all files in the remote dir.
# NOTE: smbget will attempt to change permissions of the current
# directory after downloading, and this will (should) fail.
smbget $SMBGET_OPTIONS -R "${smb_url}" 2>&1 |
# Determine what to do with existing files based on output of smbget
# and command-line arguments
(cd "$origDir" && handleExistingFiles)
# Files will be specified on stdin.
else
SMBGET_OPTIONS="$SMBGET_OPTIONS --keep-permissions --quiet"
# Main loop: read line, download, modify if necessary, repeat
while read line; do
success=0
# Only process line if cleaning results in valid line
if clean line; then
[ $quiet ] || echo "Synchronizing: $line"
[ $ignore_file_case ] || lowercase line
local_file="${local_dir}/${line}"
backup_file="${local_file}${BACKUP_SUFFIX}"
# Backup original if it exists
if [ -f "$local_file" ]; then
mv "$local_file" "$backup_file"
success=$?
if [ $success -ne 0 -o ! -f "$backup_file" ]; then
echo "Couldn't create backup file, skipping" >&2
success=1
fi
fi
# Download the file
if [ $success -eq 0 ]; then
smbget $SMBGET_OPTIONS -o "${local_file}" "${smb_url}/${line}"
# smbget's return value is backwards, so we have to NOT it first
[ $? -eq 1 ]
success=$?
if [ ! -f "$local_file" ]; then
echo "Download failed." >&2
success=1
fi
fi
if [ $success -eq 0 -a "$local_perms" ]; then
chmod "$local_perms" "$local_file"
success=$?
fi
if [ $success -eq 0 -a "$local_ownership" ]; then
chown "$local_ownership" "$local_file"
success=$?
fi
# If any part of the process failed, remove local copy
if [ $success -ne 0 ]; then
if [ -f "$backup_file" ]; then
echo "Synchronization failed, restoring pre-sync backup..." >&2
mv "$backup_file" "$local_file" || echo "Error restoring backup, file is: $backup_file" >&2
fi
[ $quiet ] || echo -e "\tfailure."
else
if [ -f "$backup_file" ]; then
rm -f "$backup_file"
fi
[ $quiet ] || echo -e "\tsuccess."
fi
fi
done
fi
exit 0
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment