Skip to content

Instantly share code, notes, and snippets.

@dzogrim
Last active December 23, 2023 17:03
Show Gist options
  • Select an option

  • Save dzogrim/fd45954c8e0c8273a92de29282f40981 to your computer and use it in GitHub Desktop.

Select an option

Save dzogrim/fd45954c8e0c8273a92de29282f40981 to your computer and use it in GitHub Desktop.
Compare sqlite 'userData.db' stored in ZIP archive and display differences without writing out files on disk (macOS only)
#!/bin/bash
# Description:
# This script aims to compare two zipped SQLite database files (<file1.zip> and <file2.zip>),
# each containing a 'userData.db' file. It extracts these databases, converts them into SQL dumps,
# and then compares the dumps using diff.
# The script is designed to avoid writing the SQLite databases to disk by using process substitution (for fun?).
# You should use the script as indicated in the usage instructions below ...
# Error handling
set -e -o pipefail
# Set a timeout for comparison processes (30 seconds should be enough)
DIFF_TIMEOUT=30
# WARNING: Intended only for macOS
if [[ ! $( uname ) == *"Darwin"* ]]; then
printf "\nSorry... This program is meant to be running on macOS only!\n"
exit
fi
# Functions
# Classical usage function to be recalled if needed
usage() {
echo -ne "\nERROR: All required arguments must be provided and the files must exist.\n"
echo -ne "Usage: $0 --file1 <file1.zip> --file2 <file2.zip>\n\n"
exit 1
}
# Function to verify if needed tools are installed or not
checkTools()
{
local misstools
# Check presence of necessary tools
for entry in "$@"
do
if ! which "${entry}" >/dev/null 2>&1
then
misstools="${misstools} ${entry}"
fi
done
[ -z "${misstools}" ] \
&& return \
|| {
printf '\nRequired tools could not be found: \n %s \n\n' "${misstools}"
exit 1
}
}
# We need all of the following tools installed on the system
checkTools diff diskutil unzip sqlite3 timeout
# Parse command line arguments
while [[ "$#" -gt 0 ]]; do
case $1 in
--file1)
if [ -n "$2" ] && [ "${2:0:1}" != "-" ]; then
SOURCE1="$2"; shift
else
echo "Error: Argument for $1 is missing" >&2
exit 1
fi
;;
--file2)
if [ -n "$2" ] && [ "${2:0:1}" != "-" ]; then
SOURCE2="$2"; shift
else
echo "Error: Argument for $1 is missing" >&2
exit 1
fi
;;
*)
echo "Unknown parameter passed: $1" >&2
exit 1
;;
esac
shift
done
# Check if all required arguments are provided
if [ -z "$SOURCE1" ] || [ -z "$SOURCE2" ] ; then
usage
fi
# Check if provided files exists
if [ ! -f "$SOURCE1" ] || [ ! -f "$SOURCE2" ] ; then
usage
fi
# Checking given files before processing
files=("$SOURCE1" "$SOURCE2")
for file in "${files[@]}"; do
if [ -f "$file" ]; then
if file "$file" | grep -q 'Zip archive'; then
echo "$file exists and is a ZIP archive." > /dev/null
else
printf "\n%s exists but is not a ZIP archive." "${file}"
printf "\nStopping here!\n\n"
exit 1
fi
else
printf "\n%s does not exist." "${file}"
printf "\nCannot do anything ...\n\n"
exit 1
fi
done
# Create a 64MB RAM disk (macOS only); the size is specified in 512-byte blocks!
ramDiskSize=131072
RAMDISK="$( hdiutil attach -nomount ram://$ramDiskSize )"
RAMNAME="ramdisk"
RAMPATH="/Volumes/$RAMNAME"
# Disk initialization in memory (case-insensitive HFS Plus volume)
# Caution : $RAMDISK must not be quoted! (SC2086)
diskutil erasevolume HFS+ ${RAMNAME} $RAMDISK
# Check RAM Disk Creation Success:
# Make sure the "RAMDISK" is ready to be used ...
[[ ! -d $RAMPATH ]] && \
printf "\nAn error occurred: Directory does not exist.\n\n" && \
exit 1
# If userData.db not found, unzip return: 'caution: filename not matched: userData.db'
unzip -p "$SOURCE1" userData.db > $RAMPATH/temp1.db
unzip -p "$SOURCE2" userData.db > $RAMPATH/temp2.db
# Check for an empty dump
if [ ! -s "$RAMPATH/temp1.db" ] || [ ! -s "$RAMPATH/temp2.db" ] ; then
printf "\n An error occurred: Failed to decompress the ZIP file, or
'userData.db' was not found inside the ZIP archive.\n
Please ensure the ZIP file is valid and contains userData.db.\n\n"
diskutil eject $RAMPATH
exit 1
fi
# Check if db files are available
if [ ! -s "$RAMPATH/temp1.db" ] || [ ! -s "$RAMPATH/temp2.db" ] ; then
printf "\nAn error occurred: One or more files do not exist or are empty!\n\n"
diskutil eject $RAMPATH
exit 1
fi
sqlite3 $RAMPATH/temp1.db .dump >| $RAMPATH/output1.sql
sqlite3 $RAMPATH/temp2.db .dump >| $RAMPATH/output2.sql
# Check if sql files are available
if [ ! -s "$RAMPATH/output1.sql" ] || [ ! -s "$RAMPATH/output2.sql" ]; then
printf "\nAn error occurred: One or more files do not exist or are empty!\n\n"
diskutil eject $RAMPATH
exit 1
fi
# Check any difference between two of sqlite3 db files
printf '\nLeft %s \t\t Right %s\n\n' "${SOURCE1}" "${SOURCE2}"
# Immediate exit in case of error voluntarily suspended for the next process
set +e
diff <(grep -e "INSERT INTO LastModified VALUES" $RAMPATH/output1.sql) \
<(grep -e "INSERT INTO LastModified VALUES" $RAMPATH/output2.sql) \
--report-identical-files --side-by-side --suppress-common-lines
if diff -q $RAMPATH/output1.sql $RAMPATH/output2.sql > /dev/null; then
echo "Files are identical." > /dev/null
else
printf '\nLeft %s \t\t Right %s\n\n' "${SOURCE1}" "${SOURCE2}"
timeout $DIFF_TIMEOUT diff <(sort $RAMPATH/output1.sql | grep -v -e LastModified ) \
<(sort $RAMPATH/output2.sql | grep -v -e LastModified ) \
--report-identical-files --side-by-side --suppress-common-lines
exit_status=$?
# Handle the timeout scenario
if [ $exit_status -eq 124 ]; then
echo "The diff operation timed out after $DIFF_TIMEOUT seconds."
else
# Handle successful completion
echo "Diff operation completed successfully." > /dev/null
fi
fi
# Re-enable immediate exit on encountering an error
set -e
# Remove the temporary 64MB RAM disk
diskutil eject $RAMPATH
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment