backup_script:一款 Android 数据备份 Shell 脚本的八年进化史

作者:Administrator 发布时间: 2026-05-17 阅读量:4 评论数:0

2026 年 5 月 · 开源项目 · Android 工具

一、项目简介

backup_script 是一款面向 已 Root 的 Android 设备 的完整数据备份与恢复 Shell 脚本,由台湾开发者 YAWAsau 创建并维护。与市面上常见的备份工具(钛备份、Swift Backup)不同,它完全运行在 Shell 环境中,不需要图形界面,也不需要 Java 运行时。

项目地址:https://github.com/YAWAsau/backup_script

截至 2026 年 5 月,项目累计 296 次提交14 个发布版本(使用时间标签如 202605161105),在台湾和大陆的 Android 发烧友社区中有相当规模的用户群,同时拥有 GitHub Issues、Telegram 频道和 QQ 群三个反馈渠道。

二、核心功能

脚本的核心能力可以概括为:把一台 Android 设备上的所有应用及其数据完整迁移到另一台设备,并且做到"换机无需重新登录"。

具体功能包括:

功能说明
应用数据完整备份包括应用私有数据、OBB 数据包、SSAID、运行时权限
SSAID 备份与恢复可完美处理 LINE 等依赖设备识别码的应用
Split APK 支持备份和恢复 Split APK(已拆分安装包)
OBB 数据包可选备份外部 OBB 数据(如原神、王者荣耀等几十 GB 的大型游戏)
WiFi 设定备份备份和恢复无线网络配置
自定义目录备份可备份 DCIM、Download、Music 等任意目录
多种压缩算法支持 tar(仅打包)与 zstd(高压缩比+高速)
增量备份比对上次备份文件大小,无变化则跳过
远程备份支持 WebDAV / FTP / SMB / SCP 四种协议自动上传
完整性校验内置 SHA-256 校验与压缩包完整性验证
黑白名单系统灵活控制哪些应用备份或不备份

系统要求:Android 8+、arm64 架构、Root 权限(Magisk / KernelSU)。

三、技术架构

极简的部署模型

backup_script 不需要安装——解压后直接执行 start.sh 即可。入口脚本只有 19 行:

#!/system/bin/sh
MODDIR="${0%/*}"
conf_path="${0%/*}/backup_settings.conf"
mkdir -p "${0%/*}/log"
logfile="${0%/*}/log/log_$(date +%Y-%m-%d_%H-%M).txt"
. "${0%/*}/tools/tools.sh" | tee "$logfile"
sed -i $'s/\033\[[0-9;]*m//g' "$logfile"

它只是 source 了 tools/tools.sh 核心脚本,并将所有输出同时写入带时间戳的日志文件。

模块化重构(2026年5月)

这是最近一次合并提交(1fb2ec9)的核心变更——将 3048 行的单体 tools.sh 拆分为 6 个独立模块

tools/
├── tools.sh                  # 29 行加载器(仅含 source 语句)
└── modules/
    ├── env.sh       (243行)  # 环境检测、变量初始化、配置生成
    ├── core.sh      (695行)  # 工具函数集、busybox 部署、SHA-256 校验
    ├── remote.sh   (1425行)  # 远程备份(WebDAV/FTP/SMB/SCP)
    ├── backup.sh   (1286行)  # 备份逻辑
    ├── restore.sh   (392行)  # 恢复逻辑
    └── menu.sh      (377行)  # 用户交互与主菜单

重构后的 tools.sh 退化为一个纯加载器:

. "${0%/*}/tools/modules/env.sh"
. "${0%}/../modules/core.sh"
kill_Serve                 # 进程锁
. "${0%/*}/tools/modules/remote.sh"
. "${0%/*}/tools/modules/backup.sh"
. "${0%/*}/tools/modules/restore.sh"
trap "rm -rf '/data/.backup_lock'; remote_cleanup" EXIT
. "${0%/*}/tools/modules/menu.sh"

拆分的意义在于:

  1. 并行开发:远程、备份、恢复、环境各模块可由不同贡献者同时维护
  2. 按需加载:从原单体的 3000+ 行中按功能隔离,减少 source 时的无关开销
  3. 模块间解耦:每个模块有明确的职责边界,通过 env.sh 中定义的全局变量通信

统计对比:单体删除 3039 行,新增模块文件 4484 行——净增行数来自远程模块(1425 行)的大量新增功能。

内置二进制工具链

脚本在 tools/ 目录下完整自带了所有需要的静态编译 ARM64 二进制:

curl (2.9MB)     — 远程传输(WebDAV/FTP)
smbclient (11.7MB) — SMB 协议客户端
ssh / scp        — SCP 传输
zstd (1.2MB)     — zstd 压缩
tar (1.1MB)      — tar 打包
busybox (1.6MB)  — 核心工具集
jq (1.7MB)       — JSON 解析(用于 GitHub API 更新检查)
find             — 文件搜索
keycheck         — 音量键监听
classes.dex (2.7MB) — Java 功能扩展
bc               — 数学计算

