From 6dfd6754178114f6f4eea0b2884083f1c014d865 Mon Sep 17 00:00:00 2001 From: Tw93 Date: Sat, 3 Jan 2026 18:07:47 +0800 Subject: [PATCH] refactor: standardize epoch time retrieval with `get_epoch_seconds` and ensure locale-independent string transformations. --- bin/clean.sh | 8 +++++--- bin/uninstall.sh | 10 +++++----- bin/uninstall_lib.sh | 10 +++++----- lib/check/all.sh | 2 +- lib/check/health_json.sh | 3 ++- lib/clean/apps.sh | 6 ++++-- lib/clean/brew.sh | 4 ++-- lib/clean/project.sh | 5 +++-- lib/clean/system.sh | 24 ++++++++++++++++-------- lib/clean/user.sh | 4 ++-- lib/core/app_protection.sh | 4 ++-- lib/core/base.sh | 30 +++++++++++++++++++++++++++--- lib/optimize/tasks.sh | 4 ++-- mole | 4 ++-- scripts/setup-quick-launchers.sh | 2 +- 15 files changed, 79 insertions(+), 41 deletions(-) diff --git a/bin/clean.sh b/bin/clean.sh index 35da0af..a9adc9a 100755 --- a/bin/clean.sh +++ b/bin/clean.sh @@ -369,7 +369,8 @@ safe_clean() { fi local idx=0 - local last_progress_update=$(date +%s) + local last_progress_update + last_progress_update=$(get_epoch_seconds) for path in "${existing_paths[@]}"; do local size size=$(get_cleanup_path_size_kb "$path") @@ -384,14 +385,15 @@ safe_clean() { ((idx++)) if [[ $((idx % 20)) -eq 0 && "$show_spinner" == "true" && -t 1 ]]; then update_progress_if_needed "$idx" "${#existing_paths[@]}" last_progress_update 1 || true - last_progress_update=$(date +%s) + last_progress_update=$(get_epoch_seconds) fi done else local -a pids=() local idx=0 local completed=0 - local last_progress_update=$(date +%s) + local last_progress_update + last_progress_update=$(get_epoch_seconds) local total_paths=${#existing_paths[@]} if [[ ${#existing_paths[@]} -gt 0 ]]; then diff --git a/bin/uninstall.sh b/bin/uninstall.sh index 06eaffc..95d0053 100755 --- a/bin/uninstall.sh +++ b/bin/uninstall.sh @@ -38,8 +38,8 @@ scan_applications() { ensure_user_dir "$cache_dir" if [[ $force_rescan == false && -f "$cache_file" ]]; then - local cache_age=$(($(date +%s) - $(get_file_mtime "$cache_file"))) - [[ $cache_age -eq $(date +%s) ]] && cache_age=86401 # Handle mtime read failure + local cache_age=$(($(get_epoch_seconds) - $(get_file_mtime "$cache_file"))) + [[ $cache_age -eq $(get_epoch_seconds) ]] && cache_age=86401 # Handle mtime read failure if [[ $cache_age -lt $cache_ttl ]]; then if [[ -t 2 ]]; then echo -e "${GREEN}Loading from cache...${NC}" >&2 @@ -60,7 +60,7 @@ scan_applications() { temp_file=$(create_temp_file) local current_epoch - current_epoch=$(date "+%s") + current_epoch=$(get_epoch_seconds) # Pass 1: collect app paths and bundle IDs (no mdls). local -a app_data_tuples=() @@ -377,8 +377,8 @@ main() { local needs_scanning=true local cache_file="$HOME/.cache/mole/app_scan_cache" if [[ $force_rescan == false && -f "$cache_file" ]]; then - local cache_age=$(($(date +%s) - $(get_file_mtime "$cache_file"))) - [[ $cache_age -eq $(date +%s) ]] && cache_age=86401 + local cache_age=$(($(get_epoch_seconds) - $(get_file_mtime "$cache_file"))) + [[ $cache_age -eq $(get_epoch_seconds) ]] && cache_age=86401 [[ $cache_age -lt 86400 ]] && needs_scanning=false fi diff --git a/bin/uninstall_lib.sh b/bin/uninstall_lib.sh index badd927..75e0ad0 100755 --- a/bin/uninstall_lib.sh +++ b/bin/uninstall_lib.sh @@ -79,8 +79,8 @@ scan_applications() { # Check if cache exists and is fresh if [[ $force_rescan == false && -f "$cache_file" ]]; then - local cache_age=$(($(date +%s) - $(get_file_mtime "$cache_file"))) - [[ $cache_age -eq $(date +%s) ]] && cache_age=86401 # Handle missing file + local cache_age=$(($(get_epoch_seconds) - $(get_file_mtime "$cache_file"))) + [[ $cache_age -eq $(get_epoch_seconds) ]] && cache_age=86401 # Handle missing file if [[ $cache_age -lt $cache_ttl ]]; then # Cache hit - return immediately # Show brief flash of cache usage if in interactive mode @@ -107,7 +107,7 @@ scan_applications() { # Pre-cache current epoch to avoid repeated calls local current_epoch - current_epoch=$(date "+%s") + current_epoch=$(get_epoch_seconds) # First pass: quickly collect all valid app paths and bundle IDs (NO mdls calls) local -a app_data_tuples=() @@ -454,8 +454,8 @@ main() { local needs_scanning=true local cache_file="$HOME/.cache/mole/app_scan_cache" if [[ $force_rescan == false && -f "$cache_file" ]]; then - local cache_age=$(($(date +%s) - $(get_file_mtime "$cache_file"))) - [[ $cache_age -eq $(date +%s) ]] && cache_age=86401 # Handle missing file + local cache_age=$(($(get_epoch_seconds) - $(get_file_mtime "$cache_file"))) + [[ $cache_age -eq $(get_epoch_seconds) ]] && cache_age=86401 # Handle missing file [[ $cache_age -lt 86400 ]] && needs_scanning=false fi diff --git a/lib/check/all.sh b/lib/check/all.sh index 8cf60f0..c00c5f5 100644 --- a/lib/check/all.sh +++ b/lib/check/all.sh @@ -200,7 +200,7 @@ is_cache_valid() { return 1 fi - local cache_age=$(($(date +%s) - $(get_file_mtime "$cache_file"))) + local cache_age=$(($(get_epoch_seconds) - $(get_file_mtime "$cache_file"))) [[ $cache_age -lt $ttl ]] } diff --git a/lib/check/health_json.sh b/lib/check/health_json.sh index d62b2f5..adb20d6 100644 --- a/lib/check/health_json.sh +++ b/lib/check/health_json.sh @@ -73,7 +73,8 @@ get_uptime_days() { boot_time=$(echo "$boot_output" | awk -F 'sec = |, usec' '{print $2}' 2> /dev/null || echo "") if [[ -n "$boot_time" && "$boot_time" =~ ^[0-9]+$ ]]; then - local now=$(date +%s 2> /dev/null || echo "0") + local now + now=$(get_epoch_seconds) local uptime_sec=$((now - boot_time)) uptime_days=$(LC_ALL=C awk "BEGIN {printf \"%.1f\", $uptime_sec / 86400}" 2> /dev/null || echo "0") else diff --git a/lib/clean/apps.sh b/lib/clean/apps.sh index 5685e74..57a05e4 100644 --- a/lib/clean/apps.sh +++ b/lib/clean/apps.sh @@ -66,7 +66,8 @@ scan_installed_apps() { local cache_age_seconds=300 # 5 minutes if [[ -f "$cache_file" ]]; then local cache_mtime=$(get_file_mtime "$cache_file") - local current_time=$(date +%s) + local current_time + current_time=$(get_epoch_seconds) local age=$((current_time - cache_mtime)) if [[ $age -lt $cache_age_seconds ]]; then debug_log "Using cached app list (age: ${age}s)" @@ -158,7 +159,8 @@ is_bundle_orphaned() { esac if [[ -e "$directory_path" ]]; then local last_modified_epoch=$(get_file_mtime "$directory_path") - local current_epoch=$(date +%s) + local current_epoch + current_epoch=$(get_epoch_seconds) local days_since_modified=$(((current_epoch - last_modified_epoch) / 86400)) if [[ $days_since_modified -lt ${ORPHAN_AGE_THRESHOLD:-60} ]]; then return 1 diff --git a/lib/clean/brew.sh b/lib/clean/brew.sh index 0c3c593..b30fa7f 100644 --- a/lib/clean/brew.sh +++ b/lib/clean/brew.sh @@ -16,7 +16,7 @@ clean_homebrew() { local last_cleanup last_cleanup=$(cat "$brew_cache_file" 2> /dev/null || echo "0") local current_time - current_time=$(date +%s) + current_time=$(get_epoch_seconds) local time_diff=$((current_time - last_cleanup)) local days_diff=$((time_diff / 86400)) if [[ $days_diff -lt $cache_valid_days ]]; then @@ -112,6 +112,6 @@ clean_homebrew() { # Update cache timestamp when any work succeeded or was intentionally skipped. if [[ "$skip_cleanup" == "true" ]] || [[ "$brew_success" == "true" ]] || [[ "$autoremove_success" == "true" ]]; then ensure_user_file "$brew_cache_file" - date +%s > "$brew_cache_file" + get_epoch_seconds > "$brew_cache_file" fi } diff --git a/lib/clean/project.sh b/lib/clean/project.sh index c5342f3..31e3924 100644 --- a/lib/clean/project.sh +++ b/lib/clean/project.sh @@ -214,7 +214,7 @@ is_safe_project_artifact() { fi # Must not be a direct child of the search root. local relative_path="${path#"$search_path"/}" - local depth=$(echo "$relative_path" | tr -cd '/' | wc -c) + local depth=$(echo "$relative_path" | LC_ALL=C tr -cd '/' | wc -c) if [[ $depth -lt 1 ]]; then return 1 fi @@ -398,7 +398,8 @@ is_recently_modified() { fi local mod_time mod_time=$(get_file_mtime "$path") - local current_time=$(date +%s) + local current_time + current_time=$(get_epoch_seconds) local age_seconds=$((current_time - mod_time)) local age_in_days=$((age_seconds / 86400)) if [[ $age_in_days -lt $age_days ]]; then diff --git a/lib/clean/system.sh b/lib/clean/system.sh index e6c55eb..fe78075 100644 --- a/lib/clean/system.sh +++ b/lib/clean/system.sh @@ -40,7 +40,7 @@ clean_deep_system() { fi if [[ -d "/macOS Install Data" ]]; then local mtime=$(get_file_mtime "/macOS Install Data") - local age_days=$((($(date +%s) - mtime) / 86400)) + local age_days=$((($(get_epoch_seconds) - mtime) / 86400)) debug_log "Found macOS Install Data (age: ${age_days} days)" if [[ $age_days -ge 30 ]]; then local size_kb=$(get_path_size_kb "/macOS Install Data") @@ -58,17 +58,23 @@ clean_deep_system() { start_section_spinner "Scanning system caches..." local code_sign_cleaned=0 local found_count=0 - local last_update_time=$(date +%s) + local last_update_time + last_update_time=$(get_epoch_seconds) local update_interval=2 while IFS= read -r -d '' cache_dir; do if safe_remove "$cache_dir" true; then ((code_sign_cleaned++)) fi ((found_count++)) - local current_time=$(date +%s) - if [[ $((current_time - last_update_time)) -ge $update_interval ]]; then - start_section_spinner "Scanning system caches... ($found_count found)" - last_update_time=$current_time + + # Optimize: only check time every 50 files + if ((found_count % 50 == 0)); then + local current_time + current_time=$(get_epoch_seconds) + if [[ $((current_time - last_update_time)) -ge $update_interval ]]; then + start_section_spinner "Scanning system caches... ($found_count found)" + last_update_time=$current_time + fi fi done < <(run_with_timeout 5 command find /private/var/folders -type d -name "*.code_sign_clone" -path "*/X/*" -print0 2> /dev/null || true) stop_section_spinner @@ -155,7 +161,8 @@ clean_time_machine_failed_backups() { [[ -d "$inprogress_file" ]] || continue # Only delete old incomplete backups (safety window). local file_mtime=$(get_file_mtime "$inprogress_file") - local current_time=$(date +%s) + local current_time + current_time=$(get_epoch_seconds) local hours_old=$(((current_time - file_mtime) / 3600)) if [[ $hours_old -lt $MOLE_TM_BACKUP_SAFE_HOURS ]]; then continue @@ -200,7 +207,8 @@ clean_time_machine_failed_backups() { while IFS= read -r inprogress_file; do [[ -d "$inprogress_file" ]] || continue local file_mtime=$(get_file_mtime "$inprogress_file") - local current_time=$(date +%s) + local current_time + current_time=$(get_epoch_seconds) local hours_old=$(((current_time - file_mtime) / 3600)) if [[ $hours_old -lt $MOLE_TM_BACKUP_SAFE_HOURS ]]; then continue diff --git a/lib/clean/user.sh b/lib/clean/user.sh index 5f7a096..fde24f9 100644 --- a/lib/clean/user.sh +++ b/lib/clean/user.sh @@ -485,7 +485,7 @@ process_container_cache() { if is_critical_system_component "$bundle_id"; then return 0 fi - if should_protect_data "$bundle_id" || should_protect_data "$(echo "$bundle_id" | tr '[:upper:]' '[:lower:]')"; then + if should_protect_data "$bundle_id" || should_protect_data "$(echo "$bundle_id" | LC_ALL=C tr '[:upper:]' '[:lower:]')"; then return 0 fi local cache_dir="$container_dir/Data/Library/Caches" @@ -583,7 +583,7 @@ clean_application_support_logs() { for app_dir in ~/Library/Application\ Support/*; do [[ -d "$app_dir" ]] || continue local app_name=$(basename "$app_dir") - local app_name_lower=$(echo "$app_name" | tr '[:upper:]' '[:lower:]') + local app_name_lower=$(echo "$app_name" | LC_ALL=C tr '[:upper:]' '[:lower:]') local is_protected=false if should_protect_data "$app_name"; then is_protected=true diff --git a/lib/core/app_protection.sh b/lib/core/app_protection.sh index da64e63..c92ff55 100755 --- a/lib/core/app_protection.sh +++ b/lib/core/app_protection.sh @@ -425,7 +425,7 @@ is_critical_system_component() { [[ -z "$token" ]] && return 1 local lower - lower=$(echo "$token" | tr '[:upper:]' '[:lower:]') + lower=$(echo "$token" | LC_ALL=C tr '[:upper:]' '[:lower:]') case "$lower" in *backgroundtaskmanagement* | *loginitems* | *systempreferences* | *systemsettings* | *settings* | *preferences* | *controlcenter* | *biometrickit* | *sfl* | *tcc*) @@ -489,7 +489,7 @@ should_protect_path() { [[ -z "$path" ]] && return 1 local path_lower - path_lower=$(echo "$path" | tr '[:upper:]' '[:lower:]') + path_lower=$(echo "$path" | LC_ALL=C tr '[:upper:]' '[:lower:]') # 1. Keyword-based matching for system components # Protect System Settings, Preferences, Control Center, and related XPC services diff --git a/lib/core/base.sh b/lib/core/base.sh index 2c216a7..ab39a66 100644 --- a/lib/core/base.sh +++ b/lib/core/base.sh @@ -108,8 +108,30 @@ get_file_mtime() { return } local result - result=$($STAT_BSD -f%m "$file" 2> /dev/null) - echo "${result:-0}" + result=$($STAT_BSD -f%m "$file" 2> /dev/null || echo "") + if [[ "$result" =~ ^[0-9]+$ ]]; then + echo "$result" + else + echo "0" + fi +} + +# Determine date command once +if [[ -x /bin/date ]]; then + _DATE_CMD="/bin/date" +else + _DATE_CMD="date" +fi + +# Get current time in epoch seconds (defensive against locale/aliases) +get_epoch_seconds() { + local result + result=$($_DATE_CMD +%s 2> /dev/null || echo "") + if [[ "$result" =~ ^[0-9]+$ ]]; then + echo "$result" + else + echo "0" + fi } # Get file owner username @@ -635,11 +657,13 @@ update_progress_if_needed() { local interval="${4:-2}" # Default: update every 2 seconds # Get current time - local current_time=$(date +%s) + local current_time + current_time=$(get_epoch_seconds) # Get last update time from variable local last_time eval "last_time=\${$last_update_var:-0}" + [[ "$last_time" =~ ^[0-9]+$ ]] || last_time=0 # Check if enough time has elapsed if [[ $((current_time - last_time)) -ge $interval ]]; then diff --git a/lib/optimize/tasks.sh b/lib/optimize/tasks.sh index 6349c33..3675562 100644 --- a/lib/optimize/tasks.sh +++ b/lib/optimize/tasks.sh @@ -590,9 +590,9 @@ opt_spotlight_index_optimize() { local slow_count=0 local test_start test_end test_duration for _ in 1 2; do - test_start=$(date +%s) + test_start=$(get_epoch_seconds) mdfind "kMDItemFSName == 'Applications'" > /dev/null 2>&1 || true - test_end=$(date +%s) + test_end=$(get_epoch_seconds) test_duration=$((test_end - test_start)) if [[ $test_duration -gt 3 ]]; then ((slow_count++)) diff --git a/mole b/mole index e3a3d5d..0585066 100755 --- a/mole +++ b/mole @@ -179,7 +179,7 @@ show_version() { local sip_status if command -v csrutil > /dev/null; then sip_status=$(csrutil status 2> /dev/null | grep -o "enabled\|disabled" || echo "Unknown") - sip_status="$(tr '[:lower:]' '[:upper:]' <<< "${sip_status:0:1}")${sip_status:1}" + sip_status="$(LC_ALL=C tr '[:lower:]' '[:upper:]' <<< "${sip_status:0:1}")${sip_status:1}" else sip_status="Unknown" fi @@ -613,7 +613,7 @@ interactive_main_menu() { local flag_file local cache_dir="$HOME/.cache/mole" ensure_user_dir "$cache_dir" - flag_file="$cache_dir/intro_$(echo "$tty_name" | tr -c '[:alnum:]_' '_')" + flag_file="$cache_dir/intro_$(echo "$tty_name" | LC_ALL=C tr -c '[:alnum:]_' '_')" if [[ ! -f "$flag_file" ]]; then animate_mole_intro ensure_user_file "$flag_file" diff --git a/scripts/setup-quick-launchers.sh b/scripts/setup-quick-launchers.sh index 13bfa97..11b8784 100755 --- a/scripts/setup-quick-launchers.sh +++ b/scripts/setup-quick-launchers.sh @@ -302,7 +302,7 @@ create_alfred_workflow() { for entry in "${workflows[@]}"; do IFS="|" read -r bundle name keyword subtitle command <<< "$entry" - local workflow_uid="user.workflow.$(uuid | tr '[:upper:]' '[:lower:]')" + local workflow_uid="user.workflow.$(uuid | LC_ALL=C tr '[:upper:]' '[:lower:]')" local input_uid local action_uid input_uid="$(uuid)"