matsudaira 发布的文章

简介

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

不管哪台机子都要跑的

sudo apt-get update -y && sudo apt-get dist-upgrade -y && sudo apt autoremove -y

k8s安装

swapoff -a
echo 1 > /proc/sys/net/ipv4/ip_forwar

换源
https://www.cnblogs.com/leisurelylicht/p/Ubuntu-guo-nei-an-zhuang-kubernetes.html

sudo vim /etc/apt/sources.list.d/kubernetes.list
# 将下面的阿里源加入文件中
deb https://mirrors.aliyun.com/kubernetes/apt/ kubernetes-xenial main

apt udpate 会报错给一串key

运行下面两个指令,把key换成上面key的后8位

gpg --keyserver keyserver.ubuntu.com --recv-keys E084DAB9
gpg --export --armor E084DAB9 | sudo apt-key add -
运行下面两个指令,把key换成上面key的后8位

apt-get update && apt-get install -y apt-get install -y kubelet=1.23.9-00 kubeadm=1.23.9-00 kubectl=1.23.9-00

配置

systemctl start kubelet
systemctl enable kubelet

apt install docker.io
docker --version

 vi /etc/docker/daemon.js
{
  "exec-opts": ["native.cgroupdriver=systemd"],
  "registry-mirrors": ["https://ud6340vz.mirror.aliyuncs.com"]
}
systemctl daemon-reload
systemctl restart docker
systemctl enable docker

echo 1 > /proc/sys/net/bridge/bridge-nf-call-iptables

kubeadm config images pull --image-repository=registry.aliyuncs.com/google_containers
kubeadm init --image-repository=registry.aliyuncs.com/google_containers --pod-network-cidr=10.244.0.0/16 

如果要机子加入集群别忘了配置Hosts

mkdir -p $HOME/.kube
cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
chown $(id -u):$(id -g) $HOME/.kube/config

然后就是安装flannel,装完ok就行

https://man7.org/linux/man-pages/man5/proc.5.html
https://www.cnblogs.com/cute/archive/2011/04/20/2022280.html

proc文件系统是一个伪文件系统,他提供了一个接口以访问内核数据结构
如读取文件时遇到报错,部分是因为权限不够(一般限制属主读取),部分是该文件只供系统调用读取
如果想看一些文件的详细信息,请查询上方的man手册

/proc/pid

proc目录下有许多以数字命名的文件夹,其代表对应pid的进程目前的一些运行信息
以下以一个docker进程为例子,进程的pid可以通过ps -ef | grep docker来获取

子目录

pid/task:包含线程的信息,每个子目录都是一个tid号
pid/attr:提供与SElinux相关的api,只有在kernel设置了 CONFIG_SECURITY的情况下会被展示出来
pid/fd:包含进程打开的每一个文件的文件描述符
pid/fdinfo:以文件描述符命名,描述了每个进程打开的文件的信息

cat /proc/8432/fdinfo/7
pos:    0  偏移量
flags:  02004001  八进制数,用于展示访问模式和文件状态
mnt_id: 9   ID of the mount containing this file

pid/map_files:内存映射文件,命名格式为存储器起始和结束的地址,十六进制
pid/net:包含各种网络层信息
pid/ns:命名空间

文件

cmdline:启动命令

[root@master 8432]# cat cmdline 
/usr/bin/dockerd-Hfd://--containerd=/run/containerd/containerd.sock

environ:环境变量,变量大写,值小写

[root@master 8432]# cat environ 
LANG=en_US.UTF-8PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/binNOTIFY_SOCKET=/run/systemd/notifyLISTEN_PID=8432LISTEN_FDS=1

limits:资源使用限制

[root@master 8432]# cat limits 
Limit                     Soft Limit           Hard Limit           Units     
Max cpu time              unlimited            unlimited            seconds   
Max file size             unlimited            unlimited            bytes     
Max data size             unlimited            unlimited            bytes     
Max stack size            8388608              unlimited            bytes     
Max core file size        unlimited            unlimited            bytes     
Max resident set          unlimited            unlimited            bytes     
Max processes             unlimited            unlimited            processes 
Max open files            1048576              1048576              files     
Max locked memory         65536                65536                bytes     
Max address space         unlimited            unlimited            bytes     
Max file locks            unlimited            unlimited            locks     
Max pending signals       14500                14500                signals   
Max msgqueue size         819200               819200               bytes     
Max nice priority         0                    0                    
Max realtime priority     0                    0                    
Max realtime timeout      unlimited            unlimited            us

maps:进程的每个可执行文件和库文件在内存中的映射区及其访问权限

[root@master 8432]# cat maps 
c000000000-c002c00000 rw-p 00000000 00:00 0 
c002c00000-c004000000 ---p 00000000 00:00 0 
55ca7b56e000-55ca7e852000 r-xp 00000000 fd:01 276901                     /usr/bin/dockerd
55ca7ea51000-55ca80337000 r--p 032e3000 fd:01 276901                     /usr/bin/dockerd
55ca80337000-55ca803eb000 rw-p 04bc9000 fd:01 276901                     /usr/bin/dockerd

cwd:运行环境的链接文件
mem:进程所占内存空间,只能由系统调用读取
stat:当前进程的状态信息,可读性差,一般用ps使用
statm:当前进程占用的内存的状态信息
status:可读性好的stat+statm

[root@master 8432]# cat status
Name:   dockerd 进程名,最长16字节,超过会被截断
Umask:  0022
State:  S (sleeping)
Tgid:   8432   线程gid
Ngid:   0       NUMA group ID,非统一访问内存
Pid:    8432   线程id
PPid:   1      父进程pid
TracerPid:      0 tracing进程的pid,0代表没有被traced
Uid:    0       0       0       0   Real, effective, saved set, and filesystem UIDs
Gid:    0       0       0       0
FDSize: 256    当前分配的文件描述符槽数
Groups:
VmPeak:  1413800 kB
VmSize:  1357548 kB
VmLck:         0 kB
VmPin:         0 kB
VmHWM:     87892 kB
VmRSS:     79328 kB
RssAnon:           52272 kB
RssFile:           27056 kB
RssShmem:              0 kB
VmData:  1223236 kB
VmStk:       132 kB
VmExe:     52112 kB
VmLib:      6368 kB
VmPTE:       508 kB
VmSwap:        0 kB
Threads:        15
SigQ:   0/14500
SigPnd: 0000000000000000
ShdPnd: 0000000000000000
SigBlk: fffffffc3bba2800
SigIgn: 0000000000000000
SigCgt: fffffffdffc1feff
CapInh: 0000000000000000
CapPrm: 0000001fffffffff
CapEff: 0000001fffffffff
CapBnd: 0000001fffffffff
CapAmb: 0000000000000000
NoNewPrivs:     0
Seccomp:        0
Speculation_Store_Bypass:       vulnerable
Cpus_allowed:   3
Cpus_allowed_list:      0-1
Mems_allowed:   00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000001
Mems_allowed_list:      0
voluntary_ctxt_switches:        19545
nonvoluntary_ctxt_switches:     19

stack:堆栈函数调用

基础语法

使用前记得给文件加上x权限
第一行加解释器,格式如

#!/bin/sh

变量

定义变量

var1="123"

使用语句给变量赋值

for file in `ls /etc`

使用变量使用$

#!/bin/sh
var1="hello world"
echo $var1

当然,当你的变量和句子混在一起的时候,你可以使用${}来帮助解释变量范围,就像这样

#!/bin/sh
var1=" hello world"
echo "I want to say${var1}"

字符串

单引号中的所有字符都会原样输出,而双引号中的变量、转义字符等都可以被解释,单引号中不能有单引号,转义了的也不行

[root@master shtest]# ./test1.sh 
I want to say${var1}
[root@master shtest]# cat test1.sh 
#!/bin/sh
var1=" hello world"
echo 'I want to say${var1}'

拼接字符串

[root@master shtest]# ./test1.sh 
hello worldnihao, hello world
[root@master shtest]# cat test1.sh 
#!/bin/sh
var1=" hello world"
var2="nihao,$var1"
echo $var1$var2

输出字符串长度${#}

[root@master shtest]# cat test1.sh 
#!/bin/sh
var1=" hello world"
var2="nihao,$var1"
echo ${#var2}
[root@master shtest]# ./test1.sh 
18

字符串切片

[root@master shtest]# ./test1.sh 
nihao
[root@master shtest]# cat test1.sh 
#!/bin/sh
var1=" hello world"
var2="nihao,$var1"
echo ${var2:0:5}