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"
拆分的意义在于:
- 并行开发:远程、备份、恢复、环境各模块可由不同贡献者同时维护
- 按需加载:从原单体的 3000+ 行中按功能隔离,减少 source 时的无关开销
- 模块间解耦:每个模块有明确的职责边界,通过
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 | 需要逐级创建目录;稳定可靠 |
| FTP | curl -T --ftp-create-dirs | 自动创建目录,被动模式 |
| SMB | smbclient batch 脚本 | 支持 SMB2/3;内置 11.7MB 独立二进制 |
| SCP | scp / 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_STATUS、does not exist、ERR 等错误模式)。
FTP 修复历程(9 次提交的迭代)
FTP 从第一次实现到稳定经历了这样的试错过程:
- 系统 curl → DNS 不可靠(root 环境下
/system/bin/curlDNS 解析偶尔失败) busybox ftpput→ Android 的 toybox 实现不支持 FTP- 内置 curl → 内置的 6MB 静态编译 curl DNS 有问题
--retry-all-errors→ 加入 curl 重试应对间歇性 DNS 失败- DNS 预解析 → 提取域名 IP,用
--resolve绕过 DNS - IP 直连 → root DNS 仍然不可靠,改为解析后直接 IP 访问
/system/bin/curl优先 → 系统 curl DNS 最可靠,回退到内置 curl- 被动模式 + 超时 30s → FTP 需要 PASV 模式,并加长超时
- 退出码判断 → FTP 用 curl 退出码判断成功,WebDAV 用 HTTP 状态码
每一步修复都在版本历史中留下记录(c556575 → 828625c → 00be4b9 → ... → 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 解析和缓存:
- 先检查 IP 格式(已经是 IP 则直接返回)
- 检查
$TMPDIR/.dns_cache缓存文件(通过 awk 快速查询) - 尝试
nslookup查询,过滤掉 127.x 的本地回环地址 - 回退到
ping -c 1 -W 1提取 IP - 成功后将结果写入缓存文件供后续复用
这个 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.sh、restore_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 的 toyboxdf不支持-B1,改用busybox df -B1(b7fda5f)- 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"
四种更新方式:
- ZIP 放置更新 — zip 包放脚本目录,下次运行自动识别
- 联网自动更新 — 通过
classes.dex的 HttpUtil 检查 GitHub API - Download 目录 —
/storage/emulated/0/Download/自动检测 - 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 四种协议,并补全了上传/下载/列表/清理的完整工作流。
相关链接
- GitHub:https://github.com/YAWAsau/backup_script
- Telegram:https://t.me/yawasau_script
- 酷安:@落叶凄凉TEL