#!/system/bin/sh # ============================================================ # SpeedBackup - Android 備份恢復腳本 # By @YAWAsau # ============================================================ # # 模組化結構 — 函數定義已移至 tools/core/ # core/config_mgmt.sh - 配置檔生成 # core/base_utils.sh - 基礎工具函數 # core/app_utils.sh - 應用/進程/權限管理 # core/backup_core.sh - 備份核心邏輯 # core/restore_core.sh - 恢復核心邏輯 # core/updater.sh - 更新/列表/WiFi/設備 # # 本文件為執行入口,包含: # L21-32 全局變量與初始檢查 # L33-43 模組載入 # L44-77 配置檔加載與語言設定 # L78-217 環境設置 (PATH/busybox/SHA/TMPDIR) # L218-292 硬件信息/語言檢測 # L293-369 用戶檢測與環境別名 # L370-498 更新檢查流程 # L499-510 後台應用檢測 # L511-599 入口分發 (菜單/直接執行) # ============================================================ if [ "$(whoami)" != root ]; then echo "你是憨批?不給Root用你媽 爬" exit 1 fi [[ -d /data/cache ]] && set -x 2> /data/cache/debug_output.log shell_language="zh-TW" MODDIR_NAME="${MODDIR##*/}" tools_path="$MODDIR/tools" script="${0##*/}" backup_version="202508162209" [[ $SHELL = *mt* ]] && echo "請勿使用MT管理器拓展包環境執行,請更換系統環境" && exit 2 # ======================== Section 2: 配置檔生成與加載 ======================== # --- Load core modules --- core_path="$MODDIR/tools/core" [[ ! -d $core_path ]] && core_path="${0%/*}/core" . "$core_path/config_mgmt.sh" . "$core_path/base_utils.sh" . "$core_path/app_utils.sh" . "$core_path/backup_core.sh" . "$core_path/restore_core.sh" . "$core_path/updater.sh" # --- End module loading --- if [[ ! -d $tools_path ]]; then tools_path="${MODDIR%/*}/tools" [[ ! -d $tools_path ]] && echo "$tools_path二進制目錄遺失" && EXIT="true" fi if [[ ! -f $conf_path ]]; then if [[ $conf_path != *restore_settings.conf && $conf_path = *backup_settings.conf ]]; then update_backup_settings_conf>"$conf_path" else if [[ $conf_path = *restore_settings.conf && $conf_path != *backup_settings.conf ]]; then update_Restore_settings_conf>"$conf_path" else echo "$conf_path配置遺失" && exit 1 fi fi echo "因腳本找不到\n$conf_path\n故重新生成默認列表\n請重新配置後重新執行腳本" && exit 0 fi [[ ! -f $conf_path ]] && echo "$conf_path遺失" && exit 2 . "$conf_path" &>/dev/null # 加載後立即更新配置(補充新選項) if [[ $conf_path != *restore_settings.conf && $conf_path = *backup_settings.conf ]]; then update_backup_settings_conf>"$conf_path" else if [[ $conf_path = *restore_settings.conf && $conf_path != *backup_settings.conf ]]; then update_Restore_settings_conf>"$conf_path" else echo "$conf_path配置遺失" && exit 1 fi fi case $Shell_LANG in 1) LANG="CN" ;; 0) LANG="TW" ;; *) LANG="${LANG:="$(getprop "persist.sys.locale")"}" ;; esac # ======================== Section 3: 基礎工具函數 ======================== rgb_a="${rgb_a:=214}" abi="$(getprop ro.product.cpu.abi)" case $abi in arm64*) if [[ $(getprop ro.build.version.sdk) -lt 26 ]]; then echoRgb "設備Android $(getprop ro.build.version.release)版本過低 請升級至Android 8+" "0" exit 1 else case $(getprop ro.build.version.sdk) in 26|27|28) echoRgb "設備Android $(getprop ro.build.version.release)版本偏低,無法確定腳本能正確的使用" "0" ;; esac fi ;; *) echoRgb "未知的架構: $abi" "0" exit 1 ;; esac get_mv="$(which mv)" PATH="/system/bin:/system/xbin:/data/adb/ksu/bin:/sbin/.magisk/busybox:/sbin/.magisk:/sbin:/system_ext/bin:/vendor/bin:/vendor/xbin:/data/data/com.omarea.vtools/files/toolkit:/data/user/0/com.termux/files/usr/bin" if [[ -d $(magisk --path 2>/dev/null) ]]; then PATH="$(magisk --path 2>/dev/null)/.magisk/busybox:$PATH" else [[ $(ksud -V 2>/dev/null) = "" ]] && echo "Magisk busybox Path does not exist" fi export PATH="$PATH" filepath="/data/backup_tools" busybox="$filepath/busybox" busybox2="$tools_path/busybox" #排除自身 exclude=" update soc.json classes.dex Device_List" if [[ ! -d $filepath ]]; then mkdir -p "$filepath" [[ $? = 0 ]] && echoRgb "設置busybox環境中" fi #刪除無效軟連結 find -L "$filepath" -maxdepth 1 -type l -exec rm -rf {} \; if [[ -f $busybox && -f $busybox2 ]]; then filesha256="$(sha256sum "$busybox" | cut -d" " -f1)" filesha256_1="$(sha256sum "$busybox2" | cut -d" " -f1)" if [[ $filesha256 != $filesha256_1 ]]; then echoRgb "busybox sha256不一致 重新創立環境中" rm -rf "$filepath"/* fi fi find "$tools_path" -maxdepth 1 ! -path "$tools_path/tools.sh" -type f | egrep -v "$(echo $exclude | sed 's/ /\|/g')" | while read; do File_name="${REPLY##*/}" if [[ ! -f $filepath/$File_name ]]; then cp -r "$REPLY" "$filepath" chmod 0755 "$filepath/$File_name" echoRgb "$File_name > $filepath/$File_name" else filesha256="$(sha256sum "$filepath/$File_name" | cut -d" " -f1)" filesha256_1="$(sha256sum "$tools_path/$File_name" | cut -d" " -f1)" if [[ $filesha256 != $filesha256_1 ]]; then echoRgb "$File_name sha256不一致 重新創建" cp -r "$REPLY" "$filepath" chmod 0755 "$filepath/$File_name" echoRgb "$File_name > $filepath/$File_name" fi fi done if [[ -f $busybox ]]; then "$busybox" --list | while read; do if [[ $REPLY != tar && $REPLY != bc && ! -f $filepath/$REPLY ]]; then ln -fs "$busybox" "$filepath/$REPLY" fi done fi [[ ! -f $filepath/zstd ]] && echoRgb "$filepath缺少zstd" && exit 2 export PATH="$filepath:$PATH" export TZ=Asia/Taipei ln -fs "$tools_path/classes.dex" "$filepath/classes.dex" export CLASSPATH="$filepath/classes.dex" quit=0 while read -r file expected_hash; do if [[ -f $tools_path/$file ]]; then computed_hash="$(sha256sum "$tools_path/$file" | awk '{print $1}')" if [[ $computed_hash = $expected_hash ]]; then echoRgb "✅ $file: 驗證通過" else echoRgb "❌ $tools_path/$file: SHA-256 不一致\n -\"$computed_hash\"" quit=2 break fi else echoRgb "⚠️ 檔案 $tools_path/$file 不存在" quit=1 break fi done <<< "$(cat </dev/null) != "" ]]; then UFS_MODEL="$(cat "/sys/class/block/sda/device/inquiry")" else UFS_MODEL="unknown" fi fi [[ $(egrep -w "$(getprop ro.product.model 2>/dev/null)" "$tools_path/Device_List" | awk -F'"' '{print $4}') != "" ]] && Device_name="$(egrep -w "$(getprop ro.product.model 2>/dev/null)" "$tools_path/Device_List" | awk -F'"' '{print $4}' | head -1)" || Device_name="$(getprop ro.product.model 2>/dev/null)" if [[ $(su -v 2>/dev/null) != "" ]]; then Manager_version="$(su -v 2>/dev/null)" [[ $Manager_version = *KernelSU* ]] && ksu="ksu" [[ $ksu = "" ]] && [[ -d /data/adb/ksu ]] && ksu="ksu" else if [[ -d /data/adb/ksu ]]; then Manager_version=KernelSU ksu="ksu" fi fi Socname="$(getprop ro.soc.model)" if [[ $Socname != "" ]]; then if [[ -f $tools_path/soc.json ]]; then jq -r --arg device "$Socname" '.[$device] | "處理器:\(.VENDOR) \(.NAME)"' "$tools_path/soc.json" &>/dev/null if [[ $? = 0 ]]; then DEVICE_NAME="$(jq -r --arg device "$Socname" '.[$device] | "處理器:\(.VENDOR) \(.NAME)"' "$tools_path/soc.json" 2>/dev/null)" jq -r --arg device "$Socname" '.[$device] | "RAM:\(.MEMORY) \(.CHANNELS)"' "$tools_path/soc.json" &>/dev/null if [[ $? = 0 ]]; then RAMINFO="$(jq -r --arg device "$Socname" '.[$device] | "RAM:\(.MEMORY) \(.CHANNELS)"' "$tools_path/soc.json" 2>/dev/null)" else RAMINFO="RAM:null" fi else DEVICE_NAME="處理器:null" RAMINFO="RAM:null" fi else DEVICE_NAME="處理器:null" RAMINFO="RAM:null" fi else DEVICE_NAME="處理器:null" RAMINFO="RAM:null" fi echoRgb "---------------------SpeedBackup---------------------" echoRgb "腳本路徑:$MODDIR\n -已開機:$(Show_boottime)\n -執行時間:$(date +"%Y-%m-%d %H:%M:%S")\n -busybox路徑:$(which busybox)\n -busybox版本:$(busybox | head -1 | cut -d' ' -f2)\n -腳本版本:$backup_version\n -管理器:$Manager_version\n -品牌:$(getprop ro.product.brand 2>/dev/null)\n -型號:$Device_name($(getprop ro.product.device 2>/dev/null))\n -閃存顆粒:$UFS_MODEL($ROM_TYPE)\n -$DEVICE_NAME\n -$RAMINFO\n -Android版本:$(getprop ro.build.version.release 2>/dev/null) SDK:$(getprop ro.build.version.sdk 2>/dev/null)\n -內核:$(uname -r)\n -Selinux狀態:$([[ $(getenforce) = Permissive ]] && echo "寬容" || echo "嚴格")\n -By@YAWAsau\n -Support: https://jq.qq.com/?_wv=1027&k=f5clPNC3" case $MODDIR in *Backup_*) if [[ -f $MODDIR/app_details.json ]]; then if [[ -d ${MODDIR%/*/*}/tools ]]; then path_hierarchy="${MODDIR%/*/*}" else path_hierarchy="${MODDIR%/*}" fi else if [[ -d ${MODDIR%/*}/tools ]]; then path_hierarchy="${MODDIR%/*}" else [[ -d $MODDIR/tools ]] && path_hierarchy="$MODDIR" fi fi ;; *) [[ -d $MODDIR/tools ]] && path_hierarchy="$MODDIR" ;; esac [[ $LANG = "" ]] && echoRgb "系統無參數語言獲取失敗\n -如果需要更改腳本語言請於$conf_path\n -Shell_LANG=填入對應數字" "0" case $LANG in *TW* | *tw* | *HK*) Script_target_language="zh-TW" ;; *CN* | *cn*) Script_target_language="zh-CN" ;; esac echoRgb "$Script_target_language腳本" # ======================== Section 4: 用戶檢測與環境別名 ======================== if [[ ! -f ${0%/*}/app_details.json ]]; then if [[ $user = "" ]]; then user_id="$(ls /data/user | tr ' ' '\n')" if [[ $user_id != "" && $(ls /data/user | tr ' ' '\n' | wc -l) -gt 1 ]]; then echo "$user_id" | while read ; do [[ $REPLY = 0 ]] && echoRgb "主用戶:$REPLY" "2" || echoRgb "分身用戶:$REPLY" "2" done echoRgb "設備存在多用戶,選擇操作目標用戶" if [[ $(echo "$user_id" | wc -l) = 2 ]]; then user1="$(echo "$user_id" | sed -n '1p')" user2="$(echo "$user_id" | sed -n '2p')" case $Lo in 0|1) echoRgb "音量上選擇用戶:$user1,音量下選擇用戶:$user2" "2" Select_user="true" get_version "$user1" "$user2" && user="$branch" unset Select_user ;; 2) Enter_options "輸入1選擇用戶:$user1 0用戶:$user2" "$user1" "$user2" case $parameter in 0) user="$user2" ;; 1) user="$user1" ;; esac ;; esac else while true ;do if [[ $option != "" ]]; then user="$option" break else echoRgb "請輸入需要操作目標分區" "1" read option fi done fi else user="0" fi else user_id="$(ls /data/user | tr ' ' '\n')" if [[ $user_id != "" && $(ls /data/user | tr ' ' '\n' | wc -l) -gt 1 ]]; then echo "$user_id" | while read ; do [[ $REPLY = 0 ]] && echoRgb "主用戶:$REPLY" "2" || echoRgb "分身用戶:$REPLY" "2" done else echoRgb "主用戶:$user_id" "2" fi fi else case $(echo "${0%}") in *zstd*) user="$(echo "${0%}" | sed 's/.*\/Backup_zstd_\([0-9]*\).*/\1/')" ;; *tar*) user="$(echo "${0%}" | sed 's/.*\/Backup_tar_\([0-9]*\).*/\1/')" ;; *) echoRgb "請勿修改備份資料夾名稱,保持原本的Backup_壓縮算法名稱_使用者id" "0" && exit 2 ;; esac fi [[ $user != 0 ]] && am start-user "$user" path="/data/media/$user/Android" path2="/data/user/$user" path3="/data/user_de/$user" [[ ! -d $path2 ]] && echoRgb "$user分區不存在,請將上方提示的用戶id按照需求填入\n -$conf_path配置項user=,一次只能填寫一個" "0" && exit 2 echoRgb "當前操作為用戶$user" export USER_ID="$user" unset LD_LIBRARY_PATH #因接收USER_ID環境變量問題故將函數放在此處 alias appinfo="app_process /system/bin com.xayah.dex.HiddenApiUtil getInstalledPackagesAsUser $USER_ID $@" alias appinfo2="app_process /system/bin com.xayah.dex.HiddenApiUtil getPackageLabel $USER_ID $@" alias appinfo3="app_process /system/bin com.xayah.dex.HiddenApiUtil getPackageArchiveInfo $@" alias get_ssaid="app_process /system/bin com.xayah.dex.SsaidUtil get $USER_ID $@" alias set_ssaid="app_process /system/bin com.xayah.dex.SsaidUtil set $USER_ID $@" alias get_uid="app_process /system/bin com.xayah.dex.HiddenApiUtil getPackageUid $USER_ID $@" alias get_Permissions="app_process /system/bin com.xayah.dex.HiddenApiUtil getRuntimePermissions $USER_ID $@" alias Set_true_Permissions="app_process /system/bin com.xayah.dex.HiddenApiUtil grantRuntimePermission $USER_ID $@" alias Set_false_Permissions="app_process /system/bin com.xayah.dex.HiddenApiUtil revokeRuntimePermission $USER_ID $@" alias Set_Ops="app_process /system/bin com.xayah.dex.HiddenApiUtil setOpsMode $USER_ID $@" alias setDisplay="app_process /system/bin com.xayah.dex.HiddenApiUtil setDisplayPowerMode $@" find_tools_path="$(find "$path_hierarchy"/* -maxdepth 1 -name "tools" -type d ! -path "$path_hierarchy/tools")" # ======================== Section 5: WiFi/腳本生成/重命名工具 ======================== # ======================== Section 6: 腳本更新邏輯 ======================== update_script zipFile="$(ls -t /storage/emulated/0/Download/*.zip 2>/dev/null | head -1)" if [[ $(unzip -l "$zipFile" 2>/dev/null | awk '{print $4}' | egrep -wo "^backup_settings.conf$") != "" ]]; then update_script else zipFile="$(ls -t /storage/emulated/0/Android/data/com.tencent.mobileqq/Tencent/QQfile_recv/*.zip 2>/dev/null | head -1)" [[ $(unzip -l "$zipFile" 2>/dev/null | awk '{print $4}' | egrep -wo "^backup_settings.conf$") != "" ]] && update_script fi SDK_V="$(getprop ro.build.version.sdk)" if [[ $SDK_V -lt 30 ]]; then alias INSTALL="pm install --user $user -r -t >/dev/null" alias create="pm install-create --user $user -tl" elif [[ $SDK_V -gt 33 ]]; then alias INSTALL="pm install -r --bypass-low-target-sdk-block -i com.android.vending --user $user -t >/dev/null" alias create="pm install-create -i com.android.vending --bypass-low-target-sdk-block --user $user -t" else alias INSTALL="pm install -r -i com.android.vending --user $user -t >/dev/null" alias create="pm install-create -i com.android.vending --user $user -t" fi #settings get system system_locales Language="https://api.github.com/repos/YAWAsau/backup_script/releases/latest" if [[ $path_hierarchy != "" && $Script_target_language != "" ]]; then K=1 J="$(find "$path_hierarchy" -maxdepth 3 -name "tools.sh" -type f | wc -l)" find "$path_hierarchy" -maxdepth 3 -name "tools.sh" -type f | while read ; do unset shell_language shell_language="$(awk -F= '/^shell_language=/ {gsub(/"/, "", $2); print $2}' "$REPLY")" case $shell_language in zh-CN|zh-TW) if [[ $Script_target_language != $shell_language ]]; then [[ $K = 1 ]] && echoRgb "腳本語言為$shell_language....轉換為$Script_target_language中,請稍後等待轉換...." ts <"$REPLY">temp && cp temp "$REPLY" && rm temp if [[ $? = 0 ]]; then touch "$TMPDIR/0" echo_log "$(echo "$REPLY" | sed "s|^$path_hierarchy/||")翻譯" MODDIR="${0%/*}" if [[ -f ${REPLY%/*/*}/backup_settings.conf ]]; then update_backup_settings_conf>"${REPLY%/*/*}/backup_settings.conf" ts <"${REPLY%/*/*}/backup_settings.conf">temp && cp temp "${REPLY%/*/*}/backup_settings.conf" && rm temp echo_log "${REPLY%/*/*}/backup_settings.conf翻譯" fi if [[ -f ${REPLY%/*/*}/restore_settings.conf ]]; then update_Restore_settings_conf>"${REPLY%/*/*}/restore_settings.conf" ts <"${REPLY%/*/*}/restore_settings.conf">temp && cp temp "${REPLY%/*/*}/restore_settings.conf" && rm temp echo_log "${REPLY%/*/*}/restore_settings.conf翻譯" fi sed "s/shell_language=\"$shell_language\"/shell_language=\"$Script_target_language\"/g" "$REPLY" > temp && cp temp "$REPLY" && rm temp [[ $shell_language != $(awk -F= '/^shell_language=/ {gsub(/"/, "", $2); print $2}' "$REPLY") ]] && echoRgb "$(echo "$REPLY" | sed "s|^$path_hierarchy/||")變量修改成功" || echoRgb "$(echo "$REPLY" | sed "s|^$path_hierarchy/||")變量修改失敗" "0" ts <"${REPLY%/*}/Device_List">temp && cp temp "${REPLY%/*}/Device_List" && rm temp echo_log "${REPLY%/*}/Device_List翻譯" [[ $K = 1 ]] && Rename_script else echoRgb "$REPLY ts進程出現錯誤" "0" fi K=$((K + 1)) fi ;; esac done [[ -e $TMPDIR/0 ]] && rm "$TMPDIR/0" && echoRgb "轉換腳本完成,退出腳本重新執行即可使用" && exit 2 fi #校驗選填是否正確 case $Lo in 0) [[ $update != "" ]] && isBoolean "$update" "update" && update="$nsx" || { echoRgb "自動更新腳本?\n -音量上更新,下不更新" get_version "更新" "不更新" && update="$branch" } ;; 1) [[ $update = "" ]] && { echoRgb "自動更新腳本?\n -音量上更新,下不更新" get_version "更新" "不更新" && update="$branch" } || isBoolean "$update" "update" && update="$nsx" ;; 2) [[ $update = "" ]] && { Enter_options "輸入1自動更新腳本,輸入0不自動更新腳本" "更新" "不更新" && isBoolean "$parameter" "update" && update="$nsx" } || { isBoolean "$update" "update" && update="$nsx" } ;; *) echoRgb "$conf_path Lo=$Lo填寫錯誤,正確值0 1 2" "0" && exit 2 ;; esac if [[ $update = true ]]; then json="$(down "$Language")" else echoRgb "自動更新被關閉" "0" fi if [[ $json != "" ]]; then tag="$(jq -r '.tag_name'<<< "$json" 2>/dev/null)" if [[ $tag != "" && $backup_version != $tag ]]; then if [[ $(expr "$(echo "$backup_version" | tr -d "a-zA-Z")" \> "$(echo "$tag" | tr -d "a-zA-Z")") -eq 0 ]]; then download="$(jq -r '.assets[].browser_download_url'<<< "$json")" case $cdn in 0) zip_url="$download" ;; 1) zip_url="https://ghfast.top/$download" ;; 2) zip_url="https://shrill-pond-3e81.hunsh.workers.dev/$download" ;; *) echoRgb "$conf_path cdn=設置錯誤 範圍只能是0-2" && exit 2 ;; esac if [[ $(expr "$(echo "$backup_version" | tr -d "a-zA-Z")" \> "$(echo "$download" | tr -d "a-zA-Z")") -eq 0 ]]; then echoRgb "發現新版本:$tag" if [[ $update = true ]]; then echoRgb "$(ts "更新日誌:\n$(down "$Language" | jq -r '.body')")" case $Lo in 0|1) echoRgb "是否更新腳本?\n -音量上更新,音量下不更新" "2" get_version "更新" "不更新" && choose="$branch" ;; 2) Enter_options "輸入1自動更新腳本,輸入0不自動更新腳本" "更新" "不更新" && isBoolean "$parameter" "update" && update="$nsx" ;; esac if [[ $choose = true ]]; then echoRgb "下載中.....耐心等待 如果下載失敗請掛飛機" starttime1="$(date -u "+%s")" down "$zip_url" >"$MODDIR/update.zip" & wait endtime 1 [[ ! -f $MODDIR/update.zip ]] && echoRgb "下載失敗" && exit 2 zipFile="$MODDIR/update.zip" fi else echoRgb "$conf_path內update選項為0忽略更新僅提示更新" "0" fi fi fi fi else [[ $update = true ]] && echoRgb "更新獲取失敗" "0" fi update_script # ======================== Section 7: 備份路徑/分區/APK/SSAID/權限 ======================== # ======================== Section 8: 數據備份與恢復核心 ======================== # ======================== Section 9: 應用列表管理與進程檢測 ======================== Background_application_list debug pkgs="$(pm list packages --user "$user" | cut -f2 -d ':' | awk -v pkg="$(echo "$Backstage" | head -1)" '$1 == pkg {print $1}')" if [[ $pkgs != "" ]]; then echoRgb "後台應用獲取成功($pkgs)" "1" [[ $(Process_Information "$pkgs") = "" ]] && echoRgb "應用pid獲取失敗" "0" || echoRgb "應用pid獲取成功$(Process_Information "$pkgs")" "1" else echoRgb "後台應用獲取失敗" "0" activity=false fi unset Backstage # ======================== Section 10: 備份主流程 ======================== # ======================== Section 11: 恢復主流程 ======================== # ======================== Section 12: 應用列表生成 ======================== # ======================== Section 13: 入口分發 ======================== if [[ $0 = *backup.sh ]]; then start=backup else [[ $0 = *recover.sh ]] && start=Restore fi if [[ $start != "" ]]; then case $(grep -o 'background_execution=.*' "$conf_path" | awk -F '=' '{print $2}') in 0) eval "$start" ;; 1) { eval "$start" } & ;; esac else if [[ -f $MODDIR/backup_settings.conf ]]; then steps=( "生成應用列表" "備份應用" "備份已更新應用" "備份自定義資料夾" "備份WiFi" "殺死運行中腳本" ) commands=( "Getlist" "backup" "backup_update_apk" "backup_media" "wifi" "echoRgb '等待腳本停止中,請稍後.....' && echoRgb '腳本終止'; exit" ) elif [[ -f $MODDIR/restore_settings.conf ]]; then steps=( "重新生成應用列表" "恢復備份" "僅恢復包含ssaid應用(含數據)" "僅恢復包含ssaid應用(不含數據)" "恢復自定義資料夾" "恢復wifi" "壓縮檔完整性檢查" "轉換文件夾名稱" "殺死運行中腳本" ) commands=( "dumpname" "Restore" "ssaid_mode=true && Restore" "ssaid_mode_1=true && Restore4" "Restore3" "recover_wifi \"$MODDIR/wifi\"" "check_file" "convert" "echoRgb '等待腳本停止中,請稍後.....' && echoRgb '腳本終止'; exit" ) fi echoRgb "請選擇要執行的操作:" for i in "${!steps[@]}"; do printf "%d) %s\n" "$((i+1))" "${steps[$i]}" done echo "x) 離開腳本" echo -n "請輸入選項編號: " read choice case $choice in x|X) echoRgb "已退出腳本" "0" exit 0 ;; [1-9]) if (( choice >= 1 && choice <= ${#steps[@]} )); then index=$((choice - 1)) echo "執行:${steps[$index]}" background=$(grep -o 'background_execution=.*' "$conf_path" | awk -F '=' '{print $2}') if [[ "$background" = "1" ]]; then eval "${commands[$index]}" & else eval "${commands[$index]}" fi else echoRgb "超出功能選項範圍(1-${#steps[@]})" "0" fi ;; *) echoRgb "輸入錯誤,請重新輸入有效的數字或輸入 x 離開。" "0" ;; esac fi