diff --git a/bin/clean.sh b/bin/clean.sh index b315b8e..b3a9e11 100755 --- a/bin/clean.sh +++ b/bin/clean.sh @@ -152,9 +152,9 @@ cleanup() { INLINE_SPINNER_PID="" fi - # Clear any spinner output + # Clear any spinner output - spinner outputs to stderr if [[ -t 1 ]]; then - printf "\r\033[K" + printf "\r\033[K" >&2 fi # Stop sudo session @@ -164,8 +164,8 @@ cleanup() { # If interrupted, show message if [[ "$signal" == "INT" ]] || [[ $exit_code -eq 130 ]]; then - printf "\r\033[K" - echo -e "${YELLOW}Interrupted by user${NC}" + printf "\r\033[K" >&2 + echo -e "${YELLOW}Interrupted by user${NC}" >&2 fi } @@ -271,9 +271,14 @@ safe_clean() { for path in "${existing_paths[@]}"; do ( local size - size=$(get_path_size_kb "$path") + # Timeout protection: prevent du from hanging on problematic paths + size=$(run_with_timeout 5 get_path_size_kb "$path") + [[ -z "$size" || ! "$size" =~ ^[0-9]+$ ]] && size=0 local count - count=$(find "$path" -type f 2> /dev/null | wc -l | tr -d ' ') + # Timeout protection: prevent find from hanging on problematic paths + count=$(run_with_timeout 10 sh -c "find \"$path\" -type f 2> /dev/null | wc -l | tr -d ' '") + # If timeout or error, set count to 0 to skip this path + [[ -z "$count" || ! "$count" =~ ^[0-9]+$ ]] && count=0 # Use index + PID for unique filename local tmp_file="$temp_dir/result_${idx}.$$" echo "$size $count" > "$tmp_file" @@ -330,9 +335,14 @@ safe_clean() { for path in "${existing_paths[@]}"; do local size_bytes - size_bytes=$(get_path_size_kb "$path") + # Timeout protection: prevent du from hanging on problematic paths + size_bytes=$(run_with_timeout 5 get_path_size_kb "$path") + [[ -z "$size_bytes" || ! "$size_bytes" =~ ^[0-9]+$ ]] && size_bytes=0 local count - count=$(find "$path" -type f 2> /dev/null | wc -l | tr -d ' ') + # Timeout protection: prevent find from hanging on problematic paths + count=$(run_with_timeout 10 sh -c "find \"$path\" -type f 2> /dev/null | wc -l | tr -d ' '") + # If timeout or error, set count to 0 to skip this path + [[ -z "$count" || ! "$count" =~ ^[0-9]+$ ]] && count=0 if [[ "$count" -gt 0 && "$size_bytes" -gt 0 ]]; then if [[ "$DRY_RUN" != "true" ]]; then @@ -676,7 +686,7 @@ perform_cleanup() { [[ -f "$app/Contents/Info.plist" ]] || continue bundle_id=$(defaults read "$app/Contents/Info.plist" CFBundleIdentifier 2> /dev/null || echo "") [[ -n "$bundle_id" ]] && echo "$bundle_id" >> "$installed_bundles" - done < <(run_with_timeout 10 find "$search_path" -maxdepth 2 -type d -name "*.app" 2> /dev/null || true) + done < <(run_with_timeout 10 command find "$search_path" -maxdepth 2 -type d -name "*.app" 2> /dev/null || true) done # Get running applications and LaunchAgents with timeout protection diff --git a/bin/uninstall.sh b/bin/uninstall.sh index a7a252d..fc599bf 100755 --- a/bin/uninstall.sh +++ b/bin/uninstall.sh @@ -183,8 +183,8 @@ scan_applications() { done < <( # Scan both system and user application directories # Using maxdepth 3 to find apps in subdirectories (e.g., Adobe apps in /Applications/Adobe X/) - find /Applications -name "*.app" -maxdepth 3 -print0 2> /dev/null - find ~/Applications -name "*.app" -maxdepth 3 -print0 2> /dev/null + command find /Applications -name "*.app" -maxdepth 3 -print0 2> /dev/null + command find ~/Applications -name "*.app" -maxdepth 3 -print0 2> /dev/null ) # Second pass: process each app with parallel size calculation diff --git a/lib/check/all.sh b/lib/check/all.sh index ddf4aa2..a41e9a6 100644 --- a/lib/check/all.sh +++ b/lib/check/all.sh @@ -441,7 +441,7 @@ get_macos_update_labels() { # ============================================================================ check_disk_space() { - local free_gb=$(df -H / | awk 'NR==2 {print $4}' | sed 's/G//') + local free_gb=$(command df -H / | awk 'NR==2 {print $4}' | sed 's/G//') local free_num=$(echo "$free_gb" | tr -d 'G' | cut -d'.' -f1) export DISK_FREE_GB=$free_num diff --git a/lib/clean/apps.sh b/lib/clean/apps.sh index 74774f4..d4cfb33 100644 --- a/lib/clean/apps.sh +++ b/lib/clean/apps.sh @@ -32,7 +32,7 @@ clean_ds_store_tree() { ) # Build find command to avoid unbound array expansion with set -u - local -a find_cmd=("find" "$target") + local -a find_cmd=("command" "find" "$target") if [[ "$target" == "$HOME" ]]; then find_cmd+=("-maxdepth" "5") fi @@ -100,7 +100,7 @@ clean_orphaned_app_data() { for app_dir in "${app_dirs[@]}"; do [[ -d "$app_dir" ]] || continue - find "$app_dir" -name "*.app" -maxdepth 3 -type d 2> /dev/null | while IFS= read -r app_path; do + command find "$app_dir" -name "*.app" -maxdepth 3 -type d 2> /dev/null | while IFS= read -r app_path; do local bundle_id bundle_id=$(defaults read "$app_path/Contents/Info.plist" CFBundleIdentifier 2> /dev/null || echo "") [[ -n "$bundle_id" ]] && echo "$bundle_id" diff --git a/lib/clean/caches.sh b/lib/clean/caches.sh index 0e597b9..ccb1390 100644 --- a/lib/clean/caches.sh +++ b/lib/clean/caches.sh @@ -44,7 +44,7 @@ check_tcc_permissions() { # Trigger all TCC prompts upfront by accessing each directory # Using find -maxdepth 1 ensures we touch the directory without deep scanning for dir in "${tcc_dirs[@]}"; do - [[ -d "$dir" ]] && find "$dir" -maxdepth 1 -type d > /dev/null 2>&1 + [[ -d "$dir" ]] && command find "$dir" -maxdepth 1 -type d > /dev/null 2>&1 done stop_inline_spinner @@ -94,7 +94,7 @@ clean_service_worker_cache() { fi cleaned_size=$((cleaned_size + size)) fi - done < <(find "$cache_path" -type d -depth 2 2> /dev/null) + done < <(command find "$cache_path" -type d -depth 2 2> /dev/null) if [[ $cleaned_size -gt 0 ]]; then local cleaned_mb=$((cleaned_size / 1024)) @@ -120,7 +120,7 @@ clean_project_caches() { local nextjs_tmp_file nextjs_tmp_file=$(create_temp_file) ( - find "$HOME" -P -mount -type d -name ".next" -maxdepth 3 \ + command find "$HOME" -P -mount -type d -name ".next" -maxdepth 3 \ -not -path "*/Library/*" \ -not -path "*/.Trash/*" \ -not -path "*/node_modules/*" \ @@ -164,7 +164,7 @@ clean_project_caches() { local pycache_tmp_file pycache_tmp_file=$(create_temp_file) ( - find "$HOME" -P -mount -type d -name "__pycache__" -maxdepth 3 \ + command find "$HOME" -P -mount -type d -name "__pycache__" -maxdepth 3 \ -not -path "*/Library/*" \ -not -path "*/.Trash/*" \ -not -path "*/node_modules/*" \ diff --git a/lib/clean/maintenance.sh b/lib/clean/maintenance.sh index e5959ea..794295d 100644 --- a/lib/clean/maintenance.sh +++ b/lib/clean/maintenance.sh @@ -49,7 +49,7 @@ clean_broken_preferences() { ((broken_count++)) ((total_size_kb += size_kb)) fi - done < <(find "$prefs_dir" -maxdepth 1 -name "*.plist" -type f 2> /dev/null || true) + done < <(command find "$prefs_dir" -maxdepth 1 -name "*.plist" -type f 2> /dev/null || true) # Check ByHost preferences local byhost_dir="$prefs_dir/ByHost" @@ -76,7 +76,7 @@ clean_broken_preferences() { ((broken_count++)) ((total_size_kb += size_kb)) fi - done < <(find "$byhost_dir" -name "*.plist" -type f 2> /dev/null || true) + done < <(command find "$byhost_dir" -name "*.plist" -type f 2> /dev/null || true) fi if [[ -t 1 ]]; then @@ -153,7 +153,7 @@ clean_broken_login_items() { ((broken_count++)) ((total_size_kb += size_kb)) - done < <(find "$launch_agents_dir" -name "*.plist" -type f 2> /dev/null || true) + done < <(command find "$launch_agents_dir" -name "*.plist" -type f 2> /dev/null || true) if [[ -t 1 ]]; then stop_inline_spinner diff --git a/lib/clean/system.sh b/lib/clean/system.sh index 03a2692..b60a3ba 100644 --- a/lib/clean/system.sh +++ b/lib/clean/system.sh @@ -13,12 +13,12 @@ clean_deep_system() { # Clean old temp files local tmp_cleaned=0 - local tmp_count=$(sudo find /tmp -type f -mtime +"${MOLE_TEMP_FILE_AGE_DAYS}" 2> /dev/null | wc -l | tr -d ' ') + local tmp_count=$(sudo command find /tmp -type f -mtime +"${MOLE_TEMP_FILE_AGE_DAYS}" 2> /dev/null | wc -l | tr -d ' ') if [[ "$tmp_count" -gt 0 ]]; then safe_sudo_find_delete "/tmp" "*" "${MOLE_TEMP_FILE_AGE_DAYS}" "f" || true tmp_cleaned=1 fi - local var_tmp_count=$(sudo find /var/tmp -type f -mtime +"${MOLE_TEMP_FILE_AGE_DAYS}" 2> /dev/null | wc -l | tr -d ' ') + local var_tmp_count=$(sudo command find /var/tmp -type f -mtime +"${MOLE_TEMP_FILE_AGE_DAYS}" 2> /dev/null | wc -l | tr -d ' ') if [[ "$var_tmp_count" -gt 0 ]]; then safe_sudo_find_delete "/var/tmp" "*" "${MOLE_TEMP_FILE_AGE_DAYS}" "f" || true tmp_cleaned=1 @@ -47,7 +47,7 @@ clean_deep_system() { while IFS= read -r -d '' item; do # Skip system-protected files (restricted flag) local item_flags - item_flags=$(stat -f%Sf "$item" 2> /dev/null || echo "") + item_flags=$(command stat -f%Sf "$item" 2> /dev/null || echo "") if [[ "$item_flags" == *"restricted"* ]]; then continue fi @@ -55,7 +55,7 @@ clean_deep_system() { if safe_sudo_remove "$item"; then ((updates_cleaned++)) fi - done < <(find /Library/Updates -mindepth 1 -maxdepth 1 -print0 2> /dev/null) + done < <(command find /Library/Updates -mindepth 1 -maxdepth 1 -print0 2> /dev/null) [[ $updates_cleaned -gt 0 ]] && log_success "System library updates" fi fi @@ -103,7 +103,7 @@ clean_time_machine_failed_backups() { fi fi - local fs_type=$(df -T "$volume" 2> /dev/null | tail -1 | awk '{print $2}') + local fs_type=$(command df -T "$volume" 2> /dev/null | tail -1 | awk '{print $2}') case "$fs_type" in nfs | smbfs | afpfs | cifs | webdav) continue ;; esac @@ -150,7 +150,7 @@ clean_time_machine_failed_backups() { note_activity fi fi - done < <(find "$backupdb_dir" -maxdepth 3 -type d \( -name "*.inProgress" -o -name "*.inprogress" \) 2> /dev/null || true) + done < <(command find "$backupdb_dir" -maxdepth 3 -type d \( -name "*.inProgress" -o -name "*.inprogress" \) 2> /dev/null || true) fi # APFS style backups (.backupbundle or .sparsebundle) @@ -200,7 +200,7 @@ clean_time_machine_failed_backups() { note_activity fi fi - done < <(find "$mounted_path" -maxdepth 3 -type d \( -name "*.inProgress" -o -name "*.inprogress" \) 2> /dev/null || true) + done < <(command find "$mounted_path" -maxdepth 3 -type d \( -name "*.inProgress" -o -name "*.inprogress" \) 2> /dev/null || true) fi done done diff --git a/lib/clean/user.sh b/lib/clean/user.sh index 3b89c52..09e8a54 100644 --- a/lib/clean/user.sh +++ b/lib/clean/user.sh @@ -15,7 +15,7 @@ clean_user_essentials() { [[ -d "$volume" && -d "$volume/.Trashes" && -w "$volume" ]] || continue # Skip network volumes - local fs_type=$(df -T "$volume" 2> /dev/null | tail -1 | awk '{print $2}') + local fs_type=$(command df -T "$volume" 2> /dev/null | tail -1 | awk '{print $2}') case "$fs_type" in nfs | smbfs | afpfs | cifs | webdav) continue ;; esac @@ -26,7 +26,7 @@ clean_user_essentials() { # Safely iterate and remove each item while IFS= read -r -d '' item; do safe_remove "$item" true || true - done < <(find "$volume/.Trashes" -mindepth 1 -maxdepth 1 -print0 2> /dev/null) + done < <(command find "$volume/.Trashes" -mindepth 1 -maxdepth 1 -print0 2> /dev/null) fi fi done @@ -65,7 +65,7 @@ clean_finder_metadata() { [[ -d "$volume" && -w "$volume" ]] || continue local fs_type="" - fs_type=$(df -T "$volume" 2> /dev/null | tail -1 | awk '{print $2}') + fs_type=$(command df -T "$volume" 2> /dev/null | tail -1 | awk '{print $2}') case "$fs_type" in nfs | smbfs | afpfs | cifs | webdav) continue ;; esac @@ -150,7 +150,7 @@ clean_browsers() { [[ "$sw_path" == *"Arc"* ]] && browser_name="Arc" [[ "$profile_name" != "Default" ]] && browser_name="$browser_name ($profile_name)" clean_service_worker_cache "$browser_name" "$sw_path" - done < <(find "$HOME/Library/Application Support/Google/Chrome" \ + done < <(command find "$HOME/Library/Application Support/Google/Chrome" \ "$HOME/Library/Application Support/Microsoft Edge" \ "$HOME/Library/Application Support/BraveSoftware/Brave-Browser" \ "$HOME/Library/Application Support/Arc/User Data" \ @@ -250,7 +250,7 @@ clean_application_support_logs() { local profile_name=$(basename "$profile_path") [[ "$profile_name" == "User Data" ]] && profile_name=$(basename "$(dirname "$profile_path")") clean_service_worker_cache "$app_name ($profile_name)" "$sw_cache" - done < <(find "$app_dir" -maxdepth 4 -type d \( -name "CacheStorage" -o -name "ScriptCache" \) -path "*/Service Worker/*" 2> /dev/null || true) + done < <(command find "$app_dir" -maxdepth 4 -type d \( -name "CacheStorage" -o -name "ScriptCache" \) -path "*/Service Worker/*" 2> /dev/null || true) # Clean stale update downloads (older than 7 days) if [[ -d "$app_dir/update" ]] && ls "$app_dir/update" > /dev/null 2>&1; then @@ -259,7 +259,7 @@ clean_application_support_logs() { if [[ $dir_age_days -ge $MOLE_TEMP_FILE_AGE_DAYS ]]; then safe_clean "$update_dir" "Stale update: $app_name" fi - done < <(find "$app_dir/update" -mindepth 1 -maxdepth 1 -type d 2> /dev/null || true) + done < <(command find "$app_dir/update" -mindepth 1 -maxdepth 1 -type d 2> /dev/null || true) fi done @@ -268,17 +268,17 @@ clean_application_support_logs() { while IFS= read -r logs_dir; do local container_name=$(basename "$(dirname "$logs_dir")") safe_clean "$logs_dir"/* "Group container logs: $container_name" - done < <(find "$HOME/Library/Group Containers" -maxdepth 2 -type d -name "Logs" 2> /dev/null || true) + done < <(command find "$HOME/Library/Group Containers" -maxdepth 2 -type d -name "Logs" 2> /dev/null || true) fi } # Check and show iOS device backup info check_ios_device_backups() { local backup_dir="$HOME/Library/Application Support/MobileSync/Backup" - if [[ -d "$backup_dir" ]] && find "$backup_dir" -mindepth 1 -maxdepth 1 | read -r _; then + if [[ -d "$backup_dir" ]] && command find "$backup_dir" -mindepth 1 -maxdepth 1 | read -r _; then local backup_kb=$(get_path_size_kb "$backup_dir") if [[ -n "${backup_kb:-}" && "$backup_kb" -gt 102400 ]]; then - local backup_human=$(du -sh "$backup_dir" 2> /dev/null | awk '{print $1}') + local backup_human=$(command du -sh "$backup_dir" 2> /dev/null | awk '{print $1}') note_activity echo -e " Found ${GREEN}${backup_human}${NC} iOS backups" echo -e " You can delete them manually: ${backup_dir}" diff --git a/lib/core/common.sh b/lib/core/common.sh index 2c5780f..e14d619 100755 --- a/lib/core/common.sh +++ b/lib/core/common.sh @@ -241,7 +241,7 @@ safe_find_delete() { fi # Execute find with safety limits - find "$base_dir" \ + command find "$base_dir" \ -maxdepth 3 \ -name "$pattern" \ -type "$type_filter" \ @@ -277,7 +277,7 @@ safe_sudo_find_delete() { fi # Execute find with safety limits - sudo find "$base_dir" \ + sudo command find "$base_dir" \ -maxdepth 3 \ -name "$pattern" \ -type "$type_filter" \ @@ -386,7 +386,7 @@ detect_architecture() { # Get free disk space on root volume (human-readable) get_free_space() { - df -h / | awk 'NR==2 {print $4}' + command df -h / | awk 'NR==2 {print $4}' } # Clear terminal screen and move cursor to home @@ -396,12 +396,16 @@ clear_screen() { # Hide terminal cursor hide_cursor() { - printf '\033[?25l' + [[ -t 1 ]] || return 0 + # Output to stderr for consistency with spinner, ensure unbuffered + printf '\033[?25l' >&2 } # Show terminal cursor show_cursor() { - printf '\033[?25h' + [[ -t 1 ]] || return 0 + # Output to stderr for consistency with spinner, ensure unbuffered + printf '\033[?25h' >&2 } # Read single keypress and return normalized key name @@ -997,7 +1001,13 @@ start_inline_spinner() { # Stop inline spinner stop_inline_spinner() { if [[ -n "$INLINE_SPINNER_PID" ]]; then - kill "$INLINE_SPINNER_PID" 2> /dev/null || true + # Try graceful TERM first, then force KILL if needed + if kill -0 "$INLINE_SPINNER_PID" 2> /dev/null; then + kill -TERM "$INLINE_SPINNER_PID" 2> /dev/null || true + sleep 0.05 2> /dev/null || true + # Force kill if still running + kill -KILL "$INLINE_SPINNER_PID" 2> /dev/null || true + fi wait "$INLINE_SPINNER_PID" 2> /dev/null || true INLINE_SPINNER_PID="" # Clear the line - use \033[2K to clear entire line, not just to end @@ -1150,7 +1160,7 @@ clean_tool_cache() { get_path_size_kb() { local path="$1" local result - result=$(du -sk "$path" 2> /dev/null | awk '{print $1}') + result=$(command du -sk "$path" 2> /dev/null | awk '{print $1}') echo "${result:-0}" } @@ -1177,22 +1187,36 @@ force_kill_app() { local app_name="$1" local app_path="${2:-}" - # Use app path for precise matching if provided - local match_pattern="$app_name" - if [[ -n "$app_path" && -e "$app_path" ]]; then - # Use the app bundle path for more precise matching - match_pattern="$app_path" + # Get the executable name from bundle if app_path is provided + local exec_name="" + if [[ -n "$app_path" && -e "$app_path/Contents/Info.plist" ]]; then + exec_name=$(defaults read "$app_path/Contents/Info.plist" CFBundleExecutable 2> /dev/null || echo "") fi - if pgrep -f "$match_pattern" > /dev/null 2>&1; then - pkill -f "$match_pattern" 2> /dev/null || true - sleep 1 + # Use executable name for precise matching, fallback to app name + local match_pattern="${exec_name:-$app_name}" + + # Check if process is actually running using exact match (-x) + if ! pgrep -x "$match_pattern" > /dev/null 2>&1; then + # Not running, return success + return 0 fi - if pgrep -f "$match_pattern" > /dev/null 2>&1; then - pkill -9 -f "$match_pattern" 2> /dev/null || true - sleep 1 + + # Try graceful termination first + pkill -x "$match_pattern" 2> /dev/null || true + sleep 1 + + # Check again after graceful kill + if ! pgrep -x "$match_pattern" > /dev/null 2>&1; then + return 0 fi - pgrep -f "$match_pattern" > /dev/null 2>&1 && return 1 || return 0 + + # Force kill if still running + pkill -9 -x "$match_pattern" 2> /dev/null || true + sleep 1 + + # Final check + pgrep -x "$match_pattern" > /dev/null 2>&1 && return 1 || return 0 } # Remove application icons from the Dock (best effort) @@ -1791,7 +1815,7 @@ find_app_files() { [[ -f ~/Library/Preferences/"$bundle_id".plist ]] && files_to_clean+=("$HOME/Library/Preferences/$bundle_id.plist") while IFS= read -r -d '' pref; do files_to_clean+=("$pref") - done < <(find ~/Library/Preferences/ByHost \( -name "$bundle_id*.plist" \) -print0 2> /dev/null) + done < <(command find ~/Library/Preferences/ByHost \( -name "$bundle_id*.plist" \) -print0 2> /dev/null) # Logs [[ -d ~/Library/Logs/"$app_name" ]] && files_to_clean+=("$HOME/Library/Logs/$app_name") @@ -1800,7 +1824,7 @@ find_app_files() { # Crash Reports and Diagnostics while IFS= read -r -d '' report; do files_to_clean+=("$report") - done < <(find ~/Library/Logs/DiagnosticReports \( -name "*$app_name*" -o -name "*$bundle_id*" \) -print0 2> /dev/null) + done < <(command find ~/Library/Logs/DiagnosticReports \( -name "*$app_name*" -o -name "*$bundle_id*" \) -print0 2> /dev/null) # Saved Application State [[ -d ~/Library/Saved\ Application\ State/"$bundle_id".savedState ]] && files_to_clean+=("$HOME/Library/Saved Application State/$bundle_id.savedState") @@ -1811,7 +1835,7 @@ find_app_files() { # Group Containers while IFS= read -r -d '' container; do files_to_clean+=("$container") - done < <(find ~/Library/Group\ Containers -type d \( -name "*$bundle_id*" \) -print0 2> /dev/null) + done < <(command find ~/Library/Group\ Containers -type d \( -name "*$bundle_id*" \) -print0 2> /dev/null) # WebKit data [[ -d ~/Library/WebKit/"$bundle_id" ]] && files_to_clean+=("$HOME/Library/WebKit/$bundle_id") @@ -1835,7 +1859,7 @@ find_app_files() { # Internet Plug-Ins while IFS= read -r -d '' plugin; do files_to_clean+=("$plugin") - done < <(find ~/Library/Internet\ Plug-Ins \( -name "$bundle_id*" -o -name "$app_name*" \) -print0 2> /dev/null) + done < <(command find ~/Library/Internet\ Plug-Ins \( -name "$bundle_id*" -o -name "$app_name*" \) -print0 2> /dev/null) # QuickLook Plugins [[ -d ~/Library/QuickLook/"$app_name".qlgenerator ]] && files_to_clean+=("$HOME/Library/QuickLook/$app_name.qlgenerator") @@ -1852,7 +1876,7 @@ find_app_files() { # CoreData while IFS= read -r -d '' coredata; do files_to_clean+=("$coredata") - done < <(find ~/Library/CoreData \( -name "*$bundle_id*" -o -name "*$app_name*" \) -print0 2> /dev/null) + done < <(command find ~/Library/CoreData \( -name "*$bundle_id*" -o -name "*$app_name*" \) -print0 2> /dev/null) # Autosave Information [[ -d ~/Library/Autosave\ Information/"$bundle_id" ]] && files_to_clean+=("$HOME/Library/Autosave Information/$bundle_id") @@ -1863,7 +1887,7 @@ find_app_files() { # Receipts (user-level) while IFS= read -r -d '' receipt; do files_to_clean+=("$receipt") - done < <(find ~/Library/Receipts \( -name "*$bundle_id*" -o -name "*$app_name*" \) -print0 2> /dev/null) + done < <(command find ~/Library/Receipts \( -name "*$bundle_id*" -o -name "*$app_name*" \) -print0 2> /dev/null) # Spotlight Plugins [[ -d ~/Library/Spotlight/"$app_name".mdimporter ]] && files_to_clean+=("$HOME/Library/Spotlight/$app_name.mdimporter") @@ -1871,7 +1895,7 @@ find_app_files() { # Scripting Additions while IFS= read -r -d '' scripting; do files_to_clean+=("$scripting") - done < <(find ~/Library/ScriptingAdditions \( -name "$app_name*" -o -name "$bundle_id*" \) -print0 2> /dev/null) + done < <(command find ~/Library/ScriptingAdditions \( -name "$app_name*" -o -name "$bundle_id*" \) -print0 2> /dev/null) # Color Pickers [[ -d ~/Library/ColorPickers/"$app_name".colorPicker ]] && files_to_clean+=("$HOME/Library/ColorPickers/$app_name.colorPicker") @@ -1879,58 +1903,58 @@ find_app_files() { # Quartz Compositions while IFS= read -r -d '' composition; do files_to_clean+=("$composition") - done < <(find ~/Library/Compositions \( -name "$app_name*" -o -name "$bundle_id*" \) -print0 2> /dev/null) + done < <(command find ~/Library/Compositions \( -name "$app_name*" -o -name "$bundle_id*" \) -print0 2> /dev/null) # Address Book Plug-Ins while IFS= read -r -d '' plugin; do files_to_clean+=("$plugin") - done < <(find ~/Library/Address\ Book\ Plug-Ins \( -name "$app_name*" -o -name "$bundle_id*" \) -print0 2> /dev/null) + done < <(command find ~/Library/Address\ Book\ Plug-Ins \( -name "$app_name*" -o -name "$bundle_id*" \) -print0 2> /dev/null) # Mail Bundles while IFS= read -r -d '' bundle; do files_to_clean+=("$bundle") - done < <(find ~/Library/Mail/Bundles \( -name "$app_name*" -o -name "$bundle_id*" \) -print0 2> /dev/null) + done < <(command find ~/Library/Mail/Bundles \( -name "$app_name*" -o -name "$bundle_id*" \) -print0 2> /dev/null) # Input Managers (app-specific only) while IFS= read -r -d '' manager; do files_to_clean+=("$manager") - done < <(find ~/Library/InputManagers \( -name "$app_name*" -o -name "$bundle_id*" \) -print0 2> /dev/null) + done < <(command find ~/Library/InputManagers \( -name "$app_name*" -o -name "$bundle_id*" \) -print0 2> /dev/null) # Custom Sounds while IFS= read -r -d '' sound; do files_to_clean+=("$sound") - done < <(find ~/Library/Sounds \( -name "$app_name*" -o -name "$bundle_id*" \) -print0 2> /dev/null) + done < <(command find ~/Library/Sounds \( -name "$app_name*" -o -name "$bundle_id*" \) -print0 2> /dev/null) # Plugins while IFS= read -r -d '' plugin; do files_to_clean+=("$plugin") - done < <(find ~/Library/Plugins \( -name "$app_name*" -o -name "$bundle_id*" \) -print0 2> /dev/null) + done < <(command find ~/Library/Plugins \( -name "$app_name*" -o -name "$bundle_id*" \) -print0 2> /dev/null) # Private Frameworks while IFS= read -r -d '' framework; do files_to_clean+=("$framework") - done < <(find ~/Library/PrivateFrameworks \( -name "$app_name*" -o -name "$bundle_id*" \) -print0 2> /dev/null) + done < <(command find ~/Library/PrivateFrameworks \( -name "$app_name*" -o -name "$bundle_id*" \) -print0 2> /dev/null) # Audio Plug-Ins while IFS= read -r -d '' plugin; do files_to_clean+=("$plugin") - done < <(find ~/Library/Audio/Plug-Ins \( -name "$app_name*" -o -name "$bundle_id*" \) -print0 2> /dev/null) + done < <(command find ~/Library/Audio/Plug-Ins \( -name "$app_name*" -o -name "$bundle_id*" \) -print0 2> /dev/null) # Components while IFS= read -r -d '' component; do files_to_clean+=("$component") - done < <(find ~/Library/Components \( -name "$app_name*" -o -name "$bundle_id*" \) -print0 2> /dev/null) + done < <(command find ~/Library/Components \( -name "$app_name*" -o -name "$bundle_id*" \) -print0 2> /dev/null) # Metadata while IFS= read -r -d '' metadata; do files_to_clean+=("$metadata") - done < <(find ~/Library/Metadata \( -name "$app_name*" -o -name "$bundle_id*" \) -print0 2> /dev/null) + done < <(command find ~/Library/Metadata \( -name "$app_name*" -o -name "$bundle_id*" \) -print0 2> /dev/null) # Workflows [[ -d ~/Library/Workflows/"$app_name".workflow ]] && files_to_clean+=("$HOME/Library/Workflows/$app_name.workflow") while IFS= read -r -d '' workflow; do files_to_clean+=("$workflow") - done < <(find ~/Library/Workflows \( -name "$app_name*" -o -name "$bundle_id*" \) -print0 2> /dev/null) + done < <(command find ~/Library/Workflows \( -name "$app_name*" -o -name "$bundle_id*" \) -print0 2> /dev/null) # Favorites (excluding Safari) while IFS= read -r -d '' favorite; do @@ -1939,7 +1963,7 @@ find_app_files() { *Safari*) continue ;; esac files_to_clean+=("$favorite") - done < <(find ~/Library/Favorites \( -name "$app_name*" -o -name "$bundle_id*" \) -print0 2> /dev/null) + done < <(command find ~/Library/Favorites \( -name "$app_name*" -o -name "$bundle_id*" \) -print0 2> /dev/null) # Unix-style configuration directories and files (cross-platform apps) [[ -d ~/.config/"$app_name" ]] && files_to_clean+=("$HOME/.config/$app_name") @@ -1972,7 +1996,7 @@ find_app_system_files() { # Privileged Helper Tools while IFS= read -r -d '' helper; do system_files+=("$helper") - done < <(find /Library/PrivilegedHelperTools \( -name "$bundle_id*" \) -print0 2> /dev/null) + done < <(command find /Library/PrivilegedHelperTools \( -name "$bundle_id*" \) -print0 2> /dev/null) # System Preferences [[ -f /Library/Preferences/"$bundle_id".plist ]] && system_files+=("/Library/Preferences/$bundle_id.plist") @@ -1980,7 +2004,7 @@ find_app_system_files() { # Installation Receipts while IFS= read -r -d '' receipt; do system_files+=("$receipt") - done < <(find /private/var/db/receipts \( -name "*$bundle_id*" \) -print0 2> /dev/null) + done < <(command find /private/var/db/receipts \( -name "*$bundle_id*" \) -print0 2> /dev/null) # System Logs [[ -d /Library/Logs/"$app_name" ]] && system_files+=("/Library/Logs/$app_name") @@ -1989,7 +2013,7 @@ find_app_system_files() { # System Crash Reports and Diagnostics while IFS= read -r -d '' report; do system_files+=("$report") - done < <(find /Library/Logs/DiagnosticReports \( -name "*$app_name*" -o -name "*$bundle_id*" \) -print0 2> /dev/null) + done < <(command find /Library/Logs/DiagnosticReports \( -name "*$app_name*" -o -name "*$bundle_id*" \) -print0 2> /dev/null) # System Frameworks [[ -d /Library/Frameworks/"$app_name".framework ]] && system_files+=("/Library/Frameworks/$app_name.framework") @@ -1997,7 +2021,7 @@ find_app_system_files() { # System Internet Plug-Ins while IFS= read -r -d '' plugin; do system_files+=("$plugin") - done < <(find /Library/Internet\ Plug-Ins \( -name "$bundle_id*" -o -name "$app_name*" \) -print0 2> /dev/null) + done < <(command find /Library/Internet\ Plug-Ins \( -name "$bundle_id*" -o -name "$app_name*" \) -print0 2> /dev/null) # System QuickLook Plugins [[ -d /Library/QuickLook/"$app_name".qlgenerator ]] && system_files+=("/Library/QuickLook/$app_name.qlgenerator") @@ -2005,7 +2029,7 @@ find_app_system_files() { # System Receipts while IFS= read -r -d '' receipt; do system_files+=("$receipt") - done < <(find /Library/Receipts \( -name "*$bundle_id*" -o -name "*$app_name*" \) -print0 2> /dev/null) + done < <(command find /Library/Receipts \( -name "*$bundle_id*" -o -name "*$app_name*" \) -print0 2> /dev/null) # System Spotlight Plugins [[ -d /Library/Spotlight/"$app_name".mdimporter ]] && system_files+=("/Library/Spotlight/$app_name.mdimporter") @@ -2013,7 +2037,7 @@ find_app_system_files() { # System Scripting Additions while IFS= read -r -d '' scripting; do system_files+=("$scripting") - done < <(find /Library/ScriptingAdditions \( -name "$app_name*" -o -name "$bundle_id*" \) -print0 2> /dev/null) + done < <(command find /Library/ScriptingAdditions \( -name "$app_name*" -o -name "$bundle_id*" \) -print0 2> /dev/null) # System Color Pickers [[ -d /Library/ColorPickers/"$app_name".colorPicker ]] && system_files+=("/Library/ColorPickers/$app_name.colorPicker") @@ -2021,32 +2045,32 @@ find_app_system_files() { # System Quartz Compositions while IFS= read -r -d '' composition; do system_files+=("$composition") - done < <(find /Library/Compositions \( -name "$app_name*" -o -name "$bundle_id*" \) -print0 2> /dev/null) + done < <(command find /Library/Compositions \( -name "$app_name*" -o -name "$bundle_id*" \) -print0 2> /dev/null) # System Address Book Plug-Ins while IFS= read -r -d '' plugin; do system_files+=("$plugin") - done < <(find /Library/Address\ Book\ Plug-Ins \( -name "$app_name*" -o -name "$bundle_id*" \) -print0 2> /dev/null) + done < <(command find /Library/Address\ Book\ Plug-Ins \( -name "$app_name*" -o -name "$bundle_id*" \) -print0 2> /dev/null) # System Mail Bundles while IFS= read -r -d '' bundle; do system_files+=("$bundle") - done < <(find /Library/Mail/Bundles \( -name "$app_name*" -o -name "$bundle_id*" \) -print0 2> /dev/null) + done < <(command find /Library/Mail/Bundles \( -name "$app_name*" -o -name "$bundle_id*" \) -print0 2> /dev/null) # System Input Managers while IFS= read -r -d '' manager; do system_files+=("$manager") - done < <(find /Library/InputManagers \( -name "$app_name*" -o -name "$bundle_id*" \) -print0 2> /dev/null) + done < <(command find /Library/InputManagers \( -name "$app_name*" -o -name "$bundle_id*" \) -print0 2> /dev/null) # System Sounds while IFS= read -r -d '' sound; do system_files+=("$sound") - done < <(find /Library/Sounds \( -name "$app_name*" -o -name "$bundle_id*" \) -print0 2> /dev/null) + done < <(command find /Library/Sounds \( -name "$app_name*" -o -name "$bundle_id*" \) -print0 2> /dev/null) # System Contextual Menu Items while IFS= read -r -d '' item; do system_files+=("$item") - done < <(find /Library/Contextual\ Menu\ Items \( -name "$app_name*" -o -name "$bundle_id*" \) -print0 2> /dev/null) + done < <(command find /Library/Contextual\ Menu\ Items \( -name "$app_name*" -o -name "$bundle_id*" \) -print0 2> /dev/null) # System Preference Panes [[ -d /Library/PreferencePanes/"$app_name".prefPane ]] && system_files+=("/Library/PreferencePanes/$app_name.prefPane") @@ -2061,17 +2085,17 @@ find_app_system_files() { # System Audio Plug-Ins while IFS= read -r -d '' plugin; do system_files+=("$plugin") - done < <(find /Library/Audio/Plug-Ins \( -name "$app_name*" -o -name "$bundle_id*" \) -print0 2> /dev/null) + done < <(command find /Library/Audio/Plug-Ins \( -name "$app_name*" -o -name "$bundle_id*" \) -print0 2> /dev/null) # System Components while IFS= read -r -d '' component; do system_files+=("$component") - done < <(find /Library/Components \( -name "$app_name*" -o -name "$bundle_id*" \) -print0 2> /dev/null) + done < <(command find /Library/Components \( -name "$app_name*" -o -name "$bundle_id*" \) -print0 2> /dev/null) # System Extensions while IFS= read -r -d '' extension; do system_files+=("$extension") - done < <(find /Library/Extensions \( -name "$app_name*" -o -name "$bundle_id*" \) -print0 2> /dev/null) + done < <(command find /Library/Extensions \( -name "$app_name*" -o -name "$bundle_id*" \) -print0 2> /dev/null) # Only print if array has elements if [[ ${#system_files[@]} -gt 0 ]]; then diff --git a/lib/optimize/tasks.sh b/lib/optimize/tasks.sh index ead607b..98348e8 100644 --- a/lib/optimize/tasks.sh +++ b/lib/optimize/tasks.sh @@ -207,7 +207,9 @@ opt_mail_downloads() { local deleted=0 for target_path in "${mail_dirs[@]}"; do if [[ -d "$target_path" ]]; then - local file_count=$(find "$target_path" -type f -mtime "+$MOLE_LOG_AGE_DAYS" 2> /dev/null | wc -l | tr -d ' ') + # Timeout protection: prevent find from hanging on large mail directories + local file_count=$(run_with_timeout 15 sh -c "find \"$target_path\" -type f -mtime \"+$MOLE_LOG_AGE_DAYS\" 2> /dev/null | wc -l | tr -d ' '") + [[ -z "$file_count" || ! "$file_count" =~ ^[0-9]+$ ]] && file_count=0 if [[ "$file_count" -gt 0 ]]; then safe_find_delete "$target_path" "*" "$MOLE_LOG_AGE_DAYS" "f" deleted=$((deleted + file_count)) @@ -238,7 +240,7 @@ opt_saved_state_cleanup() { if safe_remove "$state_path" true; then ((deleted++)) fi - done < <(find "$state_dir" -type d -name "*.savedState" -mtime "+$MOLE_SAVED_STATE_AGE_DAYS" -print0 2> /dev/null) + done < <(command find "$state_dir" -type d -name "*.savedState" -mtime "+$MOLE_SAVED_STATE_AGE_DAYS" -print0 2> /dev/null) if [[ $deleted -gt 0 ]]; then echo -e "${GREEN}${ICON_SUCCESS}${NC} Removed $deleted old saved state(s)" @@ -448,7 +450,7 @@ get_disk_info() { local home="${HOME:-/}" local df_output total_gb used_gb used_percent - df_output=$(df -k "$home" 2> /dev/null | tail -1) + df_output=$(command df -k "$home" 2> /dev/null | tail -1) local total_kb used_kb total_kb=$(echo "$df_output" | awk '{print $2}' 2> /dev/null || echo "0") diff --git a/lib/uninstall/batch.sh b/lib/uninstall/batch.sh index 9ef4d53..5c93328 100755 --- a/lib/uninstall/batch.sh +++ b/lib/uninstall/batch.sh @@ -120,8 +120,13 @@ batch_uninstall_applications() { [[ -z "$selected_app" ]] && continue IFS='|' read -r _ app_path app_name bundle_id _ _ <<< "$selected_app" - # Check if app is running (use app path for precise matching) - if pgrep -f "$app_path" > /dev/null 2>&1; then + # Check if app is running using executable name from bundle + local exec_name="" + if [[ -e "$app_path/Contents/Info.plist" ]]; then + exec_name=$(defaults read "$app_path/Contents/Info.plist" CFBundleExecutable 2> /dev/null || echo "") + fi + local check_pattern="${exec_name:-$app_name}" + if pgrep -x "$check_pattern" > /dev/null 2>&1; then running_apps+=("$app_name") fi diff --git a/mole b/mole index 55b1f4a..e3e4dc2 100755 --- a/mole +++ b/mole @@ -22,7 +22,7 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" source "$SCRIPT_DIR/lib/core/common.sh" # Version info -VERSION="1.11.19" +VERSION="1.11.20" MOLE_TAGLINE="can dig deep to clean your Mac." # Check if Touch ID is already configured