Last active
December 23, 2023 17:03
-
-
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)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/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