核心模块 core.sh 的前 80 行全是 busybox 部署逻辑:先检测 $filepath/data/backup_tools),逐文件比对 SHA-256 校验,发现不一致或缺失则从 tools/ 复制。检测通过后,通过 busybox --list 为每个支持的命令创建软链接。core.sh 硬编码了所有关键二进制文件的期望 SHA-256:

while read -r file expected_hash; do
    computed_hash="$(sha256sum "$tools_path/$file" | awk '{print $1}')"
    if [[ $computed_hash = $expected_hash ]]; then
        echoRgb "✅ $file: 驗證通過"
    else
        echoRgb "❌ $tools_path/$file: SHA-256 不一致"
        quit=2; break
    fi
done <<EOF
zstd 9ef4b54148699c9874cfd45aaf38e5cc950e5d168afdcf2edf58a2463f5561ed
tar  882639ac310a7eb4052c68c21cea02633307700f9cc8c7c469c2dd18d734a112
...
EOF

classes.dex:Shell 做不到的,交给 Java

Android 的 Shell 环境能力有限——特别是涉及到与 Java 框架交互的功能。脚本通过 app_process 调用内置的 classes.dex,实现了:

  • SSAID 读取与写入——通过 Android API 操作 Settings.Secure.ANDROID_ID
  • 运行时权限管理——读取和恢复 Runtime Permission 和 AppOps
  • GitHub API 交互——版本检查与自动下载(通过 com.xayah.dex.HttpUtil
  • 繁简中文翻译——com.xayah.dex.CCUtil 实时转换

env.sh 中通过别名封装:

alias down="app_process /system/bin com.xayah.dex.HttpUtil get $@"
case $LANG in
*CN*|*cn*) alias ts="app_process /system/bin com.xayah.dex.CCUtil t2s $@" ;;
*)         alias ts="app_process /system/bin com.xayah.dex.CCUtil s2t $@" ;;
esac

四、远程备份系统深度解析

远程备份是项目投入最大、调试次数最多的功能。从第一次提交到稳定,经历了 3 个分支、50+ 次提交 的反复打磨。

支持的协议

协议实现方式特点
WebDAV (推荐)curl -T + MKCOL需要逐级创建目录;稳定可靠
FTPcurl -T --ftp-create-dirs自动创建目录,被动模式
SMBsmbclient batch 脚本支持 SMB2/3;内置 11.7MB 独立二进制
SCPscp / sshpass优先密码认证,回退密钥认证

WebDAV:需要逐级创建目录

WebDAV 的 MKCOL(Make Collection)命令只能创建一级目录。脚本通过 url_encode_path 将远程路径编码后,逐段拆分并依次 MKCOL:

while read -r d; do
    local enc_d="$(url_encode_path "$d")"
    local cur="$base_url"
    local IFS='/'
    set -- $enc_d
    for seg; do
        cur="$cur/$seg"
        curl -sS -L --http1.1 -X MKCOL \
            -u "$remote_user:$remote_pass" "$cur" 2>/dev/null
    done
done

上传文件时自动注入重试和超时:

http_code="$(curl -sS -L --http1.1 --retry 2 --retry-delay 3 \
    --connect-timeout 10 \
    -T "$f" -u "$remote_user:$remote_pass" \
    -w '%{http_code}' -o "$TMPDIR/.curl_err" "$target_url" 2>&1)"

成功条件:HTTP 2xx(WebDAV)、226/250(FTP)。

SMB:从 curl 到 smbclient 的重大迁移

最初的 SMB 支持使用 curl smb:// 协议。但 curl 的 SMB 实现仅支持 SMB1/CIFS——这个协议版本因安全原因在现代 Windows Server 上默认关闭。

迁移到 smbclient(Samba 项目)后:

  • 支持 SMB2/3 协议
  • 使用 batch 脚本模式批量上传(避免每个文件一次连接)
  • 上传前先 cd 到远程目录,再通过 put 依次上传
local batch="$TMPDIR/.smb_batch"
echo "cd $rem_dir" > "$batch"
echo "lcd $local_dir" >> "$batch"
while read -r f; do
    echo "put $(basename "$f")" >> "$batch"
done < "$gf"
echo "exit" >> "$batch"
smbclient "$share" -U "$remote_user%$remote_pass" < "$batch" 2>&1

同时通过 upload_summary 函数解析 smbclient 输出,逐文件判断成功/失败(含 NT_STATUSdoes not existERR 等错误模式)。

FTP 修复历程(9 次提交的迭代)

FTP 从第一次实现到稳定经历了这样的试错过程:

  1. 系统 curl → DNS 不可靠(root 环境下 /system/bin/curl DNS 解析偶尔失败)
  2. busybox ftpput → Android 的 toybox 实现不支持 FTP
  3. 内置 curl → 内置的 6MB 静态编译 curl DNS 有问题
  4. --retry-all-errors → 加入 curl 重试应对间歇性 DNS 失败
  5. DNS 预解析 → 提取域名 IP,用 --resolve 绕过 DNS
  6. IP 直连 → root DNS 仍然不可靠,改为解析后直接 IP 访问
  7. /system/bin/curl 优先 → 系统 curl DNS 最可靠,回退到内置 curl
  8. 被动模式 + 超时 30s → FTP 需要 PASV 模式,并加长超时
  9. 退出码判断 → FTP 用 curl 退出码判断成功,WebDAV 用 HTTP 状态码

每一步修复都在版本历史中留下记录(c556575828625c00be4b9 → ... → 4cf1e61),展示了 Android 环境下 Shell 开发的典型调试模式。

DNS 穿透 wrapper:最巧妙的设计

最终解决方案是一个自定义的 curl() 函数,透明地拦截所有 curl 调用,自动进行 DNS 解析并注入 --resolve 参数

curl() {
    local extra_resolve="" _host _port _ip
    for _arg in "$@"; do
        case $_arg in
        http://*|https://*|ftp://*)
            _rest="${_arg#*://}"
            _hp="${_rest%%/*}"
            _host="${_hp%%:*}"
            _port="${_hp#*:}"
            # 默认端口推断
            [[ $_port = $_hp ]] && { case $_arg in
                http://*)  _port=80 ;; https://*) _port=443 ;; ftp://*) _port=21 ;; esac; }
            _ip=$(_dns_resolve "$_host")
            [[ -n $_ip && $_ip != "$_host" ]] && extra_resolve="$extra_resolve --resolve $_host:$_port:$_ip"
            ;;
        esac
    done
    if [[ -n $extra_resolve ]]; then
        command curl $extra_resolve "$@"
    else
        command curl "$@"
    fi
}

辅助函数 _dns_resolve 实现了多层次 DNS 解析和缓存:

  1. 先检查 IP 格式(已经是 IP 则直接返回)
  2. 检查 $TMPDIR/.dns_cache 缓存文件(通过 awk 快速查询)
  3. 尝试 nslookup 查询,过滤掉 127.x 的本地回环地址
  4. 回退到 ping -c 1 -W 1 提取 IP
  5. 成功后将结果写入缓存文件供后续复用

这个 wrapper 对所有 curl 调用透明生效,不需要修改任何已有上传代码。

上传清单精确化管理

remote_collect_targets 函数只收集本次备份中实际产生的文件:

while read -r f; do
    local d="${f#$Backup/}"
    d="${d%/*}"
    [[ -n $d && $d != "${f#$Backup/}" ]] && echo "$d"
done < "$list_file" | sort -u

对比早期版本简单地上传整个 Backup 目录,这种精确范围收集避免了重复上传和无关文件。

v5.1 新增:上传清单现在额外包含 tools/start.shrestore_settings.conf,确保远程目录可以独立还原,不再依赖原始脚本包。

上传成功后的本地文件清理策略

# remote_keep_local=true → 永远保留本地
# 否则:必须全部成功才删除所有已上传文件
if [[ $remote_keep_local != true ]]; then
    if [[ $fail_count -eq 0 && $ok_count -gt 0 ]]; then
        rm -f "$f"  # 全部成功,删除本地
    elif [[ $fail_count -gt 0 ]]; then
        # 部分失败,保留全部(含已成功上传的)
    fi
fi

预连接测试

上传前先测试服务器是否可达,避免每个文件上传都卡死在连接超时:

remote_precheck() {
    local host="$1" port="$2"
    if command -v nc >/dev/null 2>&1; then
        nc -z -w 3 "$host" "$port" >/dev/null 2>&1 && return 0
    fi
    # fallback: bash /dev/tcp
    timeout 3 sh -c "echo > /dev/tcp/$host/$port" >/dev/null 2>&1 && return 0
    return 1
}

使用 nc 或 bash 的 /dev/tcp 虚拟设备在 3 秒内判断端口连通性。

五、Android 特有的工程挑战

noexec 分区

脚本存储在 /sdcard 时,由于 Android 安全策略 noexec 挂载选项,二进制文件无法直接在 SD 卡上执行:

if [[ $(echo "$Backup" | grep -Eo "^/storage/emulated") != "" ]]; then
    Backup_path="/data"
fi

core.sh 中有一个完整的部署流程:检测文件 SHA-256 → 复制到 /data/backup_tools/ → 设置 0777 权限 → 通过 busybox --list 创建所有命令的软链接 → 最后 export PATH="$filepath:$PATH"

busybox 碎片化

Android 设备的 busybox 来源极多:Magisk 内置、KernelSU 内置、搞机助手、Scene、用户自行安装——不同版本的 busybox 行为不一。脚本通过自带的 tools/busybox(1.6MB 静态编译)统一所有环境:

if [[ $(which busybox) = "" ]]; then
    echoRgb "環境變量中沒有找到busybox 請在tools內添加一個"
    exit 1
fi

Shell 兼容性

Android 默认的 /system/bin/sh 是 mksh(MirBSD Korn Shell)或 toybox 的 sh——不是 bash。所有代码必须兼容 POSIX shell:

  • partition_info 的数值比较:[[ ]] 在 mksh 中行为不同,改用 awk 数值比较(7752481
  • df 命令:Android 的 toybox df 不支持 -B1,改用 busybox df -B1b7fda5f
  • CRLF 换行符:.sh / .conf 文件必须强制 LF,禁用 git autocrlf(b00deb7

音量键交互

在没有键盘的移动设备上,使用 tools/keycheck 监听音量键按键事件:

Lo=0  # 音量键选择
Lo=1  # 音量键选择(强制)
Lo=2  # 键盘输入

配合 get_version 函数实现选择逻辑:

get_version "選擇了隨身碟備份" "選擇了本地備份"

六、更新与分发机制

脚本的自动更新通过 GitHub Releases + CDN 实现:

Language="https://api.github.com/repos/YAWAsau/backup_script/releases/latest"

四种更新方式:

  1. ZIP 放置更新 — zip 包放脚本目录,下次运行自动识别
  2. 联网自动更新 — 通过 classes.dex 的 HttpUtil 检查 GitHub API
  3. Download 目录/storage/emulated/0/Download/ 自动检测
  4. QQ 群下载 — 直接使用群文件

支持 CDN 节点加速:

cdn=1  # https://ghfast.top(适合中国大陆用户)
cdn=2  # Cloudflare Workers

七、近几次合入的技术汇总

最近一次合入(1fb2ec9,2026-05-16)包含了以下变更:

模块化重构

  • 拆分 3048 行单体 tools.sh → 6 模块,tools.sh 降至 29 行纯加载器
  • 每个模块有独立职责边界,通过 env.sh 全局变量通信

远程备份补全

  • upload_current_backup — 一键上传当前备份目录
  • single_upload — 单文件上传
  • remote_list_backups — 列出远程备份
  • remote_download_backup — 从远程下载恢复
  • 统一 --http1.1 到所有 WebDAV curl 命令(7 处)

SMB 迁移

  • curl smb://(仅 SMB1)切换到 smbclient(SMB2/3)
  • 内置 11.7MB smbclient 二进制
  • batch 脚本模式批量上传

DNS 修复链(从 fork 分支 fix/remote-dns-curl 合入)

  • DNS 预解析 → 移除(HTTPS 证书不匹配)→ 重新加入 → --resolve 最终方案
  • DNS 缓存文件 $TMPDIR/.dns_cache,通过 awk 索引避免 O(n)
  • 多层回退:系统 curl → 内置 curl → DNS 预解析 → IP 直连

二进制优化

  • curl 从 6.6MB 精简到 2.9MB(重新静态编译,去除不需要的协议)
  • 新增 smbclient(11.7MB)、ssh(703KB)

其他

  • 修复 partition_info 在 mksh 下的兼容性(改用 awk)
  • 所有 WebDAV MKCOL 增加重试、超时、HTTP 状态码诊断输出
  • SCP 支持双 URL 格式,移除不安全变量展开

八、总结

backup_script 是 Android 发烧友社区中一个"小而美"的典型——它不做华丽的事情,只是用最朴素的方式把一个痛点解决到极致。

它的开发历史展示了 Android Shell 编程特有的挑战:DNS 解析在 root 环境下不可靠、noexec 分区的二进制部署、busybox 碎片化、mksh 兼容性、curl 版本差异——每一个看似简单的功能在 Android 的碎片化环境中都变成了一长串的修复迭代。

但也是这种极端的环境,催生了一些巧妙的设计:透明 DNS 穿透 wrapper、SHA-256 校验的二进制部署、无依赖的 app_process Java 扩展、音量键交互系统。这些技术方案不仅在 backup_script 中有用,对任何 Android Shell 开发者都有参考价值。

最新版本(202605161105)的发布标志着项目从单体架构正式迈入模块化时代。远程备份功能经历了三个分支、50+ 次迭代后趋于稳定,覆盖 WebDAV/FTP/SMB/SCP 四种协议,并补全了上传/下载/列表/清理的完整工作流。


相关链接

评论