#!/usr/bin/env bash SCRIPTNAME="$(basename "$0")" VERSION=3.5.0 FILEDATE="2026-07-26" echo -e "\033[0m===============================================" echo -e " $SCRIPTNAME - rsync progress monitor" echo -e " Written by Charlie Silverman" echo -e " Version: $VERSION" echo -e " Release Date: $FILEDATE" echo -e "===============================================" SRCFOLDER="" DSTFOLDER="" SLEEP=60 HELP=false while [[ $# -gt 0 ]]; do echo $1 IFS="=" read -r command value <<< "$1" echo "Command: $command" echo "Value: $value" echo "Value length: ${#value}" case "$command" in --source) if [ "${#value})" -gt 0 ]; then SRCFOLDER="$value" shift else SRCFOLDER="$2" shift 2 fi ;; --destination) if [ "${#value})" -gt 0 ]; then DSTFOLDER="$value" shift else DSTFOLDER="$2" shift 2 fi ;; --sleep) if [ "${#value})" -gt 0 ]; then SLEEP="$value" shift else SLEEP="$2" shift 2 fi ;; --help) HELP=true shift ;; *) echo "ERROR: Unknown argument: $1" HELP=true break ;; esac done ERROR=0 if [[ "$HELP" != "true" ]]; then # Check if source folder exists if [[ ! -d "${SRCFOLDER}" ]]; then echo -e "\n\033[0;31mE\e[4mRROR: Source Folder $SRCFOLDER does not exist.\e[0m" ERROR=1 fi # Check if destination folder exists if [[ ! -d "${DSTFOLDER}" ]]; then echo -e "\n\033[0;31mE\e[4mRROR: Destination Folder $DSTFOLDER does not exist.\e[0m" ERROR=1 fi # Verify that a sleep value was supplied if [ -z "${SLEEP}" ]; then echo -e "\n\033[0;31mE\e[4mRROR: Sleep delay not specified.\e[0m" ERROR=1 else # Verify that the supplied sleep value is actually a Number re='^[0-9]+$' if ! [[ $SLEEP =~ $re ]]; then echo -e "\n\033[0;31m\e[4mERROR: Sleep delay invalid.\e[0m" ERROR=1 fi fi fi # Help display if $HELP; then cat << EOF SYNOPSIS ./$SCRIPTNAME --source --destination --sleep [OPTIONS]" OPTIONS --source Full or relative path to the source rsync folder to monitor --destination Full or relative path to the destination rsync folder to monitor --sleep The number of seconds to sleep between scanning passes. Default: 60 --help Display this help message and exit. EXAMPLE ./$SCRIPTNAME --source /home/olduser --destination /home/newuser --sleep 300" EOF fi echo "Calculating size of $SRCFOLDER..." # Getting the mount point that relates to the source path, stripping off # the trailing slash if found if [[ "${SRCFOLDER: -1}" = "/" ]]; then SRCMOUNT=$(df ${SRCFOLDER%?} | sed -n '2p' | awk '{print $6}') else SRCMOUNT=$(df $SRCFOLDER | sed -n '2p' | awk '{print $6}') fi # Determining the size of the source data. If the source path # matches a mount point from df, assuming that mount point data # to be accurate. if [ $SRCMOUNT != $SRCFOLDER ]; then MEGS=$(du -sm $SRCFOLDER |awk '{print $1}') else MEGS=$(df $SRCFOLDER | sed -n '2p' | awk '{print $3}') MEGS=$(($MEGS / 1024)) fi MEGSDISPLAY="${MEGS}MB" MEGSGB=$(echo "scale=2;$MEGS/1024" | bc) if [ $(echo "$MEGSGB > 0" | bc) -ne 0 ]; then NUMFMT=$(printf "%'.3f\n" ${MEGSGB}) MEGSDISPLAY="${NUMFMT}GB" fi echo "Monitoring $DSTFOLDER..." # Getting the mount point that relates to the destination path, stripping off # the trailing slash if found if [[ "${$DSTFOLDER: -1}" = "/" ]]; then DSTMOUNT=$(df ${DSTFOLDER%?} | sed -n '2p' | awk '{print $6}') else DSTMOUNT=$(df $DSTFOLDER | sed -n '2p' | awk '{print $6}') fi SECONDSLEFT=1 STARTTIME=$(date +%s) # Determining the current size of the destination data. If the # destination path matches a mount point from df, assuming that # mount point data to be accurate. if [ $DSTMOUNT != $DSTFOLDER ]; then FIRST=$(du -sm $DSTFOLDER |awk '{print $1}') else FIRST=$(df $DSTFOLDER | sed -n '2p' | awk '{print $3}') FIRST=$(($FIRST / 1024)) fi FIRSTDISPLAY="${FIRST}MB" FIRSTGB=$(echo "scale=2;$FIRST/1024" | bc) if [ $(echo "$FIRSTGB > 0" | bc) -ne 0 ]; then NUMFMT=$(printf "%'.3f\n" ${FIRSTGB}) FIRSTDISPLAY="${NUMFMT}GB" fi CURRENTPCT=$(echo "scale=2;($FIRSTGB/$MEGSGB)*100" | bc | cut -d . -f 1) START=$FIRST CMDTIME=0 CURRENTTIME=$(date '+%Y-%m-%d %H:%M:%S') echo -e "[${CURRENTTIME}] \033[0;32m${FIRSTDISPLAY}\e[0m of \033[0;32m${MEGSDISPLAY}\e[0m (\033[0;32m${CURRENTPCT}%\e[0m)\tStarting\t\t| Sleeping ${SLEEP}s..." while (( $SECONDSLEFT > 0 )); do sleep $SLEEP # Determining the current size of the destination data. If the # destination path matches a mount point from df, assuming that # mount point data to be accurate. if [ $DSTMOUNT != $DSTFOLDER ]; then END=$(du -sm $DSTFOLDER |awk '{print $1}') else END=$(df $DSTFOLDER | sed -n '2p' | awk '{print $3}') END=$(($END / 1024)) fi TIMENOW=$(date +%s) TIMEDIFF=$(($TIMENOW-$STARTTIME)) DOWNLOADED=$(( $END-$START )) TOTALDOWNLOADED=$(( $END-$FIRST )) if [[ $DOWNLOADED -lt 0 ]]; then DOWNLOADED=0 fi ENDDISPLAY="${END}MB" ENDGB=$(echo "scale=2;$END/1024" | bc) if [[ $(echo "$END > 0" | bc) -ne 0 ]]; then NUMFMT=$(printf "%'.3f\n" ${ENDGB}) ENDDISPLAY="${NUMFMT}GB" fi CURRENTTIME=$(date '+%Y-%m0%d %H:%M:%S')/ CURRENTPCT=$(echo "scale=2;($ENDGB/$MEGSGB)*100" | bc | cut -d . -f 1) if [[ "$DOWNLOADED" == "0" ]]; then echo -e "[${CURRENTTIME}] \033[0;32m${ENDDISPLAY}\e[0m of \033[0;32m${MEGSDISPLAY}\e[0m (\033[0;32m${CURRENTPCT%}\e[0m)\t| No change since last check | Sleeping ${THISSLEEP}s..." continue fi OVERALLMEGSPERSECOND=$(( $TOTALDOWNLOADED/$TIMEDIFF )) OVERALLSECONDSLEFT=$(( ($MEGS-$END)/$TOTALDOWNLOADED*$TIMEDIFF )) OVERALLDAYS=$(( $OVERALLSECONDSLEFT/60/60/24 )) OVERALLHOURS=$(( ($OVERALLSECONDSLEFT-$OVERALLDAYS*60*60*24)/60/60 )) OVERALLMINUTES=$(( ($OVERALLSECONDSLEFT-$OVERALLDAYS*60*60*24-$OVERALLHOURS*60*60)/60 )) OVERALLSECONDS=$(( $OVERALLSECONDSLEFT-$OVERALLDAYS*60*60*24-$OVERALLHOURS*60*60-$OVERALLMINUTES*60 )) echo -e "[${CURRENTTIME}] \033[0;32m${ENDDISPLAY}\e[0m of \033[0;32m${MEGSDISPLAY}\e[0m (\033[0;32m${CURRENTPCT}%\e[0m)\t ~${OVERALLMEGSPERSECOND}MB/s\t| ${OVERALLDAYS}d ${OVERALLHOURS}h ${OVERALLMINUTES}m ${OVERALLSECONDS}s remaining\t| Sleeping ${SLEEP}s..." START=$END done