#!/usr/bin/env zsh # Check if an argument was provided if [[ -z "$1" ]]; then echo "Usage: $0 /path/to/Application.app" exit 1 fi APP_PATH="${1%/}" # Strip any trailing slash for cleaner output # Validate that the target is a directory ending in .app if [[ ! -d "$APP_PATH" || "$APP_PATH" != *.app ]]; then echo "Error: Target must be a valid .app bundle directory." exit 1 fi PLIST_PATH="$APP_PATH/Contents/Info.plist" APP_NAME=$(basename "$APP_PATH" .app) BUNDLE_ID="" # Step 1: Extract the Bundle Identifier (The unique signature of macOS apps) if [[ -f "$PLIST_PATH" ]]; then # Try using 'defaults read' first BUNDLE_ID=$(defaults read "$PLIST_PATH" CFBundleIdentifier 2>/dev/null) # Fallback to PlistBuddy if 'defaults' fails (common with binary plists) if [[ -z "$BUNDLE_ID" ]]; then BUNDLE_ID=$(/usr/libexec/PlistBuddy -c "Print :CFBundleIdentifier" "$PLIST_PATH" 2>/dev/null) fi fi echo "===================================================" echo "Analyzing: $APP_NAME" echo "Bundle ID: ${BUNDLE_ID:-Unknown}" echo "===================================================" # Step 2: Print the size of the primary .app bundle itself if [[ -e "$APP_PATH" ]]; then du -sh "$APP_PATH" fi # Step 3: Define the search matrix # 'typeset -U' ensures array elements are entirely unique, preventing duplicate checks typeset -U LOCATIONS LOCATIONS=() # If we successfully extracted a Bundle ID, build the exact paths if [[ -n "$BUNDLE_ID" ]]; then LOCATIONS+=( "$HOME/Library/Application Support/$BUNDLE_ID" "$HOME/Library/Caches/$BUNDLE_ID" "$HOME/Library/Preferences/$BUNDLE_ID.plist" "$HOME/Library/Preferences/$BUNDLE_ID.LSSharedFileList.plist" "$HOME/Library/Saved Application State/$BUNDLE_ID.savedState" "$HOME/Library/Logs/$BUNDLE_ID" "$HOME/Library/Containers/$BUNDLE_ID" # Sandboxed App Data "$HOME/Library/HTTPStorages/$BUNDLE_ID" # Network cache "$HOME/Library/HTTPStorages/$BUNDLE_ID.binarycookies" "$HOME/Library/WebKit/$BUNDLE_ID" # WebKit local storage "/Library/Application Support/$BUNDLE_ID" # System-wide App Data "/Library/Caches/$BUNDLE_ID" "/Library/Preferences/$BUNDLE_ID.plist" "/Library/Logs/$BUNDLE_ID" ) # Handle Group Containers # Group containers are often prefixed with an Apple Developer Team ID (e.g., ABC123DEF4.com.company.app) # The (N) is a zsh glob qualifier meaning "Null Glob" - it won't throw an error if nothing matches. for d in "$HOME/Library/Group Containers/"*${BUNDLE_ID}(N); do LOCATIONS+=("$d") done fi # Add Name-based fallback locations (Many older or non-sandboxed apps just use their localized name) LOCATIONS+=( "$HOME/Library/Application Support/$APP_NAME" "$HOME/Library/Caches/$APP_NAME" "$HOME/Library/Logs/$APP_NAME" "$HOME/Library/Preferences/com.apple.sharedfilelist/com.apple.LSSharedFileList.ApplicationRecentDocuments/${APP_NAME:l}.sfl2" # Recent documents list "/Library/Application Support/$APP_NAME" "/Library/Caches/$APP_NAME" "/Library/Logs/$APP_NAME" ) # Step 4: Iterate and calculate sizes for target in "${LOCATIONS[@]}"; do if [[ -e "$target" ]]; then # 2>/dev/null suppresses "Permission denied" errors for restricted system folders du -sh "$target" 2>/dev/null fi done echo "==================================================="