Windows批处理脚本实现ADB文件批量传输:自动化与效率提升实战

发布时间:2026/8/2 2:50:36
Windows批处理脚本实现ADB文件批量传输:自动化与效率提升实战 1. 项目概述为什么我们需要批处理来管理ADB文件传输如果你经常和安卓设备打交道无论是作为开发者进行应用调试还是作为极客玩家管理手机里的文件adb push和adb pull这两个命令绝对是你的老朋友。手动敲命令一次两次还行但当你需要从几十个不同目录拉取日志或者向设备推送一整套资源文件时重复劳动不仅枯燥还极易出错。文件名输错一个字母、路径少打一个斜杠都可能让操作前功尽弃。这时候一个简单的 Windows 批处理.bat脚本就能成为你的效率倍增器。它能把一系列繁琐的adb命令自动化、批量化执行。我最初写这类脚本就是因为被频繁的测试日志收集工作搞得不胜其烦。每次测试完都需要手动从设备的/sdcard/Android/data/[包名]/files/log/这类深目录里把十几个日志文件拉取到本地按日期分类的文件夹中。手动操作不仅慢还经常漏文件。自从用批处理脚本自动化之后整个过程从原来的几分钟压缩到十几秒而且准确率100%。这个项目要解决的就是如何利用 Windows 系统的批处理脚本来批量、可靠、可定制地执行adb push推送文件到设备和adb pull从设备拉取文件到电脑操作。它特别适合以下场景安卓应用开发者需要批量部署测试资源或拉取调试日志手机发烧友需要定期备份或同步特定文件夹测试工程师需要自动化收集测试结果文件。接下来我会从设计思路到具体实现把其中的门道和踩过的坑都详细拆解一遍。2. 脚本核心设计与思路拆解写一个能用的批处理脚本不难但写一个健壮、好用、能应对各种意外情况的脚本就需要一些设计考量了。我们不能仅仅是把命令堆叠在一起而是要思考整个流程的鲁棒性和用户体验。2.1 核心需求与功能定义首先我们要明确这个批处理脚本需要具备哪些核心功能批量操作能够处理一个文件列表或一个目录下的所有文件而不是单个文件。路径灵活性允许用户方便地指定源路径电脑或设备和目标路径最好能通过简单修改脚本开头的变量来实现。状态反馈与日志执行过程中需要清晰地告诉用户当前在做什么成功了还是失败了。最好能生成一个日志文件记录所有操作和可能出现的错误方便事后排查。容错与交互如果设备未连接、文件不存在等情况发生脚本不能直接崩溃退出而应该给出明确的提示并可能提供重试或跳过的选项。可扩展性脚本结构应该清晰方便后续添加新功能比如在传输前后执行额外的adb shell命令例如修改文件权限或者根据日期动态创建目标文件夹。基于这些需求我设计的脚本通常会采用“配置区 函数模块 主流程”的结构。配置区定义路径、开关等参数函数模块封装push、pull、检查设备等独立功能主流程则按顺序调用这些函数并处理逻辑。2.2 为什么选择批处理.bat而不是 PowerShell 或其他这是一个很实际的选择问题。PowerShell 功能无疑更强大但对于adb命令操作这个特定场景批处理有它的独特优势兼容性极佳从 Windows XP 到最新的 Windows 11.bat 脚本无需任何额外环境或执行策略修改双击即可运行。这对于需要在不同测试机、同事电脑上快速部署脚本的情况非常友好。与命令行环境无缝集成adb本身就是一个命令行工具批处理调用它是最自然的方式输出和错误流处理起来很直接。学习与修改成本低批处理的语法相对简单即使是不太熟悉脚本的测试人员或开发者也能看懂并修改脚本中的几个路径变量。这使得脚本的共享和维护成本降低。当然如果你的操作非常复杂涉及复杂的JSON/XML解析、网络请求等那么PowerShell或Python是更好的选择。但对于纯粹的adb文件传输自动化批处理是“够用且好用”的典范。2.3 关键挑战与应对策略在实现过程中有几个挑战需要提前规划设备连接状态管理脚本执行的第一件事必须是检查是否有设备通过adb连接。没有设备一切操作都无从谈起。文件路径中的空格与特殊字符这是批处理脚本的经典“坑”。路径或文件名如果包含空格必须用双引号包裹否则会被拆分成多个参数。我们的脚本必须能妥善处理这类情况。adb命令本身的错误adb push可能会因为设备存储空间不足、权限拒绝而失败adb pull可能会因为文件不存在而失败。脚本需要捕获这些错误并作出合理反应比如记录到日志并继续执行下一个文件而不是整体中断。操作确认与防止误覆盖在pull操作时如果本地已存在同名文件是直接覆盖还是跳过或是询问用户这需要设计一个策略。我的策略是“先检查后执行勤记录慎覆盖”。在核心操作前做好状态检查所有操作和结果都输出到屏幕并写入日志对于文件覆盖默认采用“跳过已存在文件”的保守策略并通过配置项允许用户修改为强制覆盖。3. 脚本核心细节解析与实操要点让我们深入脚本内部看看几个关键部分是如何实现的以及有哪些必须注意的细节。3.1 设备连接检查脚本稳健性的第一道防线一个健壮的脚本必须在开始文件操作前确认设备在线。直接使用adb devices命令并解析其输出是最可靠的方法。echo off REM 检查ADB设备连接 echo [%date% %time%] 正在检查ADB设备连接... adb devices | findstr /R /C:^[0-9a-f].*device$ nul 2nul if %errorlevel% equ 0 ( echo 设备已连接。 set DEVICE_STATUSCONNECTED ) else ( echo 错误未找到已连接的ADB设备。请确保 echo 1. 设备已开启USB调试模式。 echo 2. 电脑已安装正确的ADB驱动。 echo 3. 设备已通过USB线连接或网络ADB已配置并连接。 echo 4. 在命令行中运行 adb devices 能看到设备。 pause exit /b 1 )要点解析与避坑指南adb devices的输出格式一条正常的设备连接行类似于abcdef012345 device。我们使用findstr命令通过正则表达式^[0-9a-f].*device$来匹配以十六进制数字开头、中间任意字符、以“device”结尾的行。这能有效过滤掉头部的“List of devices attached”和尾部的空行以及状态为offline或unauthorized的设备。错误流重定向nul 2nul将命令的标准输出和错误输出都丢弃不显示在屏幕上。我们只关心findstr的执行结果即%errorlevel%。这样界面更干净。明确的错误提示当检查失败时不要只简单说“设备未连接”。应该给出可能的原因和排查步骤如上方的四点提示。这能极大减少使用者的困惑。设置状态变量这里将DEVICE_STATUS设为CONNECTED是为了后续可能的多设备选择逻辑预留扩展性。虽然当前脚本处理的是单设备但良好的习惯是让脚本结构易于扩展。3.2 处理文件路径绕开空格的“陷阱”在批处理中参数以空格分隔。因此任何包含空格的路径如果不加引号都会被拆散。例如adb push C:\My Documents\file.txt /sdcard/会被解析为向adb传递三个参数C:\My、Documents\file.txt和/sdcard/这必然导致失败。黄金法则在脚本中所有传递给外部命令如adb的路径变量都用双引号包裹。set LOCAL_FILEC:\Users\YourName\Documents\My File.txt set DEVICE_PATH/sdcard/Download/ adb push %LOCAL_FILE% %DEVICE_PATH%即使你确信当前路径没有空格这也是一个必须养成的好习惯能避免未来某天突然引入带空格路径时脚本崩溃。在定义路径变量时就直接加引号或者在拼接路径时确保引号完整。3.3 实现批量拉取Pull遍历与判断批量pull的核心是“遍历”。我们需要一个源文件列表然后循环处理每一个。这个列表可以硬编码在脚本里也可以通过读取一个文本文件或者直接遍历设备上的某个目录来动态生成。方案一遍历本地列表文件推荐用于精确控制创建一个filelist.txt每行写一个设备上的文件路径。脚本读取并逐行拉取。set FILE_LISTfilelist.txt set DEVICE_BASE/sdcard/Android/data/com.example.app/files/ set LOCAL_BASED:\Backup\ for /f usebackq delims %%f in (%FILE_LIST%) do ( echo [%date% %time%] 正在拉取: %%f adb pull %DEVICE_BASE%%%f %LOCAL_BASE% if !errorlevel! neq 0 ( echo [警告] 拉取失败: %%f pull_log.txt ) else ( echo [成功] 已拉取: %%f pull_log.txt ) )方案二拉取整个目录如果你想拉取设备上某个文件夹下的所有内容包括子目录adb pull命令本身支持目录操作。set DEVICE_DIR/sdcard/DCIM/Camera/ set LOCAL_DIRE:\PhoneBackup\Camera_%date:~0,4%%date:~5,2%%date:~8,2%\ echo 正在拉取整个目录: %DEVICE_DIR% adb pull %DEVICE_DIR% %LOCAL_DIR%重要注意事项adb pull目录的行为它会将设备上的整个目录结构包括子文件夹原样拉取到本地。如果本地目标文件夹不存在adb会创建它。网络与性能拉取大量小文件时由于每个文件都需要建立一次ADB通信速度可能会比拉取单个大文件慢很多且对USB连接稳定性要求高。如果可能先在设备上打包如用tar再拉取压缩包最后在电脑上解压效率会高得多。错误处理上面的脚本片段中使用了if !errorlevel! neq 0来检查上一个命令adb pull的退出代码。在for循环内部我们需要使用延迟变量扩展!errorlevel!而不是%errorlevel%并在脚本开头加上setlocal enabledelayedexpansion。这是批处理中处理循环内变量动态变化的关键技巧。3.4 实现批量推送Push覆盖策略与权限管理批量push与pull思路类似但有一些独特的点。覆盖策略adb push默认行为是覆盖目标位置已存在的文件且没有提示。这有时是危险的。一个更安全的脚本可以在推送前通过adb shell ls检查目标文件是否存在并询问用户或根据配置决定是否跳过。setlocal enabledelayedexpansion set LOCAL_SOURCEC:\AppAssets\* set DEVICE_TARGET/sdcard/AppAssets/ for %%f in (%LOCAL_SOURCE%) do ( set filename%%~nxf adb shell ls %DEVICE_TARGET%!filename! nul 2nul if !errorlevel! equ 0 ( echo 文件已存在跳过: !filename! echo [跳过] !filename! push_log.txt ) else ( echo 正在推送: !filename! adb push %%f %DEVICE_TARGET% echo [推送] !filename! push_log.txt ) )权限问题推送到设备系统目录如/system/app/或某些数据目录可能需要root权限。通常的adb push可能失败。这时需要分两步adb push到临时目录如/data/local/tmp/。通过adb shell和su如果有root移动文件并设置正确权限。adb push myapp.apk /data/local/tmp/ adb shell su -c mv /data/local/tmp/myapp.apk /system/app/ chmod 644 /system/app/myapp.apk注意修改系统分区需要设备已解锁并获取 root 权限且分区必须是可写的例如通过adb remount。对普通用户和应用数据目录通常不需要这么复杂。4. 完整脚本实操过程与核心环节实现下面我将结合一个功能相对完整的实战脚本来演示如何将上述思路整合在一起。这个脚本实现了检查设备、批量拉取指定文件列表、批量推送一个本地文件夹并附带日志功能。4.1 脚本准备与环境配置在开始编写脚本前请确保你的工作环境已就绪ADB环境确保adb命令可以在命令行中直接运行。将Android SDK的platform-tools目录包含adb.exe添加到系统的PATH环境变量中。验证方法打开新的命令提示符CMD输入adb version能看到版本信息即表示成功。设备连接用USB线连接安卓设备并在设备上开启“开发者选项”中的“USB调试”模式。在电脑命令行运行adb devices应能看到你的设备序列号状态为device。脚本编辑器使用任何文本编辑器如 Notepad、VS Code甚至系统自带的记事本。关键是要以ANSI或UTF-8 without BOM编码保存文件否则中文字符可能显示为乱码。文件扩展名必须为.bat。4.2 完整脚本代码与逐段解析创建一个新文件命名为adb_file_manager.bat将以下代码复制进去。我会在代码中以REM注释的形式进行详细解析。echo off REM adb_file_manager.bat - 批量ADB文件推送/拉取管理脚本 REM 作者你的名字 REM 版本1.0 REM 功能检查设备批量拉取文件列表批量推送本地文件夹记录完整日志。 setlocal enabledelayedexpansion REM 启用延迟环境变量扩展这是在循环内部正确读取动态变量如errorlevel的关键。 REM 用户配置区域 REM 请根据你的实际情况修改以下变量 REM 1. 设备上待拉取的文件列表每行一个完整路径 set FILELIST_TO_PULLfiles_to_pull.txt REM 2. 设备上拉取文件的基准目录与列表中的路径拼接 set DEVICE_BASE_PATH/sdcard/ REM 3. 拉取文件到本地的存放目录 set LOCAL_PULL_DIRD:\ADB_Backup\ REM 4. 本地待推送的文件夹路径 set LOCAL_PUSH_DIRC:\MyAppAssets\ REM 5. 推送文件到设备的目标目录 set DEVICE_PUSH_DIR/sdcard/Download/MyAppAssets/ REM 6. 日志文件路径 set LOG_FILEadb_operation_%date:~0,4%%date:~5,2%%date:~8,2%.log REM 日志文件名包含日期例如 adb_operation_20231027.log REM 函数定义区域 REM 函数记录日志 REM 用法call :log 日志信息 :log echo [%date% %time%] %~1 %LOG_FILE% goto :eof REM 函数检查ADB设备连接 :check_device echo [%date% %time%] 正在检查ADB设备连接... adb devices | findstr /R /C:^[0-9a-f].*device$ nul 2nul if !errorlevel! equ 0 ( echo 设备已连接。 call :log ADB设备检查通过设备已连接。 ) else ( echo 错误未找到已连接的ADB设备。 echo 请检查1. USB调试已开启 2. 驱动正常 3. 设备已授权 4. 连接线正常。 call :log 错误ADB设备检查失败未找到已连接设备。 pause exit /b 1 ) goto :eof REM 函数批量拉取文件 :pull_files echo. echo [%date% %time%] 开始批量拉取文件... call :log 开始批量拉取文件操作。 if not exist %FILELIST_TO_PULL% ( echo 错误拉取文件列表 %FILELIST_TO_PULL% 不存在。 call :log 错误拉取文件列表不存在。 goto :eof ) if not exist %LOCAL_PULL_DIR% ( echo 创建本地目录%LOCAL_PULL_DIR% mkdir %LOCAL_PULL_DIR% ) set /a SUCCESS_COUNT0 set /a FAIL_COUNT0 for /f usebackq delims %%i in (%FILELIST_TO_PULL%) do ( set device_file%%i REM 去除可能的首尾空格 for /f tokens* %%a in (!device_file!) do set device_file%%a if not !device_file! ( echo 正在拉取!device_file! adb pull %DEVICE_BASE_PATH%!device_file! %LOCAL_PULL_DIR% if !errorlevel! equ 0 ( echo [成功] set /a SUCCESS_COUNT1 call :log 拉取成功!device_file! ) else ( echo [失败] set /a FAIL_COUNT1 call :log 拉取失败!device_file! ) ) ) echo 拉取完成。成功!SUCCESS_COUNT! 个 失败!FAIL_COUNT! 个。 call :log 批量拉取完成。成功!SUCCESS_COUNT! 个 失败!FAIL_COUNT! 个。 goto :eof REM 函数批量推送文件夹 :push_folder echo. echo [%date% %time%] 开始批量推送文件夹... call :log 开始批量推送文件夹操作。 if not exist %LOCAL_PUSH_DIR% ( echo 错误本地推送目录 %LOCAL_PUSH_DIR% 不存在。 call :log 错误本地推送目录不存在。 goto :eof ) echo 推送本地目录%LOCAL_PUSH_DIR% echo 到设备目录%DEVICE_PUSH_DIR% adb push %LOCAL_PUSH_DIR% %DEVICE_PUSH_DIR% if !errorlevel! equ 0 ( echo [推送操作完成] call :log 推送文件夹操作完成%LOCAL_PUSH_DIR% - %DEVICE_PUSH_DIR% ) else ( echo [推送操作可能出错] call :log 警告推送文件夹操作可能出错返回码!errorlevel! ) goto :eof REM 脚本主流程 echo echo ADB 批量文件管理脚本 echo REM 步骤1检查设备 call :check_device REM 步骤2执行批量拉取 call :pull_files REM 步骤3执行批量推送 call :push_folder REM 步骤4结束 echo. echo [%date% %time%] 所有操作执行完毕。 echo 详细日志请查看%LOG_FILE% call :log 所有脚本操作执行完毕。 pause脚本使用步骤修改配置用文本编辑器打开adb_file_manager.bat根据注释修改“用户配置区域”的六个路径变量。准备拉取列表在与脚本相同的目录下创建files_to_pull.txt文件。每行写入一个你想从设备拉取的文件或目录的相对路径相对于DEVICE_BASE_PATH。例如DCIM/Camera/IMG_20231001.jpg Android/data/com.tencent.mm/MicroMsg/Download/ Download/my_document.pdf准备推送文件夹确保LOCAL_PUSH_DIR指向的本地文件夹存在并且里面有你要推送的文件。运行脚本双击adb_file_manager.bat运行。脚本会依次执行设备检查、拉取列表文件、推送本地文件夹并在屏幕上显示进度同时在同目录下生成一个带日期的日志文件如adb_operation_20231027.log。4.3 脚本关键环节深度剖析延迟变量扩展setlocal enabledelayedexpansion和!var!这是本脚本最重要的技术点之一。在for循环中如果直接使用%errorlevel%或%var%它们会被在循环开始前一次性替换成当时的值不会随循环更新。使用!errorlevel!和!var!可以确保每次循环都读取变量的最新值。这是批处理编写复杂逻辑时必须掌握的技巧。日志函数的实现:log我定义了一个简单的日志函数。它利用追加重定向将信息写入日志文件。%~1表示函数的第一个参数。goto :eofEnd Of File是批处理中结束函数执行的常用方法。通过call :log “信息”来调用使得日志记录代码复用且清晰。文件列表的读取与清洗for /f “usebackq delims”usebackq允许我们使用双引号来引用文件名”filelist.txt”。delims将分隔符设置为空意味着整行内容都会作为一个值被读取即使行内有空格、逗号等也不会被切断。内部的for /f “tokens*”循环用于去除每行首尾可能存在的空格避免路径错误。错误处理的层次脚本进行了多层错误处理。首先是设备检查失败则直接退出。其次是文件/目录存在性检查如拉取列表文件、本地推送目录不存在则给出错误并跳过该部分操作。最后是每个adb命令执行后的错误码检查!errorlevel!并分别计数成功与失败。这种结构保证了脚本在遇到部分错误时仍能继续执行其他任务而不是整体崩溃。5. 常见问题与排查技巧实录即使脚本写得再严谨在实际运行中还是会遇到各种问题。下面是我在长期使用中积累的一些典型问题及其解决方法。5.1 问题运行脚本时提示“adb不是内部或外部命令”现象双击批处理文件窗口一闪而过或者显示错误信息“adb不是内部或外部命令也不是可运行的程序或批处理文件。”原因系统PATH环境变量中没有包含adb.exe所在的目录。解决方案临时方案仅对当前CMD窗口有效在运行脚本前先手动打开CMD并切换到adb.exe所在的目录执行。或者在脚本最开头添加一行直接指定adb的完整路径set ADB_PATHC:\Android\Sdk\platform-tools\adb.exe REM 然后后面所有调用adb的地方都用 %ADB_PATH% 代替 adb永久方案推荐将Android SDK的platform-tools目录添加到系统环境变量PATH中。右键点击“此电脑” - “属性” - “高级系统设置” - “环境变量”。在“系统变量”中找到并选中Path点击“编辑”。点击“新建”添加你的platform-tools目录路径例如C:\Users\YourName\AppData\Local\Android\Sdk\platform-tools。确定所有对话框。需要重新启动任何已打开的命令行窗口新的PATH才会生效。5.2 问题设备检查通过但adb pull或adb push失败现象脚本显示设备已连接但执行文件传输时卡住或报错例如error: closedfailed to copy 或者权限错误。原因与排查USB连接不稳定尝试拔插USB线或更换一个USB端口优先使用机箱后置的USB口。关闭电脑和手机的屏幕保护、休眠设置。设备锁屏或休眠有些设备在锁屏状态下会限制ADB文件访问。确保设备屏幕已解锁并进入开发者选项将“USB调试安全设置”或“充电时不锁定屏幕”等选项打开。文件权限不足尝试拉取或推送系统受保护目录的文件。对于普通应用只能访问/sdcard/外部存储和自身的数据目录。确认你操作的路径是否有权限。可以尝试先在adb shell下用ls -l命令查看文件权限。设备存储空间不足推送文件时目标设备存储空间不够。使用adb shell df -h检查存储空间。路径错误这是最常见的原因。仔细检查配置的路径设备路径是否以/开头是否区分大小写本地Windows路径是否使用了正确的反斜杠\在批处理变量中是否用双引号包裹了完整路径对于拉取列表文件中的路径是否相对于DEVICE_BASE_PATH正确ADB服务异常有时ADB服务会卡住。可以尝试在命令行中执行adb kill-server然后adb start-server重启ADB服务。5.3 问题脚本中的中文显示或处理乱码现象脚本中echo的中文、日志文件中的中文或者文件名中的中文显示为乱码如“锟斤拷”。原因批处理文件 (.bat) 的编码与Windows命令行CMD的代码页不匹配。CMD默认使用GBK代码页936编码而许多现代编辑器默认保存为UTF-8。解决方案保存脚本为ANSI编码用Notepad打开你的.bat文件在菜单栏选择“编码” - “转为ANSI编码”然后保存。这是最一劳永逸的方法。修改CMD活动代码页临时在脚本开头添加chcp 65001 nul可以切换到UTF-8代码页。但这可能带来其他兼容性问题且需要终端字体支持UTF-8不推荐作为主要方案。5.4 问题for循环只执行了一次或者文件列表没读全现象明明files_to_pull.txt中有多行但脚本只处理了第一行就结束了。原因文件列表中存在空行或者某些行的格式导致for /f解析提前终止。另外如果文件列表是Unix/Linux格式行尾是LF在Windows下也可能出现解析问题。解决方案清理你的列表文件删除所有不必要的空行。确保脚本中for /f使用了delims选项这样不会因为行内的空格而截断。尝试在for /f选项中添加eol将行注释字符设置为空防止某些以分号;开头的行被忽略。for /f usebackq eol delims %%i in (%FILELIST%) do (...)使用Notepad等工具将文件列表的格式从“UTF-8”转换为“UTF-8无BOM”或者从“Unix (LF)”转换为“Windows (CR LF)”。5.5 高级技巧使用变量动态构建带时间戳的备份目录在拉取文件时为了避免覆盖之前的备份我们经常希望按日期时间创建子文件夹。批处理中可以通过字符串截取来获取日期时间。REM 获取当前日期和时间格式为 YYYYMMDD_HHMMSS set BACKUP_TIMESTAMP%date:~0,4%%date:~5,2%%date:~8,2%_%time:~0,2%%time:~3,2%%time:~6,2% REM 替换时间中的空格小时若为个位数前面会有空格 set BACKUP_TIMESTAMP%BACKUP_TIMESTAMP: 0% set LOCAL_PULL_DIRD:\ADB_Backup\%BACKUP_TIMESTAMP%\这样每次运行脚本拉取的文件都会自动存放到像D:\ADB_Backup\20231027_143025\这样的新目录中实现了自动归档。5.6 脚本优化增加交互式菜单选择对于更复杂的场景你可以将脚本升级为带菜单的交互式版本让用户选择执行拉取、推送还是其他操作。echo off :menu cls echo echo ADB 文件管理工具 echo echo 1. 批量拉取文件 echo 2. 批量推送文件夹 echo 3. 检查设备状态 echo 4. 重启ADB服务 echo 5. 退出 echo set /p choice请选择操作 (1-5): if %choice%1 goto pull if %choice%2 goto push if %choice%3 goto check if %choice%4 goto restart if %choice%5 exit /b echo 选择无效请重新输入。 pause goto menu :pull REM 调用之前定义的拉取函数或代码块 call :pull_files pause goto menu :push REM 调用之前定义的推送函数或代码块 call :push_folder pause goto menu :check adb devices pause goto menu :restart adb kill-server adb start-server echo ADB服务已重启。 pause goto menu这个框架使得脚本更加用户友好功能也更易于扩展。你可以把前面章节中封装好的函数:pull_files和:push_folder直接整合进来。