AutoJs中用Shell操作SQLite的完整实践指南

发布时间:2026/10/17 9:25:09

AutoJs中用Shell操作SQLite的完整实践指南 简介一份面向安卓自动化脚本开发者的源码资源主题是使用系统命令行操作本地数据库。压缩包内只包含1个JavaScript脚本文件整体大小约775字节体积极其精简下载后导入自动化脚本工具即可运行也适合直接阅读源码来学习实现思路。脚本通过依次调用系统命令完成创建数据库文件、建立数据表、插入记录、条件查询等典型操作并处理了命令执行后的返回信息与简单异常情况构成了一个可以直接复用的数据库操作模板读者只需调整其中的数据库路径与字段名称就能把这段代码用于自动化任务中的配置保存、运行日志记录、任务状态持久化等场景也可在此基础上扩展为定时备份或数据清理小工具。这套源码对低版本环境兼容良好运行门槛低作者同时强调资源仅供学习参考不得用于商业用途使用者需自行承担责任。目前已有122人学习下载适合需要在自动化脚本中管理本地数据的开发者参考。1. AutoJs源码里为什么偏偏要用 shell 去操作 sqlite做 AutoJs 脚本的人早晚会撞上一堵墙插件自带的 sqlite 接口在简单场景下够用可一旦数据量上了几十万行、或者你想读的是另一个应用生成的数据库整个方案就会卡死在权限和性能上。这时候用 shell 操作 sqlite 反而成了最顺手的路——直接在 sh 里调用 sqlite3 命令把查询、批量写入、备份恢复全部从“程序接口”挪到“命令行工具”这一层。这个标题讲的就是这件事什么时候绕开自带 APIshell 该怎么写以及最容易在哪些地方翻车。适合正在写自动化脚本、又需要碰数据库的从业者看完就能照着把方案落地。2. shell 操作 sqlite 的底牌sqlite3 命令与 AutoJs Shell 对象怎么配合2.1 sqlite3 命令行工具与 AutoJs Shell 对象的关系sqlite3 是 SQLite 官方自带的命令行工具设备上只要有 SQLite 库一般就带一个可执行的 sqlite3 文件常见位置在 /system/xbin/sqlite3 或 /system/bin/sqlite3。AutoJs 里的 Shell 对象做的事情更简单它 fork 出一个 sh 进程然后你把整条命令字符串丢给它由 sh 去解释执行。所以当你在 AutoJs 里写shell.exec(sqlite3 ...)时实际发生的是JS 层把字符串交给底层 shellsh 再拉起 sqlite3 进程sqlite3 的结果通过标准输出回传到 JS 层。这条链路的好处在于你写的不是某种封装后的 API而是原原本本的 sqlite3 命令。点命令.tables、.dump、.backup、PRAGMA、跨库 attach只要 sqlite3 命令行支持你在 AutoJs 里都能用。代价也很明显每次 exec 都要拉起一个 shell 进程调用开销比直接调函数大所以正确用法是把命令尽量合并成一条而不是一条 SQL 调一次 exec。Shell 对象还有个容易被忽略的构造参数new Shell(true)表示以 root 身份执行这直接影响后面能不能读到其他应用的数据库。2.2 什么时候该走 shell什么时候用 AutoJs 自带 API不是所有场景都值得绕到 shell。我自己的选型标准很简单先看操作的数据量和权限边界。如果只是读自己应用数据库里的几十行配置直接用 AutoJs 封装好的数据库接口就够了代码短、返回结构也干净可一旦碰到下面这几种情况我会毫不犹豫切到 shell。场景推荐方案原因读取几条简单配置AutoJs 自带 API代码量小返回对象可直接用批量写入几万行以上shell 显式事务自带 API 一条条 commit速度差几十倍执行 VACUUM、PRAGMA、.dumpshell自带 API 未必暴露这些命令读取其他应用的数据库shell rootSELinux 和沙箱限制下只有 su 能跨应用访问数据库备份、迁移shell 的 .backup / .dump原生命令比手动导出靠谱得多选 shell 的核心判断标准就两条一是数据量大到自带 API 撑不住二是权限范围超出了当前应用沙箱。两个条件只要中一个shell 就是更合理的答案。要注意的是shell 操作 sqlite 拿到的结果永远是一段文本不是结构化对象所以脚本里必须自己写解析层这是取舍的关键点。2.3 动手前先确认环境sqlite3 是否存在、权限够不够我拿到一台新设备写这类脚本第一件事不是写业务逻辑而是先跑一段环境侦察确认 sqlite3 二进制在不在、当前 shell 是什么身份、目标数据库文件能不能读。这段代码建议原样保留在你的脚本里做成一个环境检查函数免得换设备后莫名其妙跑不起来。function checkEnv(dbPath) { var shell new Shell(true); var out shell.exec(which sqlite3; echo ---; id; echo ---; ls -l dbPath); shell.exit(); log(out); } checkEnv(/data/data/com.example.app/databases/demo.db);这段代码先用which sqlite3确认命令存在然后打印当前用户身份最后查看目标数据库文件的权限和属主。为什么要三个一起查因为实际项目里最常见的情况是 sqlite3 存在但 shell 身份不是 root或者数据库文件属于另一个应用权限不足直接导致后面所有操作都报错。看到输出后逐项核对sqlite3 有没有输出完整路径id结果里有没有 uid0ls -l显示的属主是不是当前 shell 能访问的。如果设备上确实没有 sqlite3 命令我一般会先把目标数据库 copy 到应用私有目录然后用 AutoJs 自带接口处理这比想办法补一个 sqlite3 二进制要省事得多。which返回空不代表 SQLite 不存在有时候命令行工具被精简了但库文件还在这种情况只有换方案一条路。3. 最小可跑的查询脚本从一行命令到能出结果3.1 第一行命令先查表结构确认链路通不通环境检查过了下一步别急着写复杂查询先用最基础的一条命令验证整条链路查数据库里有哪几张表。这是整个方案能不能成立的最小验证把它跑通后面无非是换 SQL 文本的事。var shell new Shell(true); var out shell.exec(sqlite3 /data/local/tmp/demo.db .tables); shell.exit(); log(返回结果:\n out);这里我把数据库文件放在 /data/local/tmp 下原因只有一个这个目录在大多数设备上对 root shell 是开放的既能写也能读适合做验证。.tables是 sqlite3 的点命令不需要分号结尾作用是列出当前库里的所有表。log 打印时留意一下返回的如果是空字符串大概率是命令执行失败但错误信息走了 stderrexec 返回的字段里可能不包含 stderr 内容这时候要把命令改成21把错误输出重定向到标准输出才能看到真正的报错。3.2 封装一个 execSql()把命令、解析和报错放在一起链路通了之后我通常会马上封装一个最基础的 execSql()把「拼命令、执行、取输出」三步收敛到一个函数里。这样后面写业务逻辑不用每次都重复 shell 的创建和销毁也方便统一处理错误。注意不同版本 AutoJs 的 exec 返回值结构可能不一样有的返回对象有的直接返回字符串封装时先打印出来看一眼再写死解析逻辑。function execSql(dbPath, sql) { var shell new Shell(true); var cmd sqlite3 dbPath sql.replace(//g, \\) ; var result shell.exec(cmd 21); shell.exit(); var text ; if (result null || result undefined) { text ; } else if (typeof result string) { text result; } else if (result.output) { text result.output; } else { text JSON.stringify(result); } return text; } log(execSql(/data/local/tmp/demo.db, SELECT name FROM sqlite_master WHERE typetable));这个函数最关键的三个点SQL 用单引号包起来SQL 内部的单引号通过\\转义这是 shell 语境下最安全的引用方式结尾的21把错误输出合并回来不然 SQL 写错了只能看到空字符串排查效率极低返回值做了兼容处理不赌某一个版本的具体字段。注意这个写法里命令和 SQL 都是明文拼接如果 SQL 来自外部输入建议不要直接用这个函数后面第 5 章的引号坑会专门说。3.3 读懂输出默认分隔、列头、NULL 的坑跑通了查询接下来就要面对 sqlite3 文本输出的老问题默认情况下每行记录是竖线分隔的文本NULL 显示成空字符串而且不带列名。这三件事凑在一起直接按行拆出来的数据根本没法用。看这个例子var out execSql(/data/local/tmp/demo.db, SELECT id, name, score FROM student LIMIT 3); // 输出可能长这样 // 1|张三|88 // 2|李四| // 3|王五|91.5第一行没有列头第二行的空字符串既可能是 NULL 也可能是空字符串这个输出如果直接拿去生成 JSON 必然出错。我一般会加上-header -separator |两个参数让输出带列名同时把分隔符固定成管道符。NULL 的问题则要靠 SQL 层处理比如用IFNULL(score, NULL)显式标记或者直接在主查询里把字段名拼进结果。说到底shell 方式拿到的终究是给人看的文本不是给程序用的结构化数据解析层必须自己把它补成对象。4. 把增删改查彻底做成一个 shell 操作 sqlite 的工具类4.1 一个能直接抄的 SqliteShell 工具类完成最小验证后我习惯把代码升级成一个工具类把查询、执行、关闭、结果解析全部包进去。下面的实现是我在几个项目里反复用的一套结构核心思路是查询自动加-header -separator返回对象数组执行操作返回最后一条命令的 changes()所有命令统一拼装、统一走 stderr 合并。function SqliteShell(dbPath, useRoot) { this.dbPath dbPath; this.useRoot useRoot ! false; } SqliteShell.prototype.query function (sql) { var cmd sqlite3 -header -separator | this.dbPath sql.replace(//g, \\) 21; var shell new Shell(this.useRoot); var result shell.exec(cmd); shell.exit(); return parseRows(toText(result)); }; SqliteShell.prototype.execute function (sql) { var fullSql sql ; SELECT changes() AS affected_rows;; var cmd sqlite3 this.dbPath fullSql.replace(//g, \\) 21; var shell new Shell(this.useRoot); var result shell.exec(cmd); shell.exit(); var text toText(result); var lines text.split(\n).filter(function (line) { return line.trim().length 0; }); return parseInt(lines[lines.length - 1], 10) || 0; }; function toText(result) { if (result null || result undefined) return ; if (typeof result string) return result; if (result.output) return result.output; return JSON.stringify(result); } function parseRows(text) { var lines text.split(\n).filter(function (line) { return line.trim().length 0; }); if (lines.length 2) return []; var headers lines[0].split(|); var rows []; for (var i 1; i lines.length; i) { var values lines[i].split(|); var row {}; for (var j 0; j headers.length; j) { row[headers[j]] values[j] ? null : values[j]; } rows.push(row); } return rows; }这个类有两个设计取舍值得说明execute 方法在业务 SQL 后面追加了SELECT changes()因为 sqlite3 命令行工具本身不直接返回受影响行数必须靠这个函数拿到最后一次写入的结果这比去数输出文本可靠得多parseRows 里把空字符串统一转成 null解决了上一节提到的 NULL 显示问题代价是真正的空字符串也会被转掉如果你业务里需要区分这两种情况就得换用 IFNULL 加占位符。query 方法的返回值已经是对象数组后续业务代码直接遍历就行不再需要关心文本分隔。4.2 批量写入用事务把性能跑满的关键shell 方式批量写数据最大的性能陷阱是不包事务。sqlite3 命令行在没有任何显式事务包裹时每条 INSERT 语句都会触发一次独立的自动提交也就是每写一条都要刷一次磁盘。几万行数据这么写耗时直接从秒级膨胀到分钟级。解决方案很简单把所有 INSERT 用 BEGIN 和 COMMIT 包起来让它们在一个事务里完成。var db new SqliteShell(/data/local/tmp/demo.db, true); var values []; for (var i 0; i 50000; i) { values.push(( i , user_ i )); } var batchSql BEGIN; INSERT INTO t_user(id, name) VALUES values.join(,) ; COMMIT;; var t1 new Date().getTime(); var aff db.execute(batchSql); log(写入行数: aff , 耗时: (new Date().getTime() - t1) ms);这里把五万条 INSERT 一次性拼成一条 SQL外面套 BEGIN 和 COMMIT整个写入在同一个事务里完成耗时通常能压到几百毫秒级别。动手前注意两点values 数组如果太大拼出的 SQL 字符串可能超过 shell 命令长度上限我一般每批塞一万条左右超出就拆成多个事务SQL 里不能出现分号外的特殊字符所有字符串值都必须提前做单引号转义。如果不幸写了一半失败事务内前面的语句会全部回滚数据库不会处于半写状态这点比逐条写要安全得多。4.3 结果集解析把文本变成对象数组parseRows 函数看起来逻辑简单实际有一个隐藏前提查询结果中的字段本身不能包含管道符。如果某个字段存储的文本里有|字符按split(|)解析出来的数组就对不齐了。这个问题有两种解决思路一种是在 SQL 里把字段值统一替换掉分隔符另一种是改用一个更不容易出现的分隔符比如把 separator 参数换成|||或者\t。我自己的习惯是用制表符文本字段里出现 Tab 的概率远低于管道符。SqliteShell.prototype.queryTab function (sql) { var cmd sqlite3 -header -separator \t this.dbPath sql.replace(//g, \\) 21; var shell new Shell(this.useRoot); var result shell.exec(cmd); shell.exit(); var text toText(result); var lines text.split(\n).filter(function (line) { return line.trim().length 0; }); if (lines.length 2) return []; var headers lines[0].split(\t); var rows []; for (var i 1; i lines.length; i) { var values lines[i].split(\t); var row {}; for (var j 0; j headers.length; j) { row[headers[j]] values[j] ? null : values[j]; } rows.push(row); } return rows; };这个版本的解析函数只是把分隔符从管道符换成了制表符其他逻辑没变。但实践中我发现一个更难受的问题数字字段经过文本输出后全部变成了字符串如果业务代码需要做数值比较还得手动parseInt或parseFloat。想省事的话可以在 SQL 层提前做类型转换比如CAST(score AS INTEGER)但这只适用于你已经知道字段类型的情况。说到底shell 方式的结果解析必须由使用者自己兜底这是它不如自带 API 的地方。5. 避坑shell 操作 sqlite 常见的 5 个坑和排查方法5.1 坑一设备上根本没有 sqlite3 命令现象环境检查脚本输出里which sqlite3返回空命令跑起来直接提示 not found。原因部分精简 ROM 只保留了库文件没有把命令行工具打进去。解决先确认 /system/xbin、/system/bin 两个目录下有没有 sqlite3都没有的话最稳妥的方案是放弃 shell 路线改用 AutoJs 自带 API 处理如果你坚持要 sqlite3就需要自己准备一个对应架构的二进制放到 /data/local/tmp 下并 chmod 755但兼容性风险不小。我用过几个渠道后得出的结论是与其折腾二进制不如换回自带 API。5.2 坑二SQL 里的引号把 shell 弄炸了现象SQL 里包含字符串值比如WHERE name张三命令执行后报语法错误或者干脆输出为空。原因SQL 里的单引号和外层包 SQL 的单引号互相干涉shell 提前截断了命令。解决SQL 内部的每个单引号都必须转义成\我提供的 execSql 和工具类里已经做了这个处理。如果你的 SQL 里有大量引号、反引号、美元符号转义很容易漏这时候我一般改用临时文件方案把 SQL 写成 .sql 文件放 /data/local/tmp再用sqlite3 db.sqlite .read /data/local/tmp/x.sql执行彻底绕开引号地狱。5.3 坑三输出中文乱码现象查询结果里中文显示成一串乱码但数据在库里明明是正常的。原因sqlite3 命令行输出的字节编码和 shell 返回字符串的编码不一致常见于系统 locale 是 GBK 而库文件是 UTF-8 的情况。解决优先绕开管道用.output /data/local/tmp/result.txt把查询结果写到文件再通过文件读取内容这个方法不会经过 stdout 的编码转换一般能保住中文。我还会在查询前执行一次PRAGMA encoding;确认数据库本身是 UTF-8如果库文件源头就不是 UTF-8那就只能在写入端修正。5.4 坑四非 root 权限访问不了应用私有目录现象shell 执行ls能看到数据库文件但 sqlite3 一打开就报 unable to open database file。原因Android 的沙箱机制和 SELinux 限制了跨应用访问即使 shell 身份能看到文件路径实际打开时仍被拒绝。解决如果 AutoJs 脚本运行在 root 模式下new Shell(true)能解决大部分设备的问题个别设备 SELinux 策略比较严还需要先su -c chcon ...调整上下文或者临时把数据库 copy 到 /data/local/tmp 再操作。我的建议是优先把外部数据库复制到应用自己的目录下处理处理完成后再复制回去这样最省心。5.5 坑五WAL 模式下的 database is locked现象执行写入操作时返回信息里出现 database is locked但没有任何其他脚本在操作同一个库。原因数据库开启了 WAL 模式另一个进程持有写锁时新的写操作会立刻失败sqlite3 命令行默认的 busy_timeout 很短等不到锁释放。解决在业务 SQL 前面拼上PRAGMA busy_timeout5000;让 sqlite3 等待 5 秒而不是立刻放弃也可以直接用.timeout 5000点命令设置超时。如果加了超时仍然频繁报锁检查是不是目标应用自己持有数据库连接没有释放这种情况只能找业务应用的空窗期再操作。6. 进阶技巧备份恢复、批量导入与表结构迁移6.1 备份恢复的一条命令sqlite 的备份最好不要用「复制 db 文件」的方式因为 WAL 模式下数据可能还在日志里没合并直接复制出来的副本往往是旧的。我一般用.backup命令它内部会处理一致性。sqlite3 /data/local/tmp/demo.db .backup /data/local/tmp/demo_bak.db sqlite3 /data/local/tmp/demo_bak.db .restore /data/local/tmp/demo.db.backup是官方推荐的在线备份方式备份期间源库还能继续读写.restore则是把备份文件还原回目标路径。实际操作里记得先确认目标文件没有被占用否则还原会失败。6.2 用 CSV 做批量导入导出导入 CSV 时最容易踩的坑是表头被当成数据写进去。sqlite3 的.import在目标表已存在时会把 CSV 的第一行也当作普通数据行插入。我的做法是导入前先建一张不含表头的临时表或者导入后用DELETE FROM t WHERE id 表头清理。导出侧反而简单.mode csv加.output指定文件路径查完再用.output stdout恢复标准输出。6.3 表结构迁移用 dump 搬运数据迁移表结构最稳定的办法是用.dump生成完整的 SQL 文本再灌入新库。.dump的输出包含建表语句、索引和数据在新库上执行一遍相当于完整的表结构迁移。跨版本升级数据库时我会先 dump 出 SQL 做 diff确认没有不兼容的语法再在新库上执行。这套习惯已经让我避开了好几次因为直接改 schema 导致的数据损失。现在遇到任何涉及 sqlite 的脚本任务我都会先 dump 一份再动任何 SQL这个习惯救过我很多次。希望帮到你。本文还有配套的精品资源点击获取
延伸阅读

更多相关文章

2026/10/17 9:25:09

.chess申请背后的新顶级域名技术竞争与基础设施重构

顶级域名不只是网址后缀。当国际棋联(FIDE)和世界国际象棋(World Chess)联手申请 .chess 这个新顶级域名时,很多人第一反应是“又多了一个网址后缀”,但这件事真正值得技术人关注的,是它背后代表…

2026/10/17 9:25:09

用claude-code-templates解决AI编程输出不稳定问题

上个季度给一个微服务项目补单元测试,我对着同一个终端里的Claude Code连续问了三次“给这个模块写测试”,三次回答风格完全不一样:第一次用了JUnit 5的ParameterizedTest,第二次用的是Mockito原生写法,第三次把断言库…

2026/10/17 9:20:09

SQL空值处理实战:COALESCE函数原理、用法与避坑指南

在数据库里跑 SQL 时,最让人又爱又恨的东西应该就是 NULL 了。你说它是空值吧,它又不等于空字符串,也不等于 0;你说它没有值吧,它在聚合、连接、排序里又无处不在,稍不留神就给你惹一堆麻烦。COALESCE 这个…

2026/10/17 10:45:15

子母钟系统采购——应以全生命周期总拥有成本为核心标尺

关键词:数字子母钟、gps子母钟系统、ntp子母钟、网络子母钟系统医院、轨道交通、学校、工厂、智慧楼宇等项目建设子母钟系统时,采购常陷入只对比硬件报价、优先低价方案的误区。前期投入看似低廉,但运行3-5年后易出现备件停产、维…

2026/10/17 10:45:15

SQL生成落地关键:定制能力与推理稳定性实战指南

1. 这不是选“谁家大模型更好”,而是选“谁能把SQL生成这件事真正落地”最近两周,我连续帮三支不同行业的团队做过SQL生成场景的技术评估:一支是金融风控中台,要从千万级客户行为日志里实时提取异常路径;一支是电商BI小…

2026/10/17 10:45:15

从“人眼排雷”到“智能防控”:AI文档审核如何赋能企业提效?

在政企单位与大型企业的日常运转中,文档不仅是信息的载体,更是法律效力的延伸。从对外发布的红头文件、商务合同,到对内的管理制度、会议纪要,任何一处错别字、标点误用,甚至语义歧义,都可能引发严重的合规…

2026/10/17 10:45:15

鸿蒙Flutter Scrollbar实战:适配触摸与鼠标双模式的滚动条调优

说实话,写鸿蒙生态下的Flutter组件,每次都有种“带着熟悉武器进新战场”的感觉。Scrollbar这个组件,在标准Flutter里你可能只用一行Scrollbar(child: ListView(...))就完事了,但在OpenHarmony上,它偏偏是“细节决定体验…

2026/10/17 10:45:15

Agent学习总结二

二、智能体发展史2.1、基于符号与逻辑的早期智能体人工智能领域的早期探索,深受数理逻辑和计算机科学基本原理的影响。当时候研究者们普遍相信:人类的智能,尤其是逻辑推理能力,可以被形式化的符号体系所捕捉和复现。这一核心思想催…

2026/10/17 10:40:15

机械臂预分析:用Matlab可视化可达空间与可操作性

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/16 5:33:43

Python调用Gemini Structured Outputs实现工单路由门禁

客服工单最怕的不是模型“答错一句话”,而是它给出一段看起来合理的说明,程序却从中猜错优先级。通俗做法是:要求模型只交 JSON(JavaScript Object Notation,轻量数据格式),再让代码验证它。Gem…

2026/10/16 4:03:48

Spring Boot超市进销存系统毕设实战:从需求拆解到答辩通关

最近带的一个学生项目组里,有A同学跑来问我:选什么毕设题目最稳妥,既能让评审老师觉得工作量够,又不会在答辩时被问到语无伦次。我第一反应就是推荐基于Spring Boot的超市仓库管理系统——也就是超市进销存系统。这个题目乍一看平…

2026/10/15 13:42:16

Flutter StatefulWidget 生命周期核心解析

很多刚开始接触 Flutter 的朋友,在看完一堆“Hello World”和基础组件之后,大概率都会撞上同一堵墙:StatefulWidget 里那堆 initState、build、dispose 方法,到底什么时候被调用?为什么顺序是那样?在里面到…

2026/10/17 0:04:32

PlanarSLAM:面向曼哈顿世界的点线面联合SLAM系统

简介:本资源是面向机器人视觉与SLAM研究者的C开源RGB-D SLAM系统实现,专为结构化室内场景(如办公室、走廊)设计,通过融合点、线、平面三类几何特征并引入“曼哈顿世界”假设,显著提升位姿跟踪鲁棒性与稠密地…

2026/10/17 0:04:32

TDH Workflow调度实战:从DAG依赖驱动到任务编排与故障排查

1. 从一个调度脚本说起:TDH Workflow到底在解决什么问题第一次接触TDH的Workflow,是在一个数据清洗项目里。当时团队每天要跑几十个ETL任务,任务之间有严格的先后依赖——A表清洗完才能跑B表的关联,B表跑完才能触发C表的聚合。最开…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