ADB批量文件操作:批处理脚本实现自动化文件推送与拉取

ADB批量文件操作:批处理脚本实现自动化文件推送与拉取
1. 为什么需要批量adb文件操作在移动应用开发、测试或者设备维护的日常工作中我们经常需要与Android设备交换文件。比如测试同学需要从测试机上拉取几百张截图进行缺陷分析开发同学需要将一批新的资源文件推送到设备上进行功能验证或者运维同学需要批量备份设备上的日志文件。如果每次都手动输入adb push或adb pull命令不仅效率低下而且极易出错文件名输错一个字母就可能导致操作失败。adbAndroid Debug Bridge命令行工具本身是单次操作的。adb push local remote推送一个文件adb pull remote local拉取一个文件。当文件数量上升到几十、上百个时这种重复劳动就变得难以忍受。这时一个简单的批处理脚本.bat文件就能解放我们的双手实现自动化、批量化操作。它本质上是对Windows命令行能力的封装将一系列adb命令按逻辑组合起来一键执行。对于经常需要与多台设备、大量文件打交道的工程师来说掌握这项技能是提升工作效率的必备手段。2. 批处理脚本的核心构建思路批处理脚本的威力在于其“批量”和“逻辑控制”能力。要实现文件的批量推送或拉取核心思路无非两种基于文件列表和基于通配符匹配。每种方法都有其适用的场景和需要注意的细节。2.1 方法一遍历文件列表最稳妥这是最经典、控制粒度最细的方法。它的逻辑非常清晰先准备好一份需要操作的文件清单然后让脚本逐行读取这个清单并对每一行指定的文件执行adb命令。假设我们有一个名为file_list.txt的文本文件里面每一行都是一个文件的路径可以是相对路径或绝对路径例如sdcard/DCIM/Camera/IMG_20231001_123456.jpg sdcard/Download/report.pdf data/local/tmp/config.json对应的批处理脚本核心部分如下echo off setlocal enabledelayedexpansion REM 设置设备序列号多设备时需指定 set DEVICE_SERIALemulator-5554 REM 设置本地存储目录 set LOCAL_DIRD:\Backup\ REM 设置文件列表路径 set FILE_LISTfile_list.txt for /f usebackq delims %%i in (%FILE_LIST%) do ( echo 正在拉取: %%i adb -s %DEVICE_SERIAL% pull %%i %LOCAL_DIR% if !errorlevel! equ 0 ( echo [成功] %%i ) else ( echo [失败] %%i ) ) pause关键点解析echo off关闭命令回显让脚本运行过程更清爽只显示我们echo的内容。setlocal enabledelayedexpansion启用延迟环境变量扩展。这是批处理中处理循环体内变量如!errorlevel!的关键否则无法正确获取每条命令执行后的状态。for /f “usebackq delims” %%i in (“%FILE_LIST%”)这是批处理的文件读取循环。usebackq允许路径包含空格时使用引号delims表示不以任何字符作为分隔符整行读取%%i是每行内容的变量。adb -s %DEVICE_SERIAL% pull …通过-s参数指定操作设备在多设备连接时至关重要。if !errorlevel! equ 0errorlevel是上一条命令的退出代码0通常表示成功。这里用!包裹是延迟扩展的语法用于判断每条adb pull是否成功并给出相应提示。这种方法优势明显精准控制可以拉取分散在不同目录的文件脚本有明确的成功/失败反馈文件列表本身可以作为操作记录存档。缺点在于需要额外维护一个文件列表。2.2 方法二使用通配符匹配较便捷对于同一目录下的一批具有共同特征的文件如所有.log日志文件、所有.jpg图片使用通配符*会更方便。但这里有一个非常重要的坑在批处理脚本中直接对adb pull使用通配符可能会因为路径扩展问题而失败。错误示范adb pull /sdcard/*.jpg .在复杂的脚本循环中这样的命令可能无法按预期工作。正确做法是先在本地用adb shell配合find或ls命令生成文件列表然后再用方法一来处理。或者使用for循环直接解析adb shell ls的输出。下面是一个拉取某个目录下所有.mp4文件的例子echo off setlocal enabledelayedexpansion set DEVICE_SERIALemulator-5554 set REMOTE_DIR/sdcard/Movies/ set LOCAL_DIRD:\Videos\ REM 注意adb shell ls 的路径要用引号括起来防止空格 for /f “delims” %%i in (‘adb -s %DEVICE_SERIAL% shell ls “%REMOTE_DIR%*.mp4” 2^nul’) do ( set “remote_file%REMOTE_DIR%%%i” echo 正在拉取: !remote_file! adb -s %DEVICE_SERIAL% pull “!remote_file!” “%LOCAL_DIR%” ) pause关键点解析adb -s %DEVICE_SERIAL% shell ls “%REMOTE_DIR%*.mp4”在设备上执行ls命令列出指定目录下所有.mp4文件。2^nul是将错误输出重定向到空设备避免因目录不存在等错误干扰列表。for /f … in (‘command’)这里不是读取文件而是执行反引号内的命令并对其输出进行逐行解析。set “remote_file%REMOTE_DIR%%%i”将设备目录和文件名拼接成完整的设备端路径。注意变量拼接的语法。注意这种方法依赖于adb shell ls的输出格式。如果文件名包含空格或特殊字符可能会被错误地分割。因此对于文件名复杂的情况方法一文件列表的鲁棒性要高得多。3. 实战一个健壮的批量拉取脚本详解结合上面的思路我们来编写一个功能更完整、容错性更强的批量拉取脚本。这个脚本将包含设备选择、日志记录、错误重试等实用功能。echo off chcp 65001 nul setlocal enabledelayedexpansion title ADB批量文件拉取工具 REM 用户配置区域 REM 1. 指定设备序列号如果只有一台设备可以留空或使用 adb devices 列表中的第一个 set TARGET_DEVICE REM 2. 指定要拉取的远程设备目录 set REMOTE_PATH/sdcard/DCIM/Camera/ REM 3. 指定本地保存目录 set LOCAL_ROOTD:\PhoneBackup\ REM 4. 指定文件列表如果使用列表方式。留空则使用通配符方式拉取REMOTE_PATH下所有文件 set FILE_LIST REM 5. 最大重试次数 set MAX_RETRY2 REM REM 自动创建本地保存目录 if not exist “%LOCAL_ROOT%” mkdir “%LOCAL_ROOT%” set LOG_FILE%LOCAL_ROOT%pull_%date:~0,4%%date:~5,2%%date:~8,2%_%time:~0,2%%time:~3,2%%time:~6,2%.log echo 操作开始时间: %date% %time% “%LOG_FILE%” echo “%LOG_FILE%” REM 设备检测与选择逻辑 echo 正在检测连接的ADB设备… adb devices | findstr /r “device$” nul if %errorlevel% neq 0 ( echo [错误] 未找到已授权的ADB设备请检查设备连接和USB调试。 pause exit /b 1 ) set DEVICE_CMDadb if defined TARGET_DEVICE ( set DEVICE_CMDadb -s %TARGET_DEVICE% echo 已指定设备: %TARGET_DEVICE% ) else ( for /f “tokens1” %%d in (‘adb devices ^| findstr /r “device$”’) do ( set “DEVICE_CMDadb -s %%d” echo 使用设备: %%d goto :device_selected ) ) :device_selected REM 核心拉取逻辑 set /a SUCCESS_COUNT0 set /a FAIL_COUNT0 if defined FILE_LIST ( echo [模式] 使用文件列表模式: %FILE_LIST% if not exist “%FILE_LIST%” ( echo [错误] 文件列表不存在: %FILE_LIST% goto :summary ) for /f “usebackq delims” %%f in (“%FILE_LIST%”) do ( call :pull_single_file “%%f” ) ) else ( echo [模式] 使用目录通配符模式: %REMOTE_PATH%* REM 获取文件列表处理带空格的文件名 for /f “tokens*” %%f in (‘%DEVICE_CMD% shell ls “%REMOTE_PATH%” 2^nul’) do ( call :pull_single_file “%REMOTE_PATH%%%f” ) ) goto :summary REM ———— 子程序拉取单个文件包含重试机制 ———— :pull_single_file set “remote_file%~1” set “local_file%LOCAL_ROOT%%~nx1” set /a RETRY0 :retry_loop echo [尝试拉取] !remote_file! “%LOG_FILE%” %DEVICE_CMD% pull “!remote_file!” “!local_file!” nul 21 if !errorlevel! equ 0 ( echo [成功] !remote_file! echo [成功] !remote_file! “%LOG_FILE%” set /a SUCCESS_COUNT1 goto :eof ) else ( set /a RETRY1 if !RETRY! leq %MAX_RETRY% ( echo [重试!RETRY!/%MAX_RETRY%] !remote_file! timeout /t 1 /nobreak nul goto :retry_loop ) else ( echo [失败] !remote_file! echo [失败] !remote_file! “%LOG_FILE%” set /a FAIL_COUNT1 ) ) goto :eof REM ———— 汇总与日志 ———— :summary echo “%LOG_FILE%” echo 操作结束时间: %date% %time% “%LOG_FILE%” echo 总计成功: %SUCCESS_COUNT% 个文件 “%LOG_FILE%” echo 总计失败: %FAIL_COUNT% 个文件 “%LOG_FILE%” echo echo 操作完成 echo 成功: %SUCCESS_COUNT% 个文件 echo 失败: %FAIL_COUNT% 个文件 echo 详细日志见: %LOG_FILE% pause脚本亮点与经验解析编码与标题chcp 65001将控制台切换到UTF-8编码防止中文路径或文件名乱码。title设置窗口标题方便识别。设备自动选择脚本优先使用用户配置的TARGET_DEVICE。如果未指定则自动选取adb devices列表中第一台状态为device的设备。这是处理多设备环境的实用技巧。双模式支持通过判断FILE_LIST变量是否定义脚本自动切换“文件列表模式”和“目录通配符模式”灵活性更高。健壮的文件名处理在通配符模式中for /f “tokens*”确保了即使文件名以空格开头也能被完整捕获。子程序中使用%~1和%~nx1来去除参数引号并提取文件名处理路径更加规范。重试机制网络波动或设备暂时无响应可能导致单次pull失败。pull_single_file子程序提供了简单的重试逻辑MAX_RETRY可配置提高了批量操作的容错率。完整的日志记录所有操作包括每次尝试和最终结果都实时写入以时间戳命名的日志文件.log。这对于事后排查问题、确认操作结果至关重要。清晰的进度与结果反馈控制台实时显示成功/失败信息最后给出统计摘要用户体验良好。4. 从拉取到推送脚本的逆向改造与高级用法掌握了批量拉取批量推送就是举一反三。核心逻辑几乎一致只是将adb pull替换为adb push并注意源和目的路径对调。一个简单的批量推送脚本框架如下echo off setlocal enabledelayedexpansion set LOCAL_SOURCE_DIRC:\MyAppAssets\ set REMOTE_TARGET_DIR/sdcard/AppData/ set FILE_LISTassets_to_push.txt for /f “usebackq delims” %%i in (“%FILE_LIST%”) do ( set “local_file%LOCAL_SOURCE_DIR%%%i” set “remote_file%REMOTE_TARGET_DIR%%%i” echo 正在推送: !local_file! adb push “!local_file!” “!remote_file!” ) pause推送操作的特殊注意事项目标目录权限确保设备上的目标目录如/sdcard/下的子目录具有写权限。系统分区如/system/通常需要root权限才能写入。文件覆盖adb push默认会覆盖已存在的文件。如果希望避免覆盖可以在推送前通过adb shell ls检查文件是否存在。推送整个目录adb push支持推送整个文件夹。例如adb push my_local_folder /sdcard/会将my_local_folder及其内部所有文件递归地推送到设备的/sdcard/目录下。在批量脚本中这可以简化对大量同级文件的推送。高级用法条件化批量操作真正的生产力工具往往需要一点“智能”。我们可以让脚本根据文件属性如修改时间、大小或名称特征来决定是否操作。场景只拉取设备上最近24小时内修改过的日志文件。echo off setlocal enabledelayedexpansion REM 获取24小时前的时间戳Unix时间戳秒级 REM 注意此方法需要设备shell支持date命令的‘%s’格式部分定制系统可能不支持 for /f %%t in (‘adb shell “date -d ‘-1 day’ %%s 2nul || echo 0”’) do set cutoff_time%%t for /f “tokens1,2*” %%a in (‘adb shell “find /sdcard/ -name ‘*.log’ -type f -newermt !cutoff_time! 2/dev/null || echo”’) do ( REM %%c 是文件名但find输出可能包含空格这里简化处理 echo 拉取新日志: %%c adb pull “%%c” .\NewLogs\ ) pause这个脚本利用了adb shell执行设备端的find命令通过-newermt参数过滤出修改时间在特定时间之后的文件。这比拉取全部文件后再在本地筛选要高效得多尤其适合处理大量文件。需要注意的是设备shell环境下的命令支持程度因系统而异这类脚本需要针对目标设备进行测试和调整。5. 常见踩坑点与排查指南即使脚本写得再完善在实际执行中也可能遇到各种问题。下面是一些我亲身踩过的坑和对应的排查思路。5.1 错误“device not found” 或 “more than one device/emulator”问题现象脚本执行失败提示未找到设备或存在多个设备。根因分析这是最常见的问题。adb无法确定操作目标。可能原因有1) 设备未通过USB调试授权2) 设备离线3) 连接了多台设备但未在命令中指定序列号。排查与解决检查设备连接首先在命令行手动执行adb devices查看设备列表。确保目标设备状态是device而不是offline或unauthorized。处理未授权如果状态是unauthorized需要在设备屏幕上点击“允许USB调试”的授权对话框。指定设备序列号当adb devices列出多台设备时必须在每条adb命令中通过-s 序列号来指定目标。我们的脚本中已经包含了设备选择逻辑请确保TARGET_DEVICE变量设置正确或者让自动选择逻辑正常工作。使用连接标识除了序列号也可以使用adb -t 传输ID来指定通过TCP/IP连接的设备。5.2 错误“Permission denied” 或 “Read-only file system”问题现象adb pull时提示权限不足或adb push时提示只读文件系统。根因分析尝试访问的设备路径当前shell用户权限不足。例如非root用户尝试访问/data/data/下的应用私有目录或尝试向/system分区写入文件。排查与解决确认路径权限在脚本执行前先通过adb shell ls -l 路径查看目标目录或文件的权限属性。使用 run-as针对应用数据对于调试自己开发的应用可以使用adb shell run-as package.name来临时获取该应用的数据目录访问权限然后再执行cp命令将文件复制到可访问的目录如/sdcard/最后再从/sdcard/拉取。获取 root 权限如果设备已root可以在adb shell命令前加上su -c例如adb shell “su -c ‘chmod 666 /data/某个文件’”。但请注意这会使脚本通用性变差且root有风险。选择可写目录对于推送操作最稳妥的目标是/sdcard/、/storage/emulated/0/或应用的外部存储目录。确保脚本中的REMOTE_PATH指向这些位置。5.3 错误文件名含空格或特殊字符导致操作异常问题现象脚本只处理了文件名的一部分或者报错找不到文件。根因分析在批处理和shell环境中空格是默认的命令参数分隔符。如果文件名包含空格如My Document.txt在命令中如果没有用引号正确包裹它会被拆分成My和Document.txt两个参数。排查与解决始终使用引号在脚本中所有涉及文件路径的变量在传递给adb命令时必须用双引号包裹。例如adb pull “!remote_file!” “!local_file!”。我们的示例脚本已经严格遵守了这一点。谨慎处理 for 循环解析在通过adb shell ls获取文件列表时使用for /f “tokens*”或delims来保留整行内容避免因空格被切割。测试极端情况用包含空格、括号、、$等特殊字符的文件名进行测试确保脚本能稳定处理。5.4 错误拉取大量小文件时速度极慢问题现象拉取包含成千上万个小文件如图标、配置文件的目录时进度缓慢远超单个大文件的传输时间。根因分析adb pull每个文件都会建立一次独立的连接和协议开销。对于海量小文件这个开销累积起来就非常可观。排查与解决打包再传输最优解是在设备端先将目录打包。adb shell进入目录使用tar或zip命令打包需设备支持然后拉取单个压缩包最后在本地解压。adb shell “cd /sdcard/my_folder tar -czf /sdcard/my_folder.tar.gz .” adb pull /sdcard/my_folder.tar.gz .使用 adb sync标准的adb没有sync命令。虽然有些资料提到但那通常是adb源码编译工具链里的并非通用客户端所有。不要依赖不存在的命令。考虑第三方工具对于极频繁的批量文件同步需求可以考虑使用基于adb协议开发的图形化工具或更高级的脚本它们可能实现了更高效的传输算法。5.5 脚本在中文路径下执行异常问题现象当本地保存路径LOCAL_ROOT或文件名包含中文时脚本可能报错或拉取的文件名乱码。根因分析Windows命令行和批处理对Unicode尤其是UTF-8的支持有历史遗留问题。虽然我们用了chcp 65001但某些情况下adb命令自身或文件系统API可能仍有兼容性问题。排查与解决坚持使用 chcp 65001脚本开头的chcp 65001 nul是基础。避免深层次中文路径尽量将工作目录和保存目录设置在英文路径下。如果必须用中文路径层次不宜过深。测试与验证在实际使用前用包含中文的文件名和路径进行小规模测试确保整个流程从adb shell ls列出到最终本地保存都能正确保持编码。6. 效率提升超越基础批处理的思路当基本的批量操作脚本已经满足日常需求后我们可以思考如何更进一步让文件管理更加智能和高效。思路一集成到右键菜单将常用的批量拉取脚本比如拉取指定应用日志添加到Windows文件的右键菜单中。这需要通过修改注册表实现创建一个新的右键菜单项其命令指向你的批处理脚本并可以将当前选中的文件或文件夹路径作为参数传递给脚本。这样在设备连接好后直接在文件资源管理器里右键点击一个包含文件列表的文本文件就能触发拉取操作体验无缝衔接。思路二使用Python/ PowerShell 重写批处理.bat功能有限尤其在字符串处理、错误处理和跨平台兼容性上。对于更复杂的逻辑如递归对比本地和远程文件的MD5、仅同步差异文件、更友好的进度条显示使用Python或PowerShell是更好的选择。它们拥有更强大的标准库和社区支持。例如Python的subprocess模块可以很好地调用adb结合os和hashlib库可以轻松实现一个智能同步脚本。思路三与持续集成/持续部署CI/CD流程结合在自动化测试流水线中测试任务结束后自动从测试设备上拉取测试报告、截图、日志等结果文件并归档到指定服务器或触发邮件通知。可以将你的批处理脚本封装成Jenkins Pipeline的一个步骤或者GitLab CI/CD的一个job实现测试产物的自动回收这是提升团队效率的关键一环。思路四制作图形化界面GUI工具使用AutoHotkey、WinForms或简单的HTML界面配合Node.js为你的批处理脚本套上一个图形外壳。通过界面让用户选择设备、浏览本地和远程目录、勾选要操作的文件然后点击按钮执行。这能极大降低脚本的使用门槛让非技术人员如产品经理、设计师也能安全、方便地进行文件交换。从一条简单的adb pull命令到一个健壮的批处理脚本再到思考如何将其工程化、产品化这个过程本身就是自动化思维的体现。核心永远不变将重复、易错的手动操作转化为可靠、可重复的自动化流程。这个脚本可能只是一个开始但它所代表的“批量处理”和“错误处理”思想可以应用到无数其他的命令行工具和日常工作中。