欢迎光临散文网 会员登陆 & 注册

Linux环境自用文本数据库Camelia山茶花实现介绍

2022-08-20 01:47 作者:纤凌依  | 我要投稿

github链接:https://github.com/raine01/databaseCamelia

说它是数据库有些抬举它了。山茶花只是一个shell脚本,把一些信息存储进文本文件。类似的项目网上可能一搜一把,但想着好不容易写出来了别浪费掉,就放上来了~万一有人喜欢呢

起因是我的低配云服务器需要存储点东西,为了这不到十万行的东西装个数据库有点没必要。想起前几天亲友用csv存东西,感觉自己也可以参考着做一个

由于时间长了自己都会混淆,所以先来明确些概念

:由于有些数据希望保留每一次变更记录(这些key对应的value不常修改),另一些只需要保留最新的状态(这些key对应的value经常修改),或者同一个key希望保留多份不同的value。因此我引入了这个概念。每个山茶花生成的具有不同前缀的记录数据的文件称为一个

版本:由于山茶花的修改与删除不是真正意义上的修改删除,只是放了特殊标识用以区分的插入操作,因此变动的越频繁就越容易产生废数据。我的应对策略是隔一段时间手动执行一次tar操作,把每个key的最新版本copy进一个新的文件,最后执行del操作的数据则不考虑(似乎大家都是类似的做法)。同一个每次压缩后生成的文件我称为一个版本

选项-u-d-hv都是山茶花的选项

原始库名:库可以被多次压缩,最初的未被压缩的库名(不带有下划线与数字)被称为原始库名

版本库名原始库名_版本号,且该版本是数次压缩后的最新版本,被我称为版本库名

下面是data某个版本的一段(可以把它当成全部),存储数据的结构大概像这样:

第一行以#开头,之后是这个版本的创建时间(2022年1月3日18:19:48)版本名(原始库名或原始库名后跟着下划线与数字),以及创建这个库的用户。除了留痕以外还可以用于校验(后文会提到)

下面每一行都是相同的结构:key,option(i表示插入/更新,d表示删除),value,操作时间

注释中的这一行本身是一个示例,其中Camelia是这个可执行文件自身。

我通常喜欢跳转至山茶花所在的目录,使用

chmod 777 Camelia

赋权。然后使用类似这样的命令(加上./意味着执行)

./Camelia testdb -i key1 value1 -i key2 value2 -i key1 Meow key1 key2

如果你像我一样不熟悉shell,那么你也可以像我一样使用这些命令

粗略解释下:

testdb原始库名

-i key1 value1插入一条数据,key是key1,value是value1

-i key2 value2与上面类似

-i key1 Meow与前面两个的操作相同,不过就结果而言这个是更新操作,将key1对应的value修改为了Meow

key1打印key1的值

key2打印key2的值

跳过前面的一堆注释和函数,我们先来看看后面的部分:

一个变量tname

一个变量Cameliatime,值为特定格式的当前时间,如220204014719(22年02月4日1点47分19秒)

执行脚本中名为help_tname的函数,函数的参数是上方示例中的第一个参数,即testdb这个词

shift,一个对于参数来说的出栈操作,原本的第一个参数没了(但是没关系,它已经完成它的任务了),第二个变第一个,第三个变第二个,以此类推

while [ -n "$1" ]把参数1变成字符串,如果这个字符串不为null,长度大于0,那么执行下面的部分

do表示这个while函数体开始,done表示这个while函数体结束

case $1 in匹配第一个参数(上方的示例中是-itestdb已经被shift掉了),看看是下面这些中的哪一个

-u|-i) ins_del $2,i,$3,$Cameliatime如果是-u或者-i,执行ins_del这个函数,参数是三个变量和其余部分拼接起来的字符串(第一次循环中拼接起来的字符串是key1,i,value1,220204014719

;;两个分号表示跳出case...esac代码块

后面的几行大同小异。特别说一下*)表示匹配任意的选项。if [[ $1 == -* ]]如果这个选项是以短横线开头,那么提示异常信息然后退出,状态码不是通常的0而是1。不以短横线开头就执行sel函数,并且把选项本身作为参数。也就是说,不加短横线的选项其实就是查询key为该选项的value的值

这几行代码的缩进很糟糕,可能是因为我当时调整了vim关于tab的设置。我不打算修改

按上方的出场顺序介绍各个函数中的一些知识点

help_tname()

    $#:参数的个数,这个条件语句的意思是表示如果没有选项或者第一个选项为--help,那么awk -F'### ' '/^###/ { print $2 }' "$0"会筛选###开头的行,并用'### '分割这些行(山茶花开头的那些注释行),打印这些行用'### '分割后的第二部分。那么这个awk命令读取的是哪个文件呢?没有默认自己读自己这种设定,awk读取的文件是$0这个参数的值,也就是文件本身的文件名

    往下,是另一个逻辑判断。参考下方开头看过的示例,这里应该是一个原始库名。所以会有"Honey,don't call a table $1"的提示

接下来,山茶花需要确定一下版本库名

tname=$(ls|egrep "^$1(_[1-9]\d*)?$"|tail -1)ls展示当前目录所有的文件、管道传给egrep命令(grep命令的变体,同grep -e,意为使用正则搜索)筛选。这个文件以$1开头(在示例中为testdb)、后面可能跟着下划线和若干非0开头的数字并以之结尾。管道将筛选出的一大串文件名传递给tail命令,并留下最后一个作为版本库名

我来举个例子:我在放着很多我破烂的路径下创建了一个,名为db。并在之后使用-tar选项压缩了五次。

现在目录下有如下文件

egrep之后剩下了这些

tail -1之后剩下的是

这个就是版本库名了~

if [[ -z $tname ]]; then,如果版本库名为空(说白了就是库还没建立)

那么新建一个,并写入留痕信息"#$Cameliatime|$1|${USER}"并重新给代表库名的变量赋值

这三个$符开头的都是变量(前面也提到过),分别是当前时间、版本库名、当前用户。这三个变量以及竖线拼接成一个字符串,构成了每个版本的第一行

与上方if相对的,如果版本库名不为空,那么进行校验:

local tnamesign=$(head -n 1 $tname|cut -d "|" -f 2)

local用于定义局部变量,作用域为函数内部

head命令显示文件的开头部分,这里显示的是名为$name这个变量的文件的第一行

cut用于截取,-d用于指定分隔符,-f表示选取分隔后的第几个(常和-d连用)

这行命令的意思是截取版本库名(假设它是版本库名)这个文件的第一行,并且取出第一行用竖线分隔后的第二部分,这个第二部分对于山茶花的任意一个版本来说就是这个文件的文件名。这个校验对于总写错原始库名的我来说格外有用

最后我们校验一下,如果文件第一行竖线隔开的第二个字符串和你的原始库名不同,那么它就不是山茶花某个库的某个版本。我们打印提示信息,然后返回-1

现在山茶花确定版本库名了,存储这个版本库名的变量整个shell脚本内均可以使用

ins_del()

几个字符串拼接起来,然后追加到版本库名的最后一行。>表示替换,>>表示追加

sel()

taccat刚好相反,也就是倒着找。我们找这个文件以相应key开头的第一行,并且用i来过滤(如前文所说,i表示插入/更新,d表示删除。如果找到的最新一行是d开头的,那么表示该key对应的value已经被逻辑删除了,逻辑删除时我们返回空)

selhistory()

传入的参数是key-value数据的key值,我们使用grep搜索版本库名这个文件,查找以key开头的行,并附上一个逗号以免某个key值是另一个key值的开头。我们将搜索出来的行用逗号分隔并取出第2、3、4个参数

使用./Camelia data -h card命令后会显示

表示在这些时间执行的都是插入(更新)操作

selhisval()

与上方相同,但是只显示第三列

./Camelia data -hv card的结果如上

selhiscount()

与上面一样,但是多了一个分类计数功能。我们来粗略地看一下这个awk命令:

经过前面的处理后获取的东西会被awk视为一行

NF表示列数(默认空格分隔)

$i表示这一列的内容

END后面跟的是处理完所有行后执行的操作,我们在END前后用两个大括号把每行需要执行的操作和全部处理完后进行的操作包起来

先来看每行都需要执行的操作for(i=1;i<=NF;i++)a[$i]++。遍历每一列,并且把角标为这列内容的项的值加一,例如

处理完后大概像是这样:

再来看后半截for(x in a)print x,a[x],这次是遍历刚刚创建的数组,逐项打印他们的角标(索引),后面跟着该项的值(在这里就是他们出现过的次数)

./Camelia data -hc card的结果如上(是的,awk中输入逗号,打印出来的是空格),如果你想统计你游戏职业各种卡牌出现的频率、某个客人光顾你店的次数等,这会是个很实用的功能

tar()

为了避免你的随着不断的使用效率下降的过于离谱,山茶花加入了压缩功能。逻辑上来说就是对于相同的key只保留最新的一行

我们粗略看看它是怎么工作的:

暂存当前的版本库名

%分隔tname这个变量,获取前半部分

#获取后半部分

(如果用db_5举例子,前半部分就是db,后半部分就是5)

index加一(5变成6)

更新储存版本库名的变量,然后把校验信息写入

tail命令筛选版本库名文件第二行至末尾的部分(第一行是校验信息不处理),并交给awk处理

awk命令创建一个数组a,将每行内容赋值给数组中角标为key的项(数组内容不能重复,这里相当于一个去重操作),然后再把每一行追加进新的版本库名文件中

(这里的tail不能替换为tac,因为awk是逐行处理的,而我们的思路是用新的顶掉旧的。如果用tac命令,我们将会保留最旧的数据)

直接退出,不给你继续操作的机会。我忘了我为什么直接退出了,可能是不推崇频繁的压缩操作?

本人linux相关的知识其实蛮匮乏的,还望共同讨论不吝赐教,比心~❤

下面放上完整的脚本




Linux环境自用文本数据库Camelia山茶花实现介绍的评论 (共 条)

分享到微博请遵守国家法律