简介:这份资源是一份AutoJs源码模板,主题为通过shell命令操作sqlite数据库,面向具备一定AutoJs基础、希望扩展本地数据存储与脚本自动化能力的开发者。它解决的是在AutoJs环境中直接调用shell执行sqlite语句、完成建库建表与增删改查等操作的学习需求,适合作为脚本工具开发与自动化项目中的参考范例。压缩包为7z格式,仅含1个js文件,体积约775B,文件精简,便于快速阅读与移植到自己的项目中。目前已有122人学习下载,说明该方向有一定关注度。源码支持低版本AutoJs,安装后可直接运行查看效果,读者可从中了解shell与sqlite结合的调用方式、命令拼接思路及基础排错方法,用于学习与参考。需注意资源仅供学习交流,请勿用于商业用途,且不熟悉AutoJs安装与使用的人群应谨慎下载。
1. AutoJs 里用 shell 操作 sqlite:为什么这条路径值得走
脚本跑久了,数据总得有个地方放。用 AutoJs 做自动化,很多人一开始把状态塞进storages或者干脆写个 JSON 文件,量小的时候没问题,一旦要按条件查、要更新某一行、要做去重统计,文件方案就开始翻车。这时候 sqlite 就是最顺手的选择——它本身就是 Android 系统自带的嵌入式数据库,不需要额外装服务,一个.db文件走天下。而 AutoJs 提供的shell函数,能直接调用系统里的sqlite3命令行工具,把建表、插入、查询、更新这些操作全部用命令跑完,不用引第三方库,也不用折腾 JDBC 那一套。
这条路径适合两类人:一类是脚本里需要持久化结构化数据、又不想引入复杂依赖的 AutoJs 玩家;另一类是想把 shell 脚本能力接进自动化流程、顺手把数据库增删改查一起管掉的工程师。核心思路就一句话:AutoJs 负责调度和逻辑,shell 负责把 SQL 喂给 sqlite3,结果再读回来。下面从环境确认一路讲到参数调优和踩坑,能照着复现。
2. 先搞清楚 AutoJs 的 shell 和 sqlite3 到底怎么配合
2.1 shell 函数的能力边界与返回结构
AutoJs 的shell(cmd, isRoot)本质是起一个子进程执行命令,返回一个ShellResult对象,里面有三个关键字段:code(退出码,0 表示成功)、result(标准输出内容)、error(标准错误内容)。很多人第一次用就栽在只看result不看code上——命令失败了result可能是空的,脚本却继续往下跑,最后报一个莫名其妙的数据错误。
// 基础调用:执行一条命令并检查退出码 var r = shell("echo hello", false); if (r.code === 0) { console.log("输出:", r.result); } else { console.error("失败:", r.error); }逻辑说明:shell第二个参数isRoot决定是否以 root 身份执行。绝大多数情况下操作自己应用目录下的数据库文件不需要 root,传false即可。参数说明:cmd是完整命令行字符串,多条命令可以用&&或;连接,但要注意转义。判断成功与否永远以code为准,result只用来取数据。
2.2 确认设备上 sqlite3 可执行文件的位置
Android 系统从某个版本开始,sqlite3命令行工具不一定在PATH里,直接shell("sqlite3 ...")可能报not found。常见做法是先探测几个典型路径,找到能用的那个再拼命令。
# 在 AutoJs 里逐条探测,找到可用的 sqlite3 which sqlite3 ls /system/bin/sqlite3 ls /system/xbin/sqlite3 ls /data/local/tmp/sqlite3逻辑说明:which走的是PATH查找,后面几条是硬编码路径探测。参数说明:不同厂商 ROM 放置位置不同,/system/bin最常见,部分设备在/system/xbin。如果全都没有,就需要自己推一个静态编译的sqlite3二进制到/data/local/tmp并chmod 755,这一步属于设备适配,不是每台机器都要做。找到路径后,后续所有命令都用绝对路径调用,避免PATH不一致导致的玄学失败。
2.3 数据库文件放哪:路径选择直接影响权限
数据库文件的位置决定了你能不能读写、shell 能不能访问。AutoJs 脚本自身的工作目录通常在/sdcard/下的某个脚本目录,这个位置 shell 和 sqlite3 都能访问,是最省事的选择。放到应用私有目录(/data/data/包名/)虽然更隔离,但非 root 的 shell 进程往往没权限,会直接报unable to open database file。
// 把数据库放在脚本可访问的公共目录 var dbPath = "/sdcard/脚本项目/data/app.db"; // 先确保目录存在 shell("mkdir -p /sdcard/脚本项目/data");逻辑说明:mkdir -p保证父目录不存在时一并创建,避免第一次运行时因目录缺失导致建库失败。参数说明:dbPath建议用绝对路径,不要用相对路径,因为 shell 子进程的当前工作目录和 AutoJs 脚本目录不一定一致,相对路径是踩坑重灾区。
3. 建表、插入、查询:把增删改查拆成可复用的 shell 命令
3.1 用一条命令完成建表并验证结构
建表是第一步,也是最容易因为 SQL 引号转义出错的一步。AutoJs 里拼命令字符串时,SQL 语句里的单引号和外层双引号会打架,常见做法是把 SQL 写进一个变量,用双引号包外层、单引号包 SQL 内部字符串。
var db = "/sdcard/脚本项目/data/app.db"; var createSql = "CREATE TABLE IF NOT EXISTS task_log (" + "id INTEGER PRIMARY KEY AUTOINCREMENT, " + "task_name TEXT NOT NULL, " + "status INTEGER DEFAULT 0, " + "created_at TEXT)"; var cmd = "sqlite3 " + db + " \"" + createSql + "\""; var r = shell(cmd, false); console.log("建表退出码:", r.code, "错误:", r.error);逻辑说明:CREATE TABLE IF NOT EXISTS保证重复执行不报错,适合脚本每次启动都跑一遍的幂等场景。参数说明:AUTOINCREMENT让id自增,DEFAULT 0给状态字段兜底,created_at用文本存时间戳,方便直接读。执行后建议再跑一条.schema task_log确认结构,别假设一定建成功。
3.2 插入数据:参数拼接与批量写入的取舍
单条插入直接拼 SQL 就行,但要注意文本字段里的单引号会破坏语句。批量插入时,逐条起 shell 进程开销大,常见做法是把多条INSERT拼成一个事务,一次 shell 调用写完。
// 批量插入:拼成一个事务,减少 shell 调用次数 var rows = [ ["签到任务", 1, "2024-06-01 08:00:00"], ["清理任务", 0, "2024-06-01 09:30:00"] ]; var values = rows.map(function(row) { return "('" + row[0] + "'," + row[1] + ",'" + row[2] + "')"; }).join(","); var insertSql = "INSERT INTO task_log (task_name,status,created_at) VALUES " + values + ";"; var r = shell("sqlite3 " + db + " \"" + insertSql + "\"", false);逻辑说明:把多行VALUES合并成一条INSERT,只起一次 shell 进程,比循环调用快一个数量级。参数说明:文本值必须用单引号包裹,数字直接拼。如果文本里本身含单引号,要先做转义(把'替换成''),否则语句会被截断,这是血泪经验。
3.3 查询与结果解析:把 stdout 变成结构化数据
查询是 shell 操作 sqlite 最有价值的部分。默认输出是竖线分隔的表格,解析起来麻烦,用-separator指定分隔符、-noheader去掉表头,输出就是干净的 CSV 风格,直接按行按列切。
// 查询:指定分隔符,输出便于解析 var querySql = "SELECT id,task_name,status FROM task_log WHERE status=0;"; var cmd = "sqlite3 -separator ',' -noheader " + db + " \"" + querySql + "\""; var r = shell(cmd, false); if (r.code === 0 && r.result.trim() !== "") { var lines = r.result.trim().split("\n"); lines.forEach(function(line) { var cols = line.split(","); console.log("ID:", cols[0], "任务:", cols[1], "状态:", cols[2]); }); }逻辑说明:-separator ','把列分隔符设成逗号,-noheader去掉列名行,输出直接可解析。参数说明:如果字段内容本身可能含逗号,分隔符要换成不常见字符如|或\t。空结果时result是空字符串,必须先trim再判断,否则会切出一个空数组导致后续报错。
3.4 更新与删除:条件语句写错就是全表遭殃
UPDATE和DELETE不带WHERE会作用于全表,这是最危险的误操作。脚本里拼条件时,务必确认WHERE子句存在且条件正确。
// 更新指定任务状态 var updateSql = "UPDATE task_log SET status=1 WHERE task_name='签到任务';"; var r = shell("sqlite3 " + db + " \"" + updateSql + "\"", false); // 删除已完成且超过指定时间的记录 var deleteSql = "DELETE FROM task_log WHERE status=1 AND created_at < '2024-06-01';"; shell("sqlite3 " + db + " \"" + deleteSql + "\"", false);逻辑说明:更新和删除都带明确WHERE,避免误伤。参数说明:时间比较用文本格式时,要保证存储格式统一(都是YYYY-MM-DD HH:mm:ss),否则字符串比较结果不符合预期。执行删除前,稳妥做法是先跑一条同条件的SELECT COUNT(*)看命中多少行。
4. 避坑与排查:shell 操作 sqlite 最常见的五个翻车点
4.1 现象:报 unable to open database file
原因:数据库路径所在目录不存在,或者当前 shell 进程对该路径没有写权限。非 root 的 shell 访问应用私有目录时几乎必然触发。
解决:把数据库放到/sdcard/下的公共目录,执行前先mkdir -p建好父目录,并用ls -l确认目录权限可写。
4.2 现象:SQL 语句执行了但数据没进去,退出码却是 0
原因:SQL 字符串里的引号被 shell 提前解析,实际传给 sqlite3 的语句被截断,sqlite3 收到空语句或残缺语句时可能不报错。
解决:把完整命令console.log出来,复制到 adb shell 里手动跑一遍对比。文本值里的单引号统一替换成两个单引号做转义。
4.3 现象:查询结果中文乱码或字段错位
原因:分隔符选得和字段内容冲突,或者编码不一致。sqlite3 默认按 UTF-8 输出,但某些终端环境会二次转码。
解决:分隔符换成\t或|这类字段内容里不会出现的字符;解析前先确认result的字节内容,必要时用-cmd ".mode csv"统一输出格式。
4.4 现象:批量插入几百条后脚本卡死
原因:每条INSERT单独起一次 shell 进程,进程创建开销累积,加上没有事务包裹,每次写入都触发磁盘同步。
解决:合并成单条多值INSERT,或者用BEGIN;...COMMIT;包住批量语句,一次 shell 调用完成。实测几百条合并后耗时从秒级降到毫秒级。
4.5 现象:并发操作时数据库被锁,报 database is locked
原因:多个 shell 进程同时写同一个库,sqlite 默认的锁机制会让后到的写操作等待或直接失败。
解决:脚本层面串行化数据库操作,避免并发写;必要时在连接时设置忙等待超时,或把写操作集中到一个队列里顺序执行。
5. 进阶:把 sqlite 操作封装成 AutoJs 可复用模块
5.1 封装一个带错误处理的查询函数
零散拼命令迟早会乱,把常用操作封装成函数是让脚本能长期维护的关键。下面这个封装把路径、分隔符、错误检查都收进去,调用方只关心 SQL 和参数。
var DB = "/sdcard/脚本项目/data/app.db"; function dbQuery(sql) { var cmd = "sqlite3 -separator '|' -noheader " + DB + " \"" + sql + "\""; var r = shell(cmd, false); if (r.code !== 0) { console.error("查询失败:", r.error); return []; } var out = r.result.trim(); if (out === "") return []; return out.split("\n").map(function(line) { return line.split("|"); }); } function dbExec(sql) { var r = shell("sqlite3 " + DB + " \"" + sql + "\"", false); if (r.code !== 0) { console.error("执行失败:", r.error); return false; } return true; }逻辑说明:dbQuery统一用|作分隔符并返回二维数组,dbExec用于建表、插入、更新、删除这类不取结果的语句。参数说明:两个函数都把错误打到日志并返回安全默认值(空数组或false),调用方不用每次判断code。这套封装在多个脚本里复用后,数据库相关 bug 明显减少。
5.2 用事务包住批量写入并验证影响行数
批量写入要快要稳,事务是标配。更进一步,写入后可以用changes()确认实际影响行数,避免“以为写进去了其实没有”。
function dbBatchInsert(rows) { var values = rows.map(function(row) { return "('" + row[0].replace(/'/g, "''") + "'," + row[1] + ",'" + row[2] + "')"; }).join(","); var sql = "BEGIN; INSERT INTO task_log (task_name,status,created_at) VALUES " + values + "; SELECT changes(); COMMIT;"; var r = shell("sqlite3 " + DB + " \"" + sql + "\"", false); if (r.code !== 0) { console.error("批量插入失败:", r.error); return -1; } return parseInt(r.result.trim()) || 0; }逻辑说明:BEGIN和COMMIT把整批写入包成一个事务,SELECT changes()返回本次事务影响的行数,用来校验是否和预期一致。参数说明:replace(/'/g, "''")是文本转义,防止单引号破坏语句。返回-1表示失败,返回数字表示实际写入行数,调用方据此判断要不要重试。
5.3 验证方法:用 db browser for sqlite 在电脑端核对
脚本写完后,把.db文件从设备拉到电脑,用 db browser for sqlite 打开,能直观看到表结构和数据,比在设备上反复查询高效得多。常见做法是脚本跑一轮后拉文件核对,确认字段类型、默认值、时间格式都符合预期。这一步能提前发现很多“脚本里看着对、实际存错了”的问题,比如时间戳格式不统一、状态字段存成了文本。
5.4 一个具体技巧:给数据库文件加个备份后悔药
操作数据库最怕误删误改,脚本里加一个备份动作成本极低。每次批量写之前,先cp一份带时间戳的副本,出问题能回滚。
function backupDb() { var ts = new Date().getTime(); var bak = DB + "." + ts + ".bak"; var r = shell("cp " + DB + " " + bak, false); return r.code === 0 ? bak : null; }逻辑说明:备份文件名带毫秒时间戳,避免覆盖。参数说明:cp在文件较大时也有开销,建议只在批量写或删除前调用,不要每次查询都备份。备份文件积累多了要定期清理,否则占用存储。
我自己做 AutoJs 数据库这块,最大的教训是早期图省事不检查退出码,结果脚本跑了一周才发现数据根本没写进去,全靠日志里一行被忽略的error才定位到。后来养成习惯:任何 shell 调用先看code,批量操作先备份,SQL 拼完先打印再执行。这套流程看着啰嗦,但省下的排查时间远超那几行代码。希望帮到你。
本文还有配套的精品资源,点击获取