Skip to content

Instantly share code, notes, and snippets.

@gdiaz384
Created September 2, 2024 04:55
Show Gist options
  • Save gdiaz384/ea57236d4af4e7d89b6b78a9ef67caad to your computer and use it in GitHub Desktop.
Save gdiaz384/ea57236d4af4e7d89b6b78a9ef67caad to your computer and use it in GitHub Desktop.
Bash reimplementation of robocopy ABI using rclone.
#!/usr/bin/env /bin/bash
# robocopy.sh is a Linux Bash script that mirrors directory trees using rsync using a somewhat similar ABI to the idea source. However, excluding directories is not supported.
# Installation:
# 1) Install rysnc: sudo apt-get install -y rsync
# 1) Download robocopy.sh
# 2) Place robocopy.sh somewhere in $PATH
# 3) Mark the script as executable: chmod +x robocopy.sh
# 4) (Optional) Rename robocopy.sh to robocopy if desired.
# 5) ./robocopy --help
# Documentation:
# https://devhints.io/bash
# https://unix.stackexchange.com/questions/203846/how-to-sync-two-folders-with-command-line-tools
# http://www.linuxfocus.org/~guido/scripts/recursive-file-copy-linux.html
# https://stackoverflow.com/questions/1728683/case-insensitive-comparison-of-strings-in-shell-script
# Syntax:
# Mirror the content of /source/mydirectory/ to /destination/directory/mydirectory
# cd /source
# rclone -v -c mydirectory /destination/directory
# Dry-run this by adding -n
# rclone -v -c -n mydirectory /destination/directory
# Documentation:
# -v means verbose
# -c means --checksum which means skip based on the checksum of the data, not modification-time and size. This will fully read both the source and destination files in order to compare them.
# -n means --dry-run which means perform a trial run with no changes made
# --transfers 1 limits the number of parallel transfers. For local transfers, 1 is ideal. The default value is 4.
# Alternative syntaxes using rsync and cp for copying copying from source to destination:
# rsync -v -u -a --del mydirectory/ /destination/directory # The trailing / is required for the source.
# cp -r -u -v "$sourceDirectory" "$targetDirectory"
# Case insensitive compare (simple case that requires multiple lines):
# var1=match
# var2=MATCH
# if echo $var1 | grep -i "^${var2}$" > /dev/null ; then
# echo "MATCH"
# fi
#set -euo pipefail # The -u option is not natively compatible with scripts that accept input. See: http://redsymbol.net/articles/unofficial-bash-strict-mode/ "Positional Parameters"
set -eo pipefail
IFS=$'\n\t'
#scriptPath=$(dirname "$(readlink -f "$BASH_SOURCE")")
scriptName=$(basename "$0")
usage() {
echo $scriptName mirrors directory trees using rclone.
echo
echo Description:
echo - Mirroring means all files in the target directory will be replaced by the source directory. Mirroring is different than copying the source directory into the target directory as a subfolder.
echo - If the targetDirectory does not exist, it will be created, including during test runs.
echo - During test runs, if the targetDirectory does not exist and the test run is successful, then the targetDirectory and only the targetDirectory is removed after printing the simulated actions. The parents of targetDirectory will remain.
echo Example: \"$scriptName test \~/a/b/c/d\" means that \~/a, \~/a/b, \~/a/b/c, and \~/a/b/c/d will be created but only \~/a/b/c/d will be removed after the test run completes.
echo - The printed paths show the paths after resolving them.
echo - Path resolution when using the relative to home symbol \~ can be flaky/wrong, so always triple check the printed paths when using a tilde \~.
echo
echo Dependencies: bash, rclone https://rclone.org/install/
echo sudo apt-get install -y rclone
echo rclone --version
echo
echo Syntax:
echo $scriptName sourceDirectory targetDirectory [/mir] [/d]
echo - [ ] means optional.
echo - Omitting /mir means to do a test run for debugging.
echo - /d means to delete files in the destination that are not in the source.
echo - Omitting /d means keeping files at the destination even if they do not exist at the source.
echo - \"\" means to use the default option.
echo
echo Examples:
echo $scriptName test test2
echo $scriptName test test2 /mir
echo $scriptName test test2 /mir /d
echo $scriptName test test2 \"\" /d
echo $scriptName /home/user/test test2
echo $scriptName /home/user/test Downloads/test2
echo $scriptName /home/user/test /Downloads/test2
echo $scriptName /home/user/test \~/Downloads/test2
echo $scriptName /home/user/test \~/Downloads/test2 \"\" /d
echo $scriptName /home/user/test \~/Downloads/test2 /mir
echo $scriptName /home/user/test \~/Downloads/test2 /mir /d
echo
}
if [[ "$1" == "" || "$2" == "" || "$1" == "-h" || "$1" == "-H" || "$1" == "--help" || "$1" == "--HELP" || "$1" == "/?" || "$1" == "?" || "$2" == "-h" || "$2" == "-H" || "$2" == "--help" || "$2" == "--HELP" || "$2" == "/?" || "$2" == "?" || "$3" == "-h" || "$3" == "-H" || "$3" == "--help" || "$3" == "--HELP" || "$3" == "/?" || "$3" == "?" ]]; then
usage
exit
fi
if [[ ! -d "$1" ]]; then
echo Error: "$1" folder does not exist.
exit
fi
targetIsTemporary=False
if [[ ! -d "$2" ]]; then
if [[ -f "$2" ]]; then
echo Error: "$2" already exists as a file.
exit
fi
echo Info: "$2" does not exist.
mkdir -p "$2"
# Permissions errors sometimes prevent this from working.
if [[ ! -d "$2" ]]; then
echo Error: "$2" does not exist
exit
fi
targetIsTemporary=True
fi
sourceDirectory="$(builtin cd "$1"; pwd)"
targetDirectory="$(builtin cd "$2"; pwd)"
homeDirectory=~
#targetDirectory="$(dirname "$targetDirectory")"
echo Copying from: "$sourceDirectory"
echo to: "$targetDirectory"
#echo Copying "$1" to "$2"
#echo $sourceDirectory
#echo $targetDirectory
# Final check.
if [[ ! -d "$sourceDirectory" ]]; then
echo Error: "$sourceDirectory" does not exist
exit
fi
if [[ ! -d "$targetDirectory" ]]; then
echo Error: "$targetDirectory" does not exist.
exit
fi
# Copying over the home directory or root is probably a mistake, so do not allow it.
if [[ "$targetDirectory" == "$homeDirectory" || "$targetDirectory" == "/" ]]; then
echo Error: Unable to set target directory.
exit
fi
# Core logic.
if [[ "$3" != "/mir" ]]; then
echo Info: The following changes will occur inside of \"$targetDirectory\"
if [[ "$4" == "/d" || "$4" == "/D" || "$4" == "/del" ]]; then
rclone -v -c -n --transfers 1 sync "$sourceDirectory/" "$targetDirectory"
else
rclone -v -c -n --transfers 1 copy "$sourceDirectory/" "$targetDirectory"
fi
if [[ "$targetIsTemporary" == "True" ]]; then
rmdir "$2"
fi
elif [[ "$3" == "/mir" ]]; then
if [[ "$4" == "/d" || "$4" == "/D" || "$4" == "/del" ]]; then
rclone -v -c --transfers 1 sync "$sourceDirectory/" "$targetDirectory"
else
rclone -v -c --transfers 1 copy "$sourceDirectory/" "$targetDirectory"
fi
fi
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment