用go语言编写连接sqlite的CLI,支持如下功能,1.输入create /insert/update/delete/select语句,成功返回影响行数,失败提示错误。2.输出结果时,.header on/off可以控制列名是否显示。.mode table/csv控制输出文本表格还是逗号风格。.read 文件名可以批量执行文件中的多个SQL语句。.maxrows 行数可以控制最大显示行数,默认10行。3.用.timer on/off控制输出按毫秒计算的计时。
他的输出如下
这是一个用 Go 语言实现的 SQLite CLI 工具,覆盖了你要求的 SQL 执行反馈、输出格式控制、批量执行和计时功能。代码基于成熟的 mattn/go-sqlite3 驱动,并利用 database/sql 标准库来区分查询类(SELECT)与非查询类(CREATE/INSERT/UPDATE/DELETE)语句。
核心实现代码
你可以将以下代码保存为 main.go,编译或直接运行。
go
package main
import (
"bufio"
"database/sql"
"encoding/csv"
"fmt"
"os"
"strconv"
"strings"
"text/tabwriter"
"time"
_ "github.com/mattn/go-sqlite3"
)
// 全局配置状态
var (
showHeader = false // .header 默认关闭
outputMode = "table" // .mode 默认 table
maxRows = 10 // .maxrows 默认 10
timerOn = false // .timer 默认关闭
db *sql.DB
)
func main() {
if len(os.Args) < 2 {
fmt.Println("用法: ./sqlite-cli <数据库文件>")
os.Exit(1)
}
var err error
// 连接 SQLite 数据库
db, err = sql.Open("sqlite3", os.Args[1])
if err != nil {
fmt.Printf("打开数据库失败: %v\n", err)
os.Exit(1)
}
defer db.Close()
// 验证连接
if err := db.Ping(); err != nil {
fmt.Printf("连接数据库失败: %v\n", err)
os.Exit(1)
}
reader := bufio.NewReader(os.Stdin)
fmt.Println("SQLite CLI (输入 .help 查看帮助, 输入 .exit 退出)")
for {
fmt.Print("sqlite> ")
line, err := reader.ReadString('\n')
if err != nil {
break // EOF
}
line = strings.TrimSpace(line)
if line == "" {
continue
}
// 处理点命令
if strings.HasPrefix(line, ".") {
if handleDotCommand(line) {
break // 退出
}
continue
}
// 执行 SQL
executeSQL(line)
}
}
// handleDotCommand 返回 true 表示退出程序
func handleDotCommand(cmd string) bool {
parts := strings.Fields(cmd)
if len(parts) == 0 {
return false
}
switch parts[0] {
case ".exit", ".quit":
return true
case ".help":
printHelp()
case ".header":
if len(parts) > 1 {
if parts[1] == "on" {
showHeader = true
} else if parts[1] == "off" {
showHeader = false
}
}
case ".mode":
if len(parts) > 1 {
if parts[1] == "table" || parts[1] == "csv" {
outputMode = parts[1]
} else {
fmt.Println("未知模式,支持: table, csv")
}
}
case ".maxrows":
if len(parts) > 1 {
if n, err := strconv.Atoi(parts[1]); err == nil && n >= 0 {
maxRows = n
} else {
fmt.Println("请输入有效的非负整数")
}
}
case ".timer":
if len(parts) > 1 {
if parts[1] == "on" {
timerOn = true
} else if parts[1] == "off" {
timerOn = false
}
}
case ".read":
if len(parts) > 1 {
readFile(parts[1])
} else {
fmt.Println("请指定文件名")
}
default:
fmt.Printf("未知命令: %s\n", parts[0])
}
return false
}
func printHelp() {
fmt.Println(`可用命令:
.header on|off 显示/隐藏列名 (默认 off)
.mode table|csv 设置输出格式 (默认 table)
.maxrows N 设置最大显示行数 (默认 10)
.timer on|off 开启/关闭计时器 (默认 off)
.read <文件> 批量执行文件中的 SQL
.exit / .quit 退出`)
}
// executeSQL 执行单条 SQL 并处理结果
func executeSQL(query string) {
start := time.Now()
// 简单判断是否为查询语句(忽略大小写和首尾空格)
upperQuery := strings.ToUpper(strings.TrimSpace(query))
isQuery := strings.HasPrefix(upperQuery, "SELECT") || strings.HasPrefix(upperQuery, "PRAGMA")
if isQuery {
rows, err := db.Query(query)
if err != nil {
fmt.Printf("错误: %v\n", err)
return
}
defer rows.Close()
printRows(rows)
} else {
// CREATE, INSERT, UPDATE, DELETE 等
result, err := db.Exec(query)
if err != nil {
fmt.Printf("错误: %v\n", err)
return
}
affected, _ := result.RowsAffected()
fmt.Printf("影响行数: %d\n", affected)
}
if timerOn {
elapsed := time.Since(start)
fmt.Printf("耗时: %d ms\n", elapsed.Milliseconds())
}
}
// printRows 根据当前配置输出查询结果
func printRows(rows *sql.Rows) {
cols, err := rows.Columns()
if err != nil {
fmt.Printf("获取列名失败: %v\n", err)
return
}
// 准备扫描容器
values := make([]interface{}, len(cols))
scanArgs := make([]interface{}, len(cols))
for i := range values {
scanArgs[i] = &values[i]
}
// 收集数据(受 maxRows 限制)
var allData [][]string
count := 0
for rows.Next() {
if maxRows > 0 && count >= maxRows {
break // 达到最大显示行数
}
if err := rows.Scan(scanArgs...); err != nil {
fmt.Printf("扫描行失败: %v\n", err)
return
}
rowData := make([]string, len(cols))
for i, v := range values {
if v == nil {
rowData[i] = "NULL"
} else {
rowData[i] = fmt.Sprintf("%v", v)
}
}
allData = append(allData, rowData)
count++
}
// 根据模式输出
if outputMode == "csv" {
printCSV(cols, allData)
} else {
printTable(cols, allData)
}
// 检查是否有更多行未显示
if maxRows > 0 {
var extra int
for rows.Next() {
extra++
}
if extra > 0 {
fmt.Printf("... (还有 %d 行未显示,使用 .maxrows 调整)\n", extra)
}
}
}
func printTable(cols []string, data [][]string) {
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
if showHeader {
fmt.Fprintln(w, strings.Join(cols, "\t"))
// 打印分隔线
separators := make([]string, len(cols))
for i := range cols {
separators[i] = strings.Repeat("-", len(cols[i]))
}
fmt.Fprintln(w, strings.Join(separators, "\t"))
}
for _, row := range data {
fmt.Fprintln(w, strings.Join(row, "\t"))
}
w.Flush()
}
func printCSV(cols []string, data [][]string) {
w := csv.NewWriter(os.Stdout)
defer w.Flush()
if showHeader {
w.Write(cols)
}
for _, row := range data {
w.Write(row)
}
}
// readFile 批量执行文件中的 SQL
func readFile(filename string) {
file, err := os.Open(filename)
if err != nil {
fmt.Printf("无法打开文件: %v\n", err)
return
}
defer file.Close()
scanner := bufio.NewScanner(file)
var sb strings.Builder
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
// 跳过空行和注释
if line == "" || strings.HasPrefix(line, "--") {
continue
}
sb.WriteString(line)
sb.WriteString(" ")
// 如果行以分号结尾,执行累积的 SQL
if strings.HasSuffix(line, ";") {
query := strings.TrimSpace(sb.String())
query = strings.TrimSuffix(query, ";")
if query != "" {
fmt.Printf("执行: %s\n", truncate(query, 60))
executeSQL(query)
}
sb.Reset()
}
}
if err := scanner.Err(); err != nil {
fmt.Printf("读取文件错误: %v\n", err)
}
}
func truncate(s string, max int) string {
if len(s) > max {
return s[:max] + "..."
}
return s
}
功能说明
| 功能 | 实现方式 |
|---|---|
| SQL 执行与影响行数 | 通过判断语句前缀区分 SELECT/PRAGMA(走 Query)和其他语句(走 Exec)。Exec 后调用 RowsAffected() 获取影响行数。 |
| .header on/off | 全局变量 showHeader 控制 printTable 和 printCSV 是否输出列名行。 |
| .mode table/csv | printTable 使用 text/tabwriter 实现对齐;printCSV 使用 encoding/csv。 |
| .read 文件名 | 逐行读取文件,以分号 ; 作为语句分隔符,累积后执行,支持多行 SQL。 |
| .maxrows 行数 | 默认 10。在 printRows 的扫描循环中计数,达到上限即停止,并提示剩余未显示行数。 |
| .timer on/off | 在 executeSQL 前后记录 time.Now(),用 time.Since().Milliseconds() 输出毫秒数。 |
依赖与编译
驱动 :项目依赖 github.com/mattn/go-sqlite3,它需要 CGO 环境(如 gcc 编译器)。
bash
# 初始化模块并获取驱动
go mod init sqlite-cli
go get github.com/mattn/go-sqlite3
# 编译(确保 CGO_ENABLED=1)
CGO_ENABLED=1 go build -o sqlite-cli main.go
运行示例:
bash
./sqlite-cli test.db
在交互界面中,你可以先建表,然后测试各种 . 命令组合。例如,执行 .read script.sql 会依次执行文件中的建表、插入语句,并打印每条的影响行数。
编译和测试运行结果如下
C:\Users\lt>cd\d\goprj
C:\d\goprj>set GOROOT=C:\d\gowin\go
C:\d\goprj>set GOPATH=c:\d\goprj
C:\d\goprj>set path=%GOROOT%\bin;%GOPATH%\bin;%PATH%;C:\d\mingw64\bin
C:\d\goprj>set CGO_ENABLED=1
C:\d\goprj>go build -o sqlite-cli.exe testcli.go
C:\d\goprj>sqlite-cli test.db
SQLite CLI (输入 .help 查看帮助, 输入 .exit 退出)
sqlite> .help
可用命令:
.header on|off 显示/隐藏列名 (默认 off)
.mode table|csv 设置输出格式 (默认 table)
.maxrows N 设置最大显示行数 (默认 10)
.timer on|off 开启/关闭计时器 (默认 off)
.read <文件> 批量执行文件中的 SQL
.exit / .quit 退出
sqlite> select * from users;
1 张三 30
2 李四 25
sqlite> .timer on
sqlite> select sum(value) from generate_series(1,10000000);
错误: no such table: generate_series
sqlite> .maxrows 1
sqlite> select * from users;
1 张三 30
耗时: 0 ms
sqlite> .header on
sqlite> select * from users;
id name age
-- ---- ---
1 张三 30
耗时: 0 ms
sqlite> .mode csv
sqlite> select * from users;
id,name,age
1,张三,30
耗时: 0 ms
sqlite> drop table users;
影响行数: 0
耗时: 27 ms
sqlite> select * from users;
错误: no such table: users
sqlite> .read schema.txt
执行: CREATE TABLE IF NOT EXISTS users ( id INTEGER PRIMARY KEY AU...
影响行数: 0
耗时: 0 ms
执行: INSERT INTO users(name, age) VALUES('张三', 30)
影响行数: 1
耗时: 0 ms
执行: INSERT INTO users(name, age) VALUES('李四', 25)
影响行数: 1
耗时: 0 ms
sqlite> select * from users;
id,name,age
1,张三,30
耗时: 1 ms
sqlite>
```