分类 Linux 下的文章

今天带两个供应商干活,以这俩人的踩坑记录把dockfile初学者可能会犯的错误罗举一下

  1. from,一定要写基础镜像(没错就是这么难绷
  2. run后面不要使用交互式的命令,如vim\不加-y的yum
  3. copy,复制的时候注意dockerfile和目标文件的层级关系,最好同级
  4. copy,如果想复制一整个文件夹,包括他自己本身,那么目的目录应该在结尾以这个文件夹的文件名为结尾,不然默认只复制文件夹里面的东西
  5. cmd,cmd是镜像启动的后执行的命令,把运行服务的命令写在run里面是没用的
  6. cmd或者run的时候要注意对应文件的权限问题,以及所使用的shell的区别
  7. 容器判断该容器是否结束退出,是通过cmd是否执行并打印0退出来判断的,不是通过后台是否有进程来决定的。如果cmd用java跑个程序那还好,如果是通过一个脚本来启动一个java程序,那么只要这个脚本被成功运行并退出了,哪怕java程序还在运行,容器也会觉得这个容器已经结束了。所以如果是通过脚本来启动服务,最好后面跟个死循环的命令,随你tail -f或者sleep睡个天荒地老都行
  8. 在cicd跑的时候注意代码文件有没有传全
  9. 最好把run一条用&&拼凑写完,run越多镜像的层数越多,构建出来的镜像越大
  10. 哪怕切了目录,也得用绝对路径

unix使用整数来追踪打开的文件,我们把这个整数叫做文件描述符(file descriptors)
image.png

运行中的进程可以查看/proc/pid/fd来看他占用的文件描述符,当然也有更简单的方法,使用lsof指令就可以查看与文件有关的信息

-a file:查看文件占用(-a加不加都行

[root@master 8432]# lsof -a /var/lib/docker/volumes/metadata.db
COMMAND  PID USER   FD   TYPE DEVICE SIZE/OFF    NODE NAME
dockerd 8432 root  mem-W  REG  253,1    32768 1183817 /var/lib/docker/volumes/metadata.db
dockerd 8432 root   10uW  REG  253,1    32768 1183817 /var/lib/docker/volumes/metadata.db

-n dev:查看磁盘占用
-p pid:查看进程打开的文件
lsof -i:port 获取端口占用

概念

mongoDB是一个nosql,也就是非关系型数据库,其使用一种类似JSON的BSON结构来记录数据,相较于mysql这种关系型数据库的库-表-行结构,mongoDB对应采用的是库-collection-document,文档没有固定的格式,但是一般放在同一个集合里的文档格式会比较统一
image.png

部署

win:
安装后在data创建db和log两个文件夹
在log创建mongo.log
将bin加到环境变量里
mongod --dbpath 刚创建的db路径
访问http://127.0.0.1:27017/查看是否启动成功
开机自启命令示例
mongod --dbpath "E:\MongoDB\data\db" --logpath "E:\MongoDB\data\log\mongo.log" -install -serviceName "MongoDB"

操作

基础

登录
mongo --port 27017 如果这个不行,用mongosh,如果都不行,先去下载mongoshell再mongosh
如果是远程连接
mongo "mongodb://mongodb0.example.com:28015"
mongo --host mongodb0.example.com:28015
mongo --host mongodb0.example.com --port 28015
使用某个集合(数据库),如果该集合不存在,则会被创建,需要注意的是,集合只有在被写入文档后才会真实存在,admin,local,config为保留数据库
use collectionname
db.myNewCollection1.insertOne( { x: 1 } ) #向collectionname库中myNewCollection1集合插入文档,如果库和集合不存在,则创建
查看库
show dbs
关闭服务
db.shutdownServer()
查看当前所在库
db
删库
db.dropDatabase()

数据类型

String − 这是存储数据最常用的数据类型。字符串在 MongoDB 必须是UTF-8有效。

Integer − 此类型用于存储数值。整数可以是32位 或 64位,具体取决于您的服务器。

Boolean −此类型用于存储布尔(true / false)值。

Double − 此类型用于存储浮点值。

Min/ Max keys −该类型用于将值与最低和最高的BSON元素进行比较。

Arrays −此类型用于将数组或列表或多个值存储到一个键中。

Timestamp− ctimestamp(时间戳)。当文档被修改或添加时,这可以方便地进行记录。

Object −此数据类型用于嵌入式文档。

Null −此类型用于存储 Null 值。

Symbol−此数据类型与字符串使用相同;但是,它通常是为使用特定符号类型的语言保留的。

Date − 此数据类型用于以UNIX时间格式存储当前日期或时间。您可以通过创建 Date 对象并将日期,月份,年份递到其中来指定自己的日期时间。

Object ID −此数据类型用于存储文档的ID。

Binary data −此数据类型用于存储二进制数据。

Code −此数据类型用于将JavaScript代码存储到文档中。

Regular expression −此数据类型用于存储正则表达式。

crud

集合

创建集合
db.createCollection("name")
查看集合
show tables/collections
删除集合
db.collection.drop()

文档

插入文档(如果集合不存在,则会创建)
db.collection.insert() 如果_id不存在,创建,如果已存在,报错
即将被insertOne()\insertMany()代替!

db.collection.insert(
<document or array of documents>,
{
writeConcern: <document>,
ordered: <boolean>
}
)
db.collection.insertMany(
[ <document 1> , <document 2>, ... ],
{
writeConcern: <document>,
ordered: <boolean>
}
)

document:要写入的文档。
writeConcern:写入策略,默认为 1,即要求确认写操作,0 是不要求。
ordered:指定是否按顺序写入,默认 true,按顺序写入。

将创建的文档结构如下

字段名称字段含义类型备注
_idIDobjectid或string主键,不设置会自动创建
project项目名string
owner项目所有人string
ownerid项目所有人idstring
om运维arrays
omid运维人员idarrays
editdate最后修改日期date
priority重要等级stringp0-p4
db.testcol.insert({
project:"cmdb",
owner:"foo",
ownerid:"000001",
om:["chiruno","test02"],
omid:["000009","000002"],
editdate:new Date(),
priority:"p0"
})

查找文档
db.col.find()
db.col.find({key:value})

testdb> db.testcol.find()
[
  {
    _id: ObjectId("645b4498118a3c99d6a3ffe0"),
    project: 'cmdb',
    owner: 'foo',
    ownerid: '000001',
    om: [ 'chiruno', 'test02' ],
    omid: [ '000009', '000002' ],
    editdate: ISODate("2023-05-10T07:15:36.942Z"),
    priority: 'p0'
  }
]

testdb> db.testcol.find({project:"cmdb"})
[
  {
    _id: ObjectId("645b4498118a3c99d6a3ffe0"),
    project: 'cmdb',
    owner: 'foo',
    ownerid: '000001',
    om: [ 'chiruno', 'test02' ],
    omid: [ '000009', '000002' ],
    editdate: ISODate("2023-05-10T07:15:36.942Z"),
    priority: 'p0'
  }
]

查询文档,但是只显示部分字段

查询所有,但只显示project,owner,_id三个字段

testdb> db.testcol.find({},{project:1,owner:1})
[
  {
    _id: ObjectId("645b4498118a3c99d6a3ffe0"),
    project: 'cmdb',
    owner: 'foo'
  }
]

查询所有,但是只显示project,owner两个字段

testdb> db.testcol.find({},{project:1,owner:1,_id:0})
[ { project: 'cmdb', owner: 'foo' } ]

更新文档
update

db.collection.update(
   <query>,
   <update>,
   {
     upsert: <boolean>,
     multi: <boolean>,
     writeConcern: <document>
   }
)
query : update的查询条件,类似sql update查询内where后面的。
update : update的对象和一些更新的操作符(如$,$inc...)等,也可以理解为sql update查询内set后面的
upsert : 可选,这个参数的意思是,如果不存在update的记录,是否插入objNew,true为插入,默认是false,不插入。
multi : 可选,mongodb 默认是false,只更新找到的第一条记录,如果这个参数为true,就把按条件查出来多条记录全部更新。
writeConcern :可选,抛出异常的级别。

示例:
现有的文档:

testdb> db.testcol.find()
[
  {
    _id: ObjectId("645b4498118a3c99d6a3ffe0"),
    project: 'cmdb',
    owner: 'foo',
    ownerid: '000001',
    om: [ 'chiruno', 'test02' ],
    omid: [ '000009', '000002' ],
    editdate: ISODate("2023-05-10T07:15:36.942Z"),
    priority: 'p0'
  },
  {
    _id: ObjectId("645b5bcc118a3c99d6a3ffe1"),
    project: 'prometheus',
    owner: 'eda',
    ownerid: '000003',
    om: [ 'chiruno' ],
    omid: [ '000009' ],
    editdate: ISODate("2023-05-10T08:54:36.328Z"),
    priority: 'p1'
  }
]

现修改prometheus的om和omid,部分修改使用$set,不然会覆盖原文档,默认只修改匹配到的第一个文档,如需贪婪,使用{multi:true}

testdb> db.testcol.update({project:"prometheus"},{$set:{om:['chiruno','reisen'],omid:['000009','000004']}})
{
  acknowledged: true,
  insertedId: null,
  matchedCount: 1,
  modifiedCount: 1,
  upsertedCount: 0
}
testdb> db.testcol.find({project:'prometheus'})
[
  {
    _id: ObjectId("645b5bcc118a3c99d6a3ffe1"),
    project: 'prometheus',
    owner: 'eda',
    ownerid: '000003',
    om: [ 'chiruno', 'reisen' ],
    omid: [ '000009', '000004' ],
    editdate: ISODate("2023-05-10T08:54:36.328Z"),
    priority: 'p1'
  }
]

整数增减
假设现有文档如下

db.products.insertOne(
   {
     _id: 1,
     sku: "abc123",
     quantity: 10,
     metrics: { orders: 2, ratings: 3.5 }
   }
)

现在相对其中orders,quality做整数加减法

db.products.updateOne(
   { sku: "abc123" },
   { $inc: { quantity: -2, "metrics.orders": 1 } }
)

删除文档
delectOne,delectMany,remove(将被取消)

testdb> db.testcol.remove({project:'prometheus'})
DeprecationWarning: Collection.remove() is deprecated. Use deleteOne, deleteMany, findOneAndDelete, or bulkWrite.
{ acknowledged: true, deletedCount: 1 }
如删除集合下全部文档:

db.inventory.deleteMany({})
删除 status 等于 A 的全部文档:

db.inventory.deleteMany({ status : "A" })
删除 status 等于 D 的一个文档:

db.inventory.deleteOne( { status: "D" } )

统计与高级查询

翻页

统计所有的记录数
countDocuments or estimatedDocumentCount

testdb> db.testcol.countDocuments()
2

限制返回的条数(分页)
db.COLLECTION_NAME.find().limit(n)
跳过前n条满足条件的查询结果
db.comment.find().skip(n)

合起来的使用效果(翻页)

//第一页
testdb> db.testcol.find().limit(2).skip(0)
[
  {
    _id: ObjectId("645b4498118a3c99d6a3ffe0"),
    project: 'cmdb',
    owner: 'foo',
    ownerid: '000001',
    om: [ 'chiruno', 'test02' ],
    omid: [ '000009', '000002' ],
    editdate: ISODate("2023-05-10T07:15:36.942Z"),
    priority: 'p0'
  },
  {
    _id: ObjectId("645b6621118a3c99d6a3ffe2"),
    project: 'prometheus',
    owner: 'eda',
    ownerid: '000003',
    om: [ 'chiruno' ],
    omid: [ '000009' ],
    editdate: ISODate("2023-05-10T09:38:41.712Z"),
    priority: 'p1'
  }
]
//第二页
testdb> db.testcol.find().limit(2).skip(1)
[
  {
    _id: ObjectId("645b6621118a3c99d6a3ffe2"),
    project: 'prometheus',
    owner: 'eda',
    ownerid: '000003',
    om: [ 'chiruno' ],
    omid: [ '000009' ],
    editdate: ISODate("2023-05-10T09:38:41.712Z"),
    priority: 'p1'
  },
  {
    _id: ObjectId("645b6904118a3c99d6a3ffe3"),
    project: 'thanos',
    owner: 'eda',
    ownerid: '000003',
    om: [ 'chiruno' ],
    omid: [ '000009' ],
    editdate: ISODate("2023-05-10T09:51:00.119Z"),
    priority: 'p2'
  }
]

排序

排序查询
可以指定字段排序,1为升序-1为降序,可以组合多个字段使用
db.col.find().sort({key:1/-1,key2:1/-1})

testdb> db.testcol.find().sort({ownerid:-1})
[
  {
    _id: ObjectId("645b6929118a3c99d6a3ffe4"),
    project: 'grafana',
    owner: 'reisen',
    ownerid: '000004',
    om: [ 'chiruno' ],
    omid: [ '000009' ],
    editdate: ISODate("2023-05-10T09:51:37.737Z"),
    priority: 'p3'
  },
  {
    _id: ObjectId("645b6621118a3c99d6a3ffe2"),
    project: 'prometheus',
    owner: 'eda',
    ownerid: '000003',
    om: [ 'chiruno' ],
    omid: [ '000009' ],
    editdate: ISODate("2023-05-10T09:38:41.712Z"),
    priority: 'p1'
  },
...

正则

正则
find({key:/parten/})

testdb> db.testcol.find({owner:/^rei/})
[
  {
    _id: ObjectId("645b6929118a3c99d6a3ffe4"),
    project: 'grafana',
    owner: 'reisen',
    ownerid: '000004',
    om: [ 'chiruno' ],
    omid: [ '000009' ],
    editdate: ISODate("2023-05-10T09:51:37.737Z"),
    priority: 'p3'
  }
]

值比较

值比较
写法跟shell差不多

$lt小于
$lte小于等于
$gt大于
$gte大于等于
$ne等于

find({key:{$lt:value}}) 查找大于某值的文档

包含

包含查询
find(key:{$in:["abc","edf"]}) 查找包含有abc和edf的

testdb> db.testcol.find({om:{$in:['sanae']}})
[
  {
    _id: ObjectId("645b6904118a3c99d6a3ffe3"),
    project: 'thanos',
    owner: 'eda',
    ownerid: '000003',
    om: [ 'sanae' ],
    omid: [ '000008' ],
    editdate: ISODate("2023-05-10T09:51:00.119Z"),
    priority: 'p2'
  }
]

多条件

条件连接查询
find({$and/$or:[{条件a},{条件b},{},{}...]})

testdb> db.testcol.find({$and:[{om:{$in:['chiruno']}},{priority:'p0'}]})
[
  {
    _id: ObjectId("645b4498118a3c99d6a3ffe0"),
    project: 'cmdb',
    owner: 'foo',
    ownerid: '000001',
    om: [ 'chiruno', 'test02' ],
    omid: [ '000009', '000002' ],
    editdate: ISODate("2023-05-10T07:15:36.942Z"),
    priority: 'p0'
  }
]

类型

按照值的类型来搜索
db.testcol.find({project:{$type:"string"}})

聚合、管道符

//等同于select by_user, count(*) from testcol group by owner
testdb> db.testcol.aggregate([{$group : {_id : "$owner", num_tutorial : {$sum : 1}}}])
[
  { _id: 'foo', num_tutorial: 1 },
  { _id: 'eda', num_tutorial: 2 },
  { _id: 'reisen', num_tutorial: 1 }
]

其他聚合运算符

表达式描述实例
$sum计算总和。db.mycol.aggregate([{$group : {_id : "$by_user", num_tutorial : {$sum : "$likes"}}}])
$avg计算平均值db.mycol.aggregate([{$group : {_id : "$by_user", num_tutorial : {$avg : "$likes"}}}])
$min获取集合中所有文档对应值得最小值。db.mycol.aggregate([{$group : {_id : "$by_user", num_tutorial : {$min : "$likes"}}}])
$max获取集合中所有文档对应值得最大值。db.mycol.aggregate([{$group : {_id : "$by_user", num_tutorial : {$max : "$likes"}}}])
$push将值加入一个数组中,不会判断是否有重复的值。db.mycol.aggregate([{$group : {_id : "$by_user", url : {$push: "$url"}}}])
$addToSet将值加入一个数组中,会判断是否有重复的值,若相同的值在数组中已经存在了,则不加入。db.mycol.aggregate([{$group : {_id : "$by_user", url : {$addToSet : "$url"}}}])
$first根据资源文档的排序获取第一个文档数据。db.mycol.aggregate([{$group : {_id : "$by_user", first_url : {$first : "$url"}}}])
$last根据资源文档的排序获取最后一个文档数据db.mycol.aggregate([{$group : {_id : "$by_user", last_url : {$last : "$url"}}}])

其他管道符

$project:修改输入文档的结构。可以用来重命名、增加或删除域,也可以用于创建计算结果以及嵌套文档。
$match:用于过滤数据,只输出符合条件的文档。$match使用MongoDB的标准查询操作。
$limit:用来限制MongoDB聚合管道返回的文档数。
$skip:在聚合管道中跳过指定数量的文档,并返回余下的文档。
$unwind:将文档中的某一个数组类型字段拆分成多条,每条包含数组中的一个值。
$group:将集合中的文档分组,可用于统计结果。
$sort:将输入文档排序后输出。
$geoNear:输出接近某一地理位置的有序文档。

索引

mongodb除了支持基于单字段(单字段索引)或多字段(复合索引)的排序索引,还支持空间地理索引、文本索引、哈希索引
默认情况下,mongodb会以_id为默认主键

地理空间索引(Geospatial Index)
为了支持对地理空间坐标数据的有效查询,MongoDB提供了两种特殊的索引:返回结果时使用平面几何的二维索引和返回结果时使用球面几何的二维球面索引。
文本索引(Text Indexes)
MongoDB提供了一种文本索引类型,支持在集合中搜索字符串内容。这些文本索引不存储特定于语言的停止词(例如“the”、“a”、“or”),而将集合中的词作为词干,只存储根词。
哈希索引(Hashed Indexes)
为了支持基于散列的分片,MongoDB提供了散列索引类型,它对字段值的散列进行索引。这些索引在其范围内的值分布更加随机,但只支持相等匹配,不支持基于范围的查询

当查询的key只包含索引的时候,mongo会直接从索引返回结果,而不会把文档带入内存,大大提高了效率

查看集合索引
db.col.getIndexes

testdb> db.testcol.getIndexes()
[ { v: 2, key: { _id: 1 }, name: '_id_' } ]

创建索引
db.collection.createIndex(keys, options)
参数

backgroundBoolean建索引过程会阻塞其它数据库操作,background可指定以后台方式创建索引,即增加 "background" 可选参数。 "background" 默认值为false
uniqueBoolean建立的索引是否唯一。指定为true创建唯一索引。默认值为false.
namestring索引的名称。如果未指定,MongoDB的通过连接索引的字段名和排序顺序生成一个索引名称。
sparseBoolean对文档中不存在的字段数据不启用索引;这个参数需要特别注意,如果设置为true的话,在索引字段中不会查询出不包含对应字段的文档.。默认值为 false.
expireAfterSecondsinteger指定一个以秒为单位的数值,完成 TTL设定,设定集合的生存时间。
vindex version索引的版本号。默认的索引版本取决于mongod创建索引时运行的版本。
weightsdocument索引权重值,数值在 1 到 99,999 之间,表示该索引相对于其他索引字段的得分权重。
default_languagestring对于文本索引,该参数决定了停用词及词干和词器的规则的列表。 默认为英语
language_overridestring对于文本索引,该参数指定了包含在文档中的字段名,语言覆盖默认的language,默认值为 language.
testdb> db.testcol.createIndex({project:1})
project_1
testdb> db.testcol.getIndexes()
[
  { v: 2, key: { _id: 1 }, name: '_id_' },
  { v: 2, key: { project: 1 }, name: 'project_1' }
]

删除索引
dropIndex({key:n}) 删除单个索引
dropIndexes() 删除所有索引

testdb> db.testcol.dropIndex({project:1})
{ nIndexesWas: 2, ok: 1 }
testdb> db.testcol.getIndexes()
[ { v: 2, key: { _id: 1 }, name: '_id_' } ]

解析一条查询语句的索引执行状况
db.col.find().explain()

建立索引前

testdb> db.testcol.find({project:'cmdb'}).explain()
{
  explainVersion: '1',
  queryPlanner: {
    namespace: 'testdb.testcol',
    indexFilterSet: false,
    parsedQuery: { project: { '$eq': 'cmdb' } },
    queryHash: '47226C69',
    planCacheKey: '47226C69',
    maxIndexedOrSolutionsReached: false,
    maxIndexedAndSolutionsReached: false,
    maxScansToExplodeReached: false,
    winningPlan: {
      stage: 'COLLSCAN',    //注意这里,COLLSCAN表示全集合扫描
      filter: { project: { '$eq': 'cmdb' } },
      direction: 'forward'
    },
    rejectedPlans: []
  },
  command: { find: 'testcol', filter: { project: 'cmdb' }, '$db': 'testdb' },
  serverInfo: {
    host: 'wg8hf6ct4owq5mi',
    port: 27017,
    version: '6.0.5',
    gitVersion: 'c9a99c120371d4d4c52cbb15dac34a36ce8d3b1d'
  },
  serverParameters: {
    internalQueryFacetBufferSizeBytes: 104857600,
    internalQueryFacetMaxOutputDocSizeBytes: 104857600,
    internalLookupStageIntermediateDocumentMaxSizeBytes: 104857600,
    internalDocumentSourceGroupMaxMemoryBytes: 104857600,
    internalQueryMaxBlockingSortMemoryUsageBytes: 104857600,
    internalQueryProhibitBlockingMergeOnMongoS: 0,
    internalQueryMaxAddToSetBytes: 104857600,
    internalDocumentSourceSetWindowFieldsMaxMemoryBytes: 104857600
  },
  ok: 1
}

建立索引后

testdb> db.testcol.createIndex({project:1})
project_1
testdb> db.testcol.find({project:'cmdb'}).explain()
{
  explainVersion: '1',
  queryPlanner: {
    namespace: 'testdb.testcol',
    indexFilterSet: false,
    parsedQuery: { project: { '$eq': 'cmdb' } },
    queryHash: '47226C69',
    planCacheKey: 'B8C89C45',
    maxIndexedOrSolutionsReached: false,
    maxIndexedAndSolutionsReached: false,
    maxScansToExplodeReached: false,
    winningPlan: {
      stage: 'FETCH',
      inputStage: {
        stage: 'IXSCAN',   //IXSCAN表示走了索引
        keyPattern: { project: 1 },
        indexName: 'project_1',
        isMultiKey: false,
        multiKeyPaths: { project: [] },
        isUnique: false,
        isSparse: false,
        isPartial: false,
        indexVersion: 2,
        direction: 'forward',
        indexBounds: { project: [ '["cmdb", "cmdb"]' ] }
      }
    },
    rejectedPlans: []
  },
  command: { find: 'testcol', filter: { project: 'cmdb' }, '$db': 'testdb' },
  serverInfo: {
    host: 'wg8hf6ct4owq5mi',
    port: 27017,
    version: '6.0.5',
    gitVersion: 'c9a99c120371d4d4c52cbb15dac34a36ce8d3b1d'
  },
  serverParameters: {
    internalQueryFacetBufferSizeBytes: 104857600,
    internalQueryFacetMaxOutputDocSizeBytes: 104857600,
    internalLookupStageIntermediateDocumentMaxSizeBytes: 104857600,
    internalDocumentSourceGroupMaxMemoryBytes: 104857600,
    internalQueryMaxBlockingSortMemoryUsageBytes: 104857600,
    internalQueryProhibitBlockingMergeOnMongoS: 0,
    internalQueryMaxAddToSetBytes: 104857600,
    internalDocumentSourceSetWindowFieldsMaxMemoryBytes: 104857600
  },
  ok: 1
}

现设计cmdb的数据结构
project集合
projectid 项目id,全局唯一,必填(计划由程序生成,无需由用户手动输入)
projectname 项目名,全局唯一,必填
owner 项目拥有者,只能从user集合中存在的人中选择,项目拥有者只能有一个人,必填字段
owerid 项目拥有者的id,根据owner在user集合中的userid走
dev 开发人员,数组,同样只能从user中选
devid 开发人员id,数组,和ownerid一样和dev对应
department 项目所属部门,必须存在于department集合中
ipgroup 项目所拥有的服务器的IP,数组,ip必须存在于server集合中
platform_label 机器所属平台,必须存在于platform集合中
label 数组,自定义标签,其值的格式如"app:thanos"
priority 项目重要等级,从p0-p4这五个等级中选择
edit_time 最后修改时间
create_time 创建时间

计划可以通过项目名、部门、拥有人、ip来搜索

服务器集合
ip_outer 公网
ip_inner 内网
project 所属项目
os 操作系统
ansible_statu 连通性测试
create_time 上架时间
edit_time 最后的修改时间

user集合
username 用户名,必填项,全局唯一
userid 用户id,必填项,全局唯一(计划由程序生成,不需要用户手动输入)
email 邮箱
phone 手机号

简介

nosql的一种,k-v类型,支持string(字符串)、list(链表)、set(集合)、zset(sorted set –有序集合)和hash(哈希类
型),常被用于缓存,所有数据都存储与内存,支持事务,使用单线程+epoll i/o多路复用+纯内存来保证性能

安装

配置文件

登录

指定配置文件登录,如果不指定则使用默认
redis-server /etc/redis.conf
客户端登录
redis-cli
关闭服务
SHUTDOWN

操作

全局操作

读取所有键,生产慎用
keys *
查看当前库中键的数量
dbsize
查看某个键是否存在
exists keyname
删除/批量删除键
del keyname
设置键过期时间
expire keyname seconds
查看键距离过期的时间
ttl keyname
查看键的数据结构类型
type keyname

字符串

设置键值
SET key value [EX seconds] [PX milliseconds] [NX|XX]
EX seconds:超时时间
PX milliseconds:设置毫秒级过期时间
nx:键必须不存在才能被创建,用于新建
xx:键必须存在才能被创建,用于更新

获取键值
get keyname

127.0.0.1:6379> set hello world EX 30 NX
OK
#创建键值对
127.0.0.1:6379> dbsize
(integer) 1
#查看库中键的数量
127.0.0.1:6379> EXISTS hello
(integer) 1
#查看键是否存在
127.0.0.1:6379> set hello world EX 30 NX
OK
127.0.0.1:6379> get hello
"world"
#查看键值
127.0.0.1:6379> ttl hello
(integer) 15
#查看键的到期时间
127.0.0.1:6379> type hello
string
#查看键的类型
127.0.0.1:6379> ttl hello
(integer) -2
127.0.0.1:6379> EXISTS hello
(integer) 0
#到期后查看键是否存在

批量设置/获取键值对
mset
mget

127.0.0.1:6379> mset k1 v1 k2 v2 k3 v3
OK
127.0.0.1:6379> mget k1 k2 k3
1) "v1"
2) "v2"
3) "v3"

自增
incr keyname

incr命令用于对值做自增操作,返回结果分为三种情况:
·值不是整数,返回错误。
·值是整数,返回自增后的结果。
·键不存在,按照值为0自增,返回结果为1。
例如对一个不存在的键执行incr操作后,返回结果是1:
127.0.0.1:6379> EXISTS key1
(integer) 0
127.0.0.1:6379> incr key1
(integer) 1
127.0.0.1:6379> incr key1
(integer) 2
127.0.0.1:6379> EXISTS key1
(integer) 1
127.0.0.1:6379> get key1
"2"

向字符串尾部增加值
append keyname value
查看字符串长度
strlen keyname
设置值,并成功后返回原值
getset keyname value
从指定位置开始修改字符串的值

127.0.0.1:6379> SET key helloworld
OK
127.0.0.1:6379> SETRANGE key 5 redis
(integer) 10
127.0.0.1:6379> get key
"helloredis"

获取字符串的部分值
getrange key start end

列表

从右插入元素
rpush keylist v1 v2 v3
从左侧插入元素
lpush keylist v1 v2
在某个值(pivot)的前后插入元素
linsert key BEFORE|AFTER pivot value
列出范围内的值,0 -1表示全部
lrange keylist start end
查看列表长度
llen listkey

删:
从列表的右/左弹出元素
lpop/rpop
删除指定元素
lrem key count value
lrem命令会从列表中找到等于value的元素进行删除
count>0,从左到右,删除最多count个元素。
count<0,从右到左,删除最多count绝对值个元素。
·count=0,删除所有。
裁剪保留区域内的列表值
ltrim key start stop


修改对应坐标的值
lset key index value

127.0.0.1:6379> lpush listkey v1 v2 v3 v4 v5
(integer) 5
127.0.0.1:6379> lpop listkey
"v5"
127.0.0.1:6379> lrem listkey 2 v3
(integer) 1
127.0.0.1:6379> lrange listkey 0 -1
1) "v4"
2) "v2"
3) "v1"
127.0.0.1:6379> ltrim listkey 0 1
OK
127.0.0.1:6379> lrange listkey 0 -1
1) "v4"
2) "v2"
127.0.0.1:6379> lset listkey 0 v5
OK
127.0.0.1:6379> lrange listkey 0 -1
1) "v5"
2) "v2"

哈希

类似python中的字典

设置/批量值
hset/hmset key field value
获取/批量获取
hget/hmget key field
测量长度
hlen key
判断field是否存在
hexists key field
获取所有的fields
hkeys key
获取所有fields-values
hgetall key

127.0.0.1:6379> hset sethash username foo
(integer) 1
127.0.0.1:6379> hset sethash age 18
(integer) 1
127.0.0.1:6379> hget sethash username
"foo"
127.0.0.1:6379> hlen sethash
(integer) 2
127.0.0.1:6379> hmget sethash username age
1) "foo"
2) "18"
127.0.0.1:6379> HEXISTS sethash username
(integer) 1
127.0.0.1:6379> hkeys sethash
1) "username"
2) "age"
127.0.0.1:6379> hgetall sethash
1) "username"
2) "foo"
3) "age"
4) "18"

集合

无序不可重复

添加元素
sadd key member [member ...]
删除元素
srem key member [member ...]
计算元素个数,直接调用内部变量,时间复杂度为O(1)
scard key
查看元素是否在集合内
sismember key member
随机返回n个集合内的元素
srandmember key [count]
随机弹出元素
spop key [count]
获取所有元素
smembers key
交集
sinter key [key ...]
并集
sunion key [key ...]
差集
sdiff key [key ...]
保存交并差集结果到新的集合中
sinterstore destination key [key ...]
suionstore destination key [key ...]
sdiffstore destination key [key ...]

127.0.0.1:6379> sadd myset v1 v2 v3
(integer) 3
127.0.0.1:6379> srem myset v2
(integer) 1
127.0.0.1:6379> scard myset
(integer) 2
127.0.0.1:6379> sismember myset v6
(integer) 0
127.0.0.1:6379> sismember myset v1
(integer) 1
127.0.0.1:6379> srandmember myset 2
1) "v3"
2) "v1"
127.0.0.1:6379> spop myset 1
1) "v3"
127.0.0.1:6379> sadd myset v2 b4 sdcd wer t3
(integer) 5
127.0.0.1:6379> smembers myset
1) "sdcd"
2) "v1"
3) "t3"
4) "v2"
5) "b4"
6) "wer"
127.0.0.1:6379> sadd testset v3 t3 sad iti hap
(integer) 5
127.0.0.1:6379> sinter myset testset
1) "t3"
127.0.0.1:6379> sunion myset testset
 1) "v1"
 2) "iti"
 3) "v2"
 4) "b4"
 5) "wer"
 6) "sdcd"
 7) "sad"
 8) "t3"
 9) "hap"
10) "v3"
127.0.0.1:6379> sdiff myset testset
1) "b4"
2) "v2"
3) "sdcd"
4) "v1"
5) "wer"
127.0.0.1:6379> sdiffstore my_test_diff myset testset
(integer) 5
127.0.0.1:6379> smember my_test_diff
(error) ERR unknown command `smember`, with args beginning with: `my_test_diff`,
127.0.0.1:6379> smembers my_test_diff
1) "b4"
2) "v2"
3) "sdcd"
4) "v1"
5) "wer"

有序集合

有序不重复,它给每个元素设置一个分数(score)作为排序的依据

添加元素
zadd key [NX|XX] [CH] [INCR] score member [score member ...]
nx:member必须不存在,才可以设置成功,用于添加。
xx:member必须存在,才可以设置成功,用于更新。
ch:返回此次操作后,有序集合元素和分数发生变化的个数
incr:对score做增加,相当于后面介绍的zincrby。

计算成员个数
zcard key
计算某个成员的分数
zscore key member
计算成员的排名(从高到低/从低到高)
zrank key member
zrevrank key member
删除成员
zrem key member [member ...]
给成员加分
zincrby key n member
返回指定排名范围的成员,加上withscores同时附上分数
zrange key start end [withscores]
zrevrange key start end [withscores]

返回指定分数范围的成员
zrangebyscore key min max [withscores] [limit offset count]
zrevrangebyscore key max min [withscores] [limit offset count]
其中zrangebyscore按照分数从低到高返回,zrevrangebyscore反之。例如
下面操作从低到高返回200到221分的成员,withscores选项会同时返回每个
成员的分数。[limit offset count]选项可以限制输出的起始位置和个数

返回指定分数范围成员个数
zcount key min max
删除指定排名范围内的成员
zremrangebyrank key start end
删除指定分数范围的成员
zremrangebyscore key min max

交/并集
zinterstore/zunionstore destination numkeys key [key ...] [WEIGHTS weight] [AGGREGATE SUM|MIN|MAX]
destination:交集计算结果保存到这个键。
numkeys:需要做交集计算键的个数。
key[key...]:需要做交集计算的键。
weights weight[weight...]:每个键的权重,在做交集计算时,每个键中
的每个member会将自己分数乘以这个权重,每个键的权重默认是1。
aggregate sum|min|max:计算成员交集后,分值可以按照sum(和)、
min(最小值)、max(最大值)做汇总,默认值是sum。

慢查询

使用命令修改配置

config set slowlog-log-slower-than 20000 # 单位为微秒
config set slowlog-max-len 1000
config rewrite

查询慢sql,返回的结果包含慢查询日志的标识
id、发生时间戳、命令耗时、执行命令和参数
slowlog get
慢sql列表长度
slowlog len
慢sql日志重置
slowlog reset

redis shell

-r n :重复n次
-i n :间隔多少秒,需要和-r一起使用
redis-cli -r 5 -i 1 ping

-x :把stdin的数值作为最后一个参数
下面的操作会将字符串world作为set hello的值
echo "world" | redis-cli -x set hello

内存检测:检测当前服务器是否可以提供对应的内存给redis(M)
耗时很长
redis-server --test-memory 1024

持久化/高可用

持久化

RDB

默认的持久化方案,没AOF的情况下执行shutdown时会执行
RDB持久是把当前数据生成快照保存到硬盘的过程
触发RDB持久化过程为手动触发和自动触发

优点

RDB是一个非常紧凑(compact)的文件,它保存了redis 在某个时间点上的数据集。这种文件非常适合用于进 行备份和灾难恢复。
RDB 在恢复大数据集时的速度比 AOF 的恢复速度要快。

缺点

RDB方式数据没办法做到实时持久化/秒级持久化。因为bgsave每次运行都要执行fork操作创建子进程,属于 重量级操作(内存中的数据被克隆了一份,大致2倍的膨胀性需要考虑),频繁执行成本过高(影响性能)
RDB文件使用特定二进制格式保存,Redis版本演进过程中有多个格式的RDB版本,存在老版本Redis服务无 法兼容新版RDB格式的问题(版本不兼容)
在一定间隔时间做一次备份,所以如果redis意外down掉的话,就会丢失最后一次快照后的所有修改(数据有 丢失)

触发方式为修改主配置中的save字段

分别表示每900秒数据发生一次改变、每300秒数据发生10次改变、每60秒数据发生10000
次改变会自动触发rdb持久化机制
save 900 1
save 300 10
save 60 10000

当遇到坏盘或磁盘写满等情况时,可以通过config set dir{newDir}在线 修改文件路径到可用的磁盘路径,之后执行bgsave进行磁盘切换,同样适用 于AOF持久化文件。
Redis默认采用LZF算法对生成的RDB文件做压缩处理,压缩后的 文件远远小于内存大小,默认开启,可以通过参数config set rdbcompression{yes|no}动态修改。
如果想要恢复相关数据,只需要将相关的RDB文件拷贝到相关的目录下面即可,redis启动时会自动将rdb文件里的内容加载到内存中

AOF

AOF以独立日志的方式记录每次写命令,重启时再重新执行AOF文件中的命令以恢复数据

AOF 持久化的方法提供了多种的同步频率,即使使用默认的同步频率每秒同步一次,Redis 最多也就丢失 1 秒的数据而已。
AOF 文件使用 Redis 命令追加的形式来构造,因此,即使 Redis 只能向 AOF 文件写入命令的片断,使用 redis-check-aof 工具也很容易修正 AOF 文件。
AOF 文件的格式可读性较强,这也为使用者提供了更灵活的处理方式。例如,如果我们不小心错用了 FLUSHALL 命令,在重写还没进行时,我们可以手工将最后的 FLUSHALL 命令去掉,然后再使用 AOF 来恢 复数据。

缺点
对于具有相同数据的的 Redis,AOF 文件通常会比 RDB 文件体积更大。
虽然 AOF 提供了多种同步的频率,默认情况下,每秒同步一次的频率也具有较高的性能。但在 Redis 的负载 较高时,RDB 比 AOF 具好更好的性能保证。
RDB 使用快照的形式来持久化整个 Redis 数据,而 AOF 只是将每次执行的命令追加到 AOF文件中,因此从 理论上说,RDB 比 AOF 方式更健壮。官方文档也指出,AOF 的确也存在一些 BUG,这些 BUG 在 RDB 没有 存在

配置方法为修改配置文件中的配置项

appendonly yes

数据闪回flashback

一种基于AOF的数据恢复机制,可以以秒级速度随时恢复到数据保存期限里面的任一时间的数据
阿里云 redis 闪回
https://www.alibabacloud.com/help/zh/apsaradb-for-redis/latest/use-data-flashback-to-restore-data-by-point-in-time

高可用

主从

把数据复制到别的副本,master宕机无法自动选举

全量同步
Redis全量复制一般发生在Slave初始化阶段,这时Slave需要将Master上的所有数据都复制一份。
步骤如下:
从服务器连接主服务器,发送SYNC命令;
主服务器接收到SYNC命名后,开始执行BGSAVE命令生成RDB文件并使用缓冲区记录此后执行的所有写命令;
主服务器BGSAVE执行完后,向所有从服务器发送快照文件,并在发送期间继续记录被执行的写命令;
从服务器收到快照文件后丢弃所有旧数据,载入收到的快照;
主服务器快照发送完毕后开始向从服务器发送缓冲区中的写命令;
从服务器完成对快照的载入,开始接收命令请求,并执行来自主服务器缓冲区的写命令;
增量同步
Redis增量复制是指Slave初始化后开始正常工作时主服务器发生的写操作同步到从服务器的过程。
增量复制的过程主要是主服务器每执行一个写命令就会向从服务器发送相同的写命令,从服务器接收并执行收到的写命令。
同步策略
主从刚刚连接的时候,进行全量同步;全同步结束后,进行增量同步。当然,如果有需要,slave在任何时候都可 以发起全量同步。redis 策略是,无论如何,首先会尝试进行增量同步,如不成功,要求从机进行全量同步。

配置

#/bin/bash
mkdir /usr/local/redis/{data,conf,log} -pv
cat << EOF > /usr/local/redis/conf/redis6380.conf
bind 127.0.0.1
port 6380
daemonize yes
pidfile /usr/local/redis/redis_6380.pid
loglevel notice
logfile /usr/local/redis/log/redis_6380.log
dir /usr/local/redis/data/
EOF

cat << EOF > /usr/local/redis/conf/redis6381.conf
bind 127.0.0.1
port 6381
daemonize yes
pidfile /usr/local/redis/redis_6381.pid
loglevel notice
logfile /usr/local/redis/redis_6381.log
dir /usr/local/redis/data/
slaveof 127.0.0.1 6380
EOF
cat << EOF > /usr/local/redis/conf/redis6382.conf
bind 127.0.0.1
port 6382
daemonize yes
pidfile /usr/local/redis/redis_6382.pid
loglevel notice
logfile /usr/local/redis/redis_6382.log
dir /usr/local/redis/data/
slaveof 127.0.0.1 6380
EOF

redis-server指定配置文件启动后,使用info replication验证

哨兵

带自动选举的主从

image.png
哨兵会去定期ping主,如果主不回复,则标记为SDOWN,当标记主不可用的哨兵数量达到一定程度的时候,就会从从服务器中选取一个新的主服务器,并同时修改其余从服务器的配置文件,将新的主节点作为数据同步的来源,然后重新启动服务,完成切换。

集群

分布式存储,每个node上都存储不一样的数据
对象保存到Redis之前先经过CRC16哈希到一个指定的Node上。
每个Node被平均分配了一个Slot段,对应着0-16384,Slot不能重复也不能缺失,否则会导致对象重复存储或无法存储。
Node之间也互相监听,一旦有Node退出或者加入,会按照Slot为单位做数据的迁移。例如Node1如果掉线了,0- 5640这些Slot将会平均分摊到Node2和Node3上,由于Node2和Node3本身维护的Slot还会在自己身上不会被重新 分配,所以迁移过程中不会影响到5641-16384Slot段的使用。

备份方案

预定方案为每周日晚上10-12点全备一次,每天晚上10-12点增量备份一次,每隔两小时备份一次日志
数据闪回功能只有部分云厂商有直接提供,直接根据厂商文档开启就行

方案实施(由gpt自动生成,未验证)

要按照您的备份方案配置Redis,可以参考以下步骤:

  1. 确保您已经安装并正确配置了Redis服务器。
  2. 创建一个用于存储备份文件和日志的目录。例如,您可以在服务器上创建一个名为"/redis-backup"的目录。
  3. 编辑Redis服务器的配置文件(redis.conf)并进行以下设置:

    save ""             # 禁用Redis的自动快照功能。
    appendonly yes      # 启用AOF持久化机制,支持增量备份。
    dir /redis-backup   # 设置备份文件和日志文件存储的目录路径。


    请注意,通过将save参数设置为空字符串,们禁用了Redis的自动快照功能。(save会导致堵塞,不推荐使用)

  4. 保存并关闭Redis配置文件。
  5. 创建全备份脚本(full_backup.sh):

    #!/bin/bash
    redis-cli bgsave                           # 创建Redis快照(全备份)
    backup_dir="/redis-backup/full_backup"
    mkdir -p $backup_dir                       # 创建备份目录(如果不存在)
    cp /var/lib/redis/dump.rdb $backup_dir/$(date +\%Y\%m\%d-\%H\%M).rdb    # 将快照文件移动到备份目录并添加时间戳
    
    # 删除一个月前的备份文件
    find $backup_dir -type f -name "*.rdb" -mtime +30 -exec rm {} \;
    


    这个脚本使用Redis命令bgsave创建一个新的Redis快照(全备份),然后将快照文件复制到备份目录,并为备份文件添加了时间戳。
    请注意,上面的示例假设Redis的快照文件默认存储在"/var/lib/redis/dump.rdb"。如果您的Redis配置使用了不同的路径,请相应地修改脚本。

  6. 创建增量备份脚本(incremental_backup.sh):

    #!/bin/bash
    redis-cli bgsave                        # 创建Redis快照(增量备份)
    cp /var/lib/redis/dump.rdb /redis-backup/redis-incremental-backup-$(date +\%Y\%m\%d-\%H\%M).rdb   # 将快照文件移动到备份目录并添加时间戳


    这个脚本与全备份脚本类似,不同之处在于它创建了Redis的增量备份。
    请注意,上面的示例假设Redis的快照文件默认存储在"/var/lib/redis/dump.rdb"。如果您的Redis配置使用了不同的路径,请相应地修改脚本。

  7. 备份日志文件脚本(log_backup.sh):

    #!/bin/bash
    backup_dir="/redis-backup/log_backup"
    mkdir -p $backup_dir                         # 创建备份目录(如果不存在)
    cp /var/lib/redis/appendonly.aof $backup_dir/$(date +\%Y\%m\%d-\%H\%M).aof    # 将当前的日志文件移动到备份目录并添加时间戳
    
    # 删除一个月前的备份文件
    find $backup_dir -type f -name "*.aof" -mtime +30 -exec rm {} \;
    

请注意,上面的示例假设Redis的日志文件默认存储在"/var/lib/redis/appendonly.aof"。如果您的Redis配置使用了不同的路径,请相应地修改脚本。

  1. 使用cron工具来安排定期运行备份脚本。运行crontab -e命令并添加以下行:

  2. 22 0 /bin/bash /path/to/full_backup.sh
  3. 22 * /bin/bash /path/to/incremental_backup.sh
    /2 * /bin/bash /path/to/log_backup.sh

    <br />上面的cron表达式表示每周日的22:00运行全备份脚本,每天的22:00运行增量备份脚本,以及每隔两小时运行日志备份脚本。 

配置完成后,您的Redis服务器将按计划执行全备份、增量备份和日志备份,并将备份文件存储在指定的目录中。请根据您的实际路径和需求进行调整。

向世界说你好

简单的hello world:

赋值、语句赋值(除了反引号你可以使用$()来包裹语句)<br />echo -e 转义输出\n<br />;分割语句<br />str切片、连接<br />$[]运算符号:没有这个罩着你会发现在别的语言里轻松实现的计算在shell里面是多么的令人头疼!但是需要注意,方括号只适合整数计算

cat test1.sh 
#!/bin/sh
var1=" hello world"
var2="nihao,$var1"
var3=`pwd;who`
var4=$(date +%y%m%d)
var5=$[1 + 5]
var6=$[$var5 * 2]
echo -e "${var2:0:5}\n"
echo $var3
echo $var4
echo $var6


# nihao

# /root/shtest root pts/0 2023-03-15 09:29 (47.96.60.109) root pts/1 2023-03-15 14:52 (47.96.60.109)
# 230315
# 12

浮点计算器bc

bc计算器可以解决浮点计算的问题,你可以在shell里面使用bc自由的使用他,当想退出的时候使用quit就行了
那么问题来了,要怎么在shell里面使用bc呢

var1=`echo "scale=5;17/4" | bc`
echo $var1

# 4.25000

scale是bc中定义保留小数位数的参数,使用;分隔开指令,通过管道符发给bc,就这么简单!

EOF

如果一条式子设计多步运算,那么一条条写管道符肯定不是什么美事,但是好在我们有内联重定向

var1=12.7
var2=3
var3=34.8
var5=`bc << EOF
scale=5
a1 = ($var1 * $var2)
b1 = ($var3 / $var2)
a1 + b1
EOF
`
echo "final result is $var5"

# final result is 49.70000

修改默认退出码

$?可以看到上个命令的退出状态,一般来讲0是正常退出,126是不可执行,127是没找到,130是中止,其他就是各种比较随意的错误了
exit命令可以让你修改默认的状态退出码,最大值255

.......
echo $?
exit 5
[root@master shtest]# ./bcc.sh 
final result is 49.70000
0
[root@master shtest]# echo $?
5

结构化

if

如果if后面的语句退出状态码是0,那么位于then部分命令就会被执行

if pwd
then
echo "here"
fi

有if自然就会有else

if cat 123.txt
then
echo "here"
else
echo "404"
fi

当然,if也可以嵌套,或者也可使用elif,用法和if一样
elif当后面的语句退出为0时会去执行then

if grep alx /etc/passwd
then
echo "user alx exist"
elif ls -d /home/alx
then
echo "no user alx exist"
echo "but his home dir remains"
else
echo "no user alx exist"
fi

# [root@master shtest]# ./iffi.sh 
# ls: cannot access /home/alx: No such file or directory
# no user alx exist
# [root@master shtest]# touch /home/alx
# [root@master shtest]# ./iffi.sh 
# /home/alx
# no user alx exist
# but his home dir remains

值测试

数值比较,这其实是使用test指令,只不过需要注意的是,老毛病:shell只能处理整数

-eq  相等
-ge  大于等于
-gt  大于
-le  小于等于
-lt  小于
-ne  不等于

字符处理

str1 = str2  检查是否相等
str1 != str2  检查是否不等
str1 < str2   检查是否小于
str1 > str2  检查是否大于
-n "$str1" 检查长度是否为非0
-z "$str2" 检查长度是否为0
code=xxx
if test -n "$code" #这里也可以换成下面那种方括号形式
then
echo  "code's value is $code "
else
echo "code is empty"
fi
user="bob"
admin="alic"
if [ $user = $admin ]  #注意这里两边该死的括号和空格
then
echo "welcom $user"
else
echo "no way"
fi

文件比较
需要注意的是,linux允许文件或目录名包含空格,但是在脚本中空格会被理解成分隔,所以在处理对象时,最好使用""将其包起,如
[ -d "$test" ]

-d  检查文件是否存在并且是个目录
-e  检查是否存在
-f  检查是否存在并且是个文件
-r  检查是否存在并且可读
-s  检查是否存在并且非空
-w  检查是否存在并且可写
-x  检查是否存在并且可执行
-O  检查是否存在并且属于当前用户
-G  检查是否存在并且默认属组与当前用户相同
file1 -nt file2 检查是否比f2新,但如果文件不存在,会造成错误结果
file1 -ot file2 检查是否比f2旧

复合条件测试

[ xx ] && [ xxx ]
[ ss ] || [ sss ]

例子:判断文件是否存在且可写,如果满足条件则写入

#!/bin/sh
if [ -e ~/xx.txt ] && [ -w ~/xx.txt ]
then
echo "start to write"
cat<<EOF>~/xx.txt
line 1 in head
line 2 as body
line 3 is end
EOF
else
echo "The file not exist or not writeable"
fi

数组

扫描目录下是否有对应文件,并生成日志

#!/bin/bash
filearray=("test.sh" "ilt.sh" "init.sh")
for file in ${filearray[@]}
do
        if [ -e "$file" ]
        then
                day=`date +%y%m%d`
                echo "$file" >> "result$day.log"
        fi
done > day.log

双括号高级运算
太高级了以至于我暂时不知道该在哪用
image.png

双方括号,用于定义正则表达式,部分shell不能支持
image.png

case

用于匹配值

#!/bin/bash
user=lisa
case $user in
lilei | alisi)
  echo "access denied";;
lisa)
  echo "welcom";;
*)
  echo "wrong auth";;
esac

for

需要注意的是,for默认以空格作为每个元素的分隔,如果你想把一个带空格的复合词语当成一个而非两个对象进行处理,你需要使用""来包裹
或者,修改系统环境变量
IFS=$'\n'<br />如需多个,则在赋值时一行串起来就行了<br />IFS=$'\n':;"

list=`ls -l | grep -v total | awk '{print $NF}'`
echo $list
for file in $list
do
echo "Now scan $file"
done

另一种写法

for file in ~/shtest/*
do
echo "Now scan $file"
done

当然,for也可以写成c++风格

for (( i=1; i<5; i++))
do 
echo $i
done

break\continue也可以使用
另外,输出也可以重定向

list=`ls -l | grep -v total | awk '{print $NF}'`
for file in $list
do
echo "Now scan $file"
done > result.log

循环嵌套

检查path中所有可执行脚本

IFSold=$IFS
IFS=:
for item in $PATH
do
  for file in $item/*
  do
  if [ -x $file ]
  then
    echo $file
  fi
  done
done > result.log
IFS=$IFSold

while

count=`ls -l | head -n 1 | awk '{print $2}'`
echo $count
while [ $count -gt 20 ]
do
count=$[ $count - 5 ]
echo $count
done

while后面也可以接多个测试语句,但是只有最后一个语句会被当成结束循环的判断标准

until

until用法和while相同,只不过他是当满足测试条件的时候才会停止循环

交互

参数

$0表示脚本名,但如果你在用别的指令调用脚本时,如果使用的是绝对路径,那么这个值也会变成绝对路径,不过你可以使用basename来返回不包含路径的脚本名<br />$1-9表示第1-9个参数
当然同样,需要注意空格分隔符问题,最好用引号包裹
$#参数个数<br />$(!#)最后一个参数,如果你想直接用上面那个$#放进去,那么会直接报错<br />$*把所有的参数作为一个整体输出
$@类似数组,把所有参数作为一个可用for迭代的对象输出

shift指令会使得参数的值向左移一位,$3移到$2,而$1的值会被删除,值一旦被丢掉就再也无法恢复,请注意这点

很好,你现在已经学会如何处理参数了,来写个能够自由带参的程序吧(
开玩笑的,虽然能写但是还不够优雅,让我们再学两个小技巧

getopt 选项 参数
如果某个选项需指定具体的参数,那么在其后使用:
如果某个选项的参数可有可无,那么在其后使用::
如果你不想看到他报错,那么使用-q参数

[root@master ~]# getopt -q ab:cd -a -b test1 -c -d
 -a -b 'test1' -c -d --

当然也可以使用长参数,使用逗号分隔,但是需要使用-o区分短参数,用-l区分长参数,使用--来分隔值和参数

[root@master ~]# getopt -q -o ab:cd -l str1:,str2,str3  -- -a -b test1 -c -d --str1 testing --str2 --str3
 -a -b 'test1' -c -d --str1 'testing' --str2 --str3 --

另外一个指令,getopts,用法比getopt简单的多,他一次只会处理一个从命令行得到的值,将参数保存在opt里,将值保存在optarg里
getopts "a:bcd" opt

set指令将getopt的输出作为参数重新输入至这个脚本

set -- `getopt -q ab:c "$@"`
while [ -n $1 ]
do
case "$1" in
  -a) echo "get -a opt";;
  -b) param=$2
      echo "get -b opt,value is $param"
      shift;;
  -c) echo "get -c opt";;
  --) shift
      break;;
   *) echo "wrong option $1"
      break;;
esac
shift
done

getopts智能很多,而且他允许值包含括号、把短选项放在一起使用

[root@master shtest]# cat opt.sh 
while getopts "ab:c" opt
do
  case $opt in
    a) echo "get option -a";;
    b) echo "get option -b ,value is $OPTARG";;
    c) echo "get option -c";;
    *) echo "no this option $opt";;
  esac
done

[root@master shtest]# ./opt.sh -ac -b "test1 test2"
get option -a
get option -c
get option -b ,value is test1 test2

读取输入

read可以接收用户的输入,后面可以设置变量来接收,当用户输入值时,以分隔符为分界,依次把值填入变量中,如果值多于变量的个数,那么多余的值都会被填入最后一个变量里面。也可以不设变量,这样所有的值都会被填入默认环境变量REPLY当中
如果想设置输入超时,可以使用-t参数
如果想要用read在获取值的时候同时输出提示语句,可以使用-p
如果想获得定长的回答,可以使用-n,当输入的字符串长度到达要求时就会自动推进

[root@master shtest]# cat ./read.sh 
read -n1 -t 10 -p "Do you want to continue?(Y/N)" answer
case $answer in
  Y | y) echo "Ok,go on";;
  N | n) echo "Well,as your wish";;
esac
[root@master shtest]# ./read.sh 
Do you want to continue?(Y/N)yOk,go on
[root@master shtest]# ./read.sh 
Do you want to continue?(Y/N)nWell,as your wish
[root@master shtest]# ./read.sh 
Do you want to continue?(Y/N)t[root@master shtest]#

使用read配合cat可以实现按行读取文件

count=1
cat result.log | while read line
do
echo "This is line $count,$line"
count=$[ $count + 1 ]
done

重定向

将stdout和stderr合并后重定向到file
cmd > file 2>&1

永久重定向:使用exec,在脚本中设置一次就行
exec 2>log.file

#!/bin/sh
exec >>result.log 2>&1
cat $1 | wc -l

双向输出

如果你想同时向文件和stdout输出内容,可以使用tee,默认为覆盖,-a为追加

#!/bin/sh
cat $1 | wc -l | tee result.log

创建临时文件

mktemp file.XXXXXX
这个指令会去替换xxx的部分,随机生成一个本目录下没有的文件名,当使用-t参数时,会在tmp目录下生成,-d将生成目录

mktemp file.XXXXXX
file.DWqD13

信号

linux kill信号

信号描述
1SIGHUPhang up 挂起进程
2SIGINTinterrupt 中断进程
3SIGQUTquit 停止进程
9SIGKILL无条件终止进程
15SIGTERMtermination 尽可能终止进程
17SIGSTOP无条件停止
18SIGTSTP停止或暂停进程
19SIGCONT继续中止的进程

3和15会被默认忽视
ctrl+c = SIGINT
ctrl+z = SIGTSTP

trap可以拦截设定的信号,用别的命令来替代执行后面的处理步骤,信号可以用空格间隔列举多个,用数字也行
除了以上几个信号,还可以监控别的,比如脚本退出的EXIT

[root@master shtest]# cat trap.sh 
#!/bin/sh
while [ -f $0 ]
do
trap "echo 'No ctrl c allowed!'" SIGINT
done
[root@master shtest]# sh trap.sh 
^CNo ctrl c allowed!
^CNo ctrl c allowed!
^CNo ctrl c allowed!
^Z
[2]+  Stopped                 sh trap.sh

定时任务

at

-f 指定执行脚本文件
支持绝对和相对时间两种设置方式,具体格式参考
https://wangchujiang.com/linux-command/c/at.html
atrm可以删除任务

[root@master shtest]# at -f checkfile.sh 10:50 
job 1 at Mon Mar 27 10:50:00 2023
[root@master shtest]# atq
1       Mon Mar 27 10:50:00 2023 a root

crontab

时间格式:分时日月星期,可以用作为占位符
etc下有几个预设的crontab文件夹,ls etc/cron
就能看到,也可以直接编辑crontab文件,contab -l可以看到当前有哪些定时任务
https://wangchujiang.com/linux-command/c/crontab.html

函数

基础定义

shell有两种函数格式,当需要使用的时候直接调用函数就行了
函数调用默认的退出码跟普通指令一样,如果想要修改可以使用return
如果想要获取函数的输出,需要直接用变量来赋值

function name {
  commands
}

name() {
commands
return $[ $value * 2 ] #修改退出码
}

result=$(name) #获取函数输出

尽管函数也使用$#,$0,$1这种作为参数,但是这和调用脚本本身的参数并不是一个东西,函数的参数是在脚本内部调用这个函数时后面加上的参数。当然,除了这个比较特殊,其他脚本内定义的全局变量你都可以使用,如果你想设置一个只在函数内部使用的临时,那可以用local来声明
另外,shell的函数不理解数组,如果你把数组作为他的参数,他只会去处理第一个值,所以你需要把数组的值拆开给他

#!/bin/bash
function func1 {
        if [ $# -eq 2 ]
        then
                echo $[ $1 * $2 ]
        fi
        }
result=`func1 2 4`
echo $result

shell函数仅在当前shell有效,当你运行一个脚本,shell会创建一个子shell,如果你想在一个脚本里面使用另外一个脚本里的函数,可以使用source把那个文件加载进当前shell里面,source也可以直接简写成 . 操作符

#!/bin/bash

. ./cfunc.sh
result=`func1 2 4`
echo $result

如果你想在每次shell启动的时候自动把函数加载进来,可以把函数写进~/.bashrc里面

sed

常用指令

s/org/new/ #replace
n,md  #delete

流处理器,在处理数据前先规定好一组规则,然后将指定的命令应用到输出流上,所以可以通过管道输入sed编辑器处理,如下面这个替换的小例子

root@master:~# echo "Have a good day"|sed 's/good/nice/'
Have a nice day

当然你也可以用它来编辑文件内容,但是在默认情况下,他修改完内容后会输出到标准输出,而不会修改原先的文件内容,如果想要使用多组修改规则,使用-e

root@master:~/shtest# cat re.log 
This is line1
here is line2
Final
root@master:~/shtest# sed -e 's/This/Where/; s/here/There/' re.log 
WThere is line1
There is line2
Final
root@master:~/shtest# cat re.log 
This is line1
here is line2
Final

如果你需要处理的命令多,可以预先写一个.sed文件,然后使用sed -f xx.sed target来使用

sed可以配合替换标记使用,一共有四种替换标记

数字 表示替换文本中第几处匹配成功的字符
g    表示会替换所有匹配的文本
p    表示原先行的内容也会打印出来
w file 将替换结果写入文件,只会有发生替换的行
root@master:~/shtest# sed 's/is/is not/w chg.log' re.log 
This not is line1
here is not line2
Final
root@master:~/shtest# cat chg.log 
This not is line1
here is not line2

你也可以指定需要替换的行数,如果是想从某行到尾巴,那么可以用n,$ 在你不知道文本有多首航的时候这个技巧很有用

root@master:~/shtest# sed '1,3s/you/we/' chg.log 
Here we can see line 5
Here we can see line 4
Here we can see line 3
Here you can see line 2
Here you can see line 1

当然,也可以用特征来匹配行,类似grep,用/string/来匹配,比如这里匹配5在的行

root@master:~/shtest# sed '/5/s/you/we/' chg.log 
Here we can see line 5
Here you can see line 4
Here you can see line 3
Here you can see line 2
Here you can see line 1

sed可以使用d来删除指定行数

root@master:~/shtest# sed '2,4d' chg.log 
Here you can see line 5
Here you can see line 1

d也可以用模式匹配,但是需要注意的是,他的开始功能可能会被多次触发,比如我想从含有xx的行删到kk的行,但是在kk之后又有一行出现了xx,那么删除功能会被再次打开,把xx后面所有的东西都删光(如果第二个xx一直到结尾都没有另外一个kk的话
image.png