晨曦's Blog

This is a window to the soul

很少在 Mac 上调试 Shell,今天在 Mac 调试 Shell 的时候发现 sed 的用法还有点不一样,报错:

1
sed: 1: "02.data.sql": invalid command code .

解决

-i 后面增加'' 变成 -i '' 即可

Mac

1
2
3
4
5
6
7
8
9
10
11
12
#!/bin/bash

public_domain="auth.xxx.com"
private_domain="authconsole.xxx.com"

if [ "$(sed -i '' s/ztsg.xxx.com/${public_domain}/ 02.data.sql)" ]; then
echo "public_domain update success"
fi

if [ "$(sed -i '' s/ztsgconsole.xxx.com/${private_domain}/ 02.data.sql)" ]; then
echo "private_domain update success"
fi

Linux

1
2
3
4
5
6
7
8
9
10
11
12
#!/bin/bash

public_domain="auth.xxx.com"
private_domain="authconsole.xxx.com"

if [ "$(sed -i s/ztsg.xxx.com/${public_domain}/ 02.data.sql)" ]; then
echo "public_domain update success"
fi

if [ "$(sed -i s/ztsgconsole.xxx.com/${private_domain}/ 02.data.sql)" ]; then
echo "private_domain update success"
fi

MacLinux 下分别用以上两种写法就可以了

说到写 Shell 第一反应肯定都是 Vim,接下来我们看看如何通过 VsCode 的插件把 VsCode 打造成一个 Shell IDE

shellman

说到 IDE 最起码的需要自动提示和补全,这就需要 shellman 效果如下:

ShellCheck

有了提示和自动补全,还需要校验我们写得代码是否优雅,这就需要 ShellCheck 语法检验,效果如下:

而且 shellcheckshellman 是兼容的
shellcheck compatible (99.99%).

shell-format

最后就是格式化插件 shell-format,写完代码风格还是需要格式化一下的

在用 PythonNodejs 做项目的时候我喜欢把项目的路由分文件存放在 routers 文件夹下,然后在启动文件夹中加载,这样即简洁也好维护,试试如何在 Gin 中实现

main.go

1
2
3
r := gin.Default()
routers.Router(r) // 路由
r.Run(":8888")

3 行代码,Router 方法用来加载所有的路由,接下来看 Router 方法具体实现

routers/router.go

1
2
3
4
5
6
7
8
9
func Router(e *gin.Engine) {
// V1版本
v1 := e.Group("/v1")
{
RouterUser(v1)
// 其他模块路由
}
// V2版本
}

Router 方法主要是去把当前下的其他模块的路由加载进来,这里使用了路由组,如果不需要路由组的话去掉即可,接来下看看 RouterUser 方法具体实现

routers/user.go

1
2
3
4
func RouterUser(e *gin.RouterGroup) {
e.GET("/user/list", controllers.UserList)
e.POST("/user/add", controllers.UserAdd)
}

使用路由组接收的参数是 RouterGroup 不使用路由组接收的参数是 Engine,这里需要注意一下

目录

最后规划下来的目录如下:

1
2
3
4
5
6
7
8
9
10
11
.
├── README.md
├── app
│   └── controllers
│   └── user.go
├── go.mod
├── go.sum
├── main.go
└── routers
   ├── router.go
   └── user.go

这样规划的话,就只需要维护 router.go 和其他的对应模块自己的路由规则,main.go 也就会变得非常的简洁

一个非常简单的需求,需要在 ES 中去匹配 IPV62409:8a10:* 段的数据,看看如何查询

实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
GET logstash-nginx-*/doc/_search
{
"_source": "remote_ip",
"query": {
"bool": {
"must": [
{
"query_string": {
"fields": [
"remote_ip.keyword"
],
"query": """2409\:8a10\:*""",
"analyzer": "keyword",
"analyze_wildcard": true
}
}
],
"filter": {
"range": {
"@timestamp": {
"gte": "2021-12-14T09:54:00.000000+0800",
"lt": "2021-12-16T09:55:00.000000+0800"
}
}
}
}
}
}

这里的重点是 """2409\:8a10\:*""" 也可以写成 "2409\\:8a10\\:*",其他的符号也可按此类推即可

最后效果:


ES 相关参考

Python 中需要热加载是非常简单的,以 Sanic 为例只需要设置 auto_reload=True,但是在 Gin 中没有内置相应的功能,这里我们可以利用 Go 中的一些其他包实现,即更改源码,保存后,自动触发更新,浏览器上刷新即可。免去了杀进程、重新启动之苦

Fresh

试了下 github.com/gravityblast/fresh 还觉得不错
安装:

1
go get github.com/pilu/fresh

使用:

1
fresh

其他

记录一下其他实现框架
Air:https://github.com/cosmtrek/air
Bee:https://github.com/beego/bee
Realize:https://github.com/oxequa/realize
Gin:https://github.com/codegangsta/gin
gowatch:https://github.com/silenceper/gowatch


题外话:
在安装非项目使用的包时如:fresh,最好是不要在项目根目录下进行 go get 或者会被写入到项目的 go.mod

前记

最近需要实现通过 Supervisor 来守护 Haproxy,折腾了半天记录下实现配置

实现

1
2
3
4
5
6
[program:haproxy]
command=/usr/sbin/haproxy -f /etc/haproxy/haproxy.cfg -p /run/haproxy.pid -Ds
process_name=haproxy
autostart=true
autorestart=true
startretries=1

这里的重点是 -p /run/haproxy.pid 必须要指定 pid 的路径,要不然没法获取到 program 状态

类型?

type 定义的结构体接口都是类型

导出名

Go 中,如果一个名字以大写字母开头,那么它就是已导出的,在导入一个包时,你只能引用其中已导出的名字。任何未导出的名字在该包外均无法访问
类似其他语言 PublicPrivate 修饰符

命名返回值

Go 的返回值可被命名,它们会被视作定义在函数顶部的变量。返回值的名称应当具有一定的意义,它可以作为文档使用。没有参数的 return 语句返回已命名的返回值。也就是直接返回

短变量声明

函数中,简洁赋值语句:= 可在类型明确的地方代替 var 声明
函数外的每个语句都必须以关键字开始(var, func 等等),因此 := 结构不能在函数外使用

零值

没有明确初始值的变量声明会被赋予它们的零值0false""(空字符串)

常量

使用 const 关键字声明,常量不能用:= 语法声明

defer

defer 语句会将函数推迟到外层函数返回之后执行,推迟调用的函数其参数会立即求值,但直到外层函数返回前该函数都不会被调用
推迟的函数调用会被压入一个栈中。当外层函数返回时,被推迟的函数会按照后进先出的顺序调用可以看做是倒序执行

指针

指针保存了值的内存地址

切片

它会选择一个半开区间,包括第一个元素,但排除最后一个元素
切片并不存储任何数据,它只是描述了底层数组中的一段。更改切片的元素会修改其底层数组中对应的元素。与它共享底层数组的切片都会观测到这些修改

匿名函数与闭包

有了匿名函数,就可以在函数中再定义函数(函数嵌套),定义的这个匿名函数,也可以称为内部函数。更重要的是,在函数内定义的内部函数,可以使用外部函数的变量等,这种方式也称为闭包

方法与函数

方法就是一类带特殊的接收者参数的函数
接收者的类型定义和方法声明必须在同一包内;不能为内建类型声明方法

选择值或指针作为接收者

使用指针接收者的原因有二:
首先,方法能够修改其接收者指向的值。
其次,这样可以避免在每次调用方法时复制该值。若值的类型为大型结构体时,这样做会更加高效
通常来说,所有给定类型的方法都应该有值或指针接收者,但并不应该二者混用

接口

接口类型是由一组方法签名定义的集合
类型通过实现一个接口的所有方法来实现该接口。既然无需专门显式声明,也就没有 “implements” 关键字
隐式接口从接口的实现中解耦了定义,这样接口的实现可以出现在任何包中,无需提前准备
因此,也就无需在每一个实现上增加新的接口名称,这样同时也鼓励了明确的接口定义


未完待续…

国内的 PYPI 源也挺多的,经过实际使用下来,还是觉得华为的 PYPI 源包的版本最为齐全,推荐用华为的源进行加速

临时使用

运行以下命令使用华为源:

1
pip install -r requirements.txt --trusted-host https://repo.huaweicloud.com -i https://repo.huaweicloud.com/repository/pypi/simple

或者

1
pip install --trusted-host https://repo.huaweicloud.com -i https://repo.huaweicloud.com/repository/pypi/simple sanic

通过上面的方式安装单个包

设为默认

Pip 的配置文件为用户根目录下的:~/.pip/pip.confWindows 路径为:C:\Users\<UserName>\pip\pip.ini), 您可以配置如下内容:

1
2
3
4
[global]
index-url = https://repo.huaweicloud.com/repository/pypi/simple
trusted-host = repo.huaweicloud.com
timeout = 120

其他国内镜像源:

1
2
3
4
5
阿里云:http://mirrors.aliyun.com/pypi/simple/
中国科技大学:https://pypi.mirrors.ustc.edu.cn/simple/
豆瓣(douban):http://pypi.douban.com/simple/
清华大学:https://pypi.tuna.tsinghua.edu.cn/simple/
中国科学技术大学:http://pypi.mirrors.ustc.edu.cn/simple/

个人推荐使用华为的 Alpine 源,通过我的测试华为的源最为齐全

华为

1
sed -i 's/dl-cdn.alpinelinux.org/repo.huaweicloud.com/g' /etc/apk/repositories

阿里

1
sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories

科大

1
sed -i 's/dl-cdn.alpinelinux.org/mirrors.ustc.edu.cn/g' /etc/apk/repositories

清华

1
sed -i 's/dl-cdn.alpinelinux.org/mirrors.tuna.tsinghua.edu.cn/g' /etc/apk/repositories

Docker

1
RUN sed -i 's/dl-cdn.alpinelinux.org/repo.huaweicloud.com/g' /etc/apk/repositories

最近开发 Python 一直在用 Sanic,感觉还不错;看看如何将 Sanic 部署到 Docker 中并用 Supervisor 来守护

Dockerfile

这里利用 Python 3.9 来构建基础镜像

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
FROM python:3.9-alpine

RUN sed -i 's/dl-cdn.alpinelinux.org/repo.huaweicloud.com/g' /etc/apk/repositories

RUN apk update \
&& apk add --no-cache build-base \
&& apk add --no-cache libffi-dev

RUN mkdir /usr/src/app

COPY . /usr/src/app/

COPY ./supervisor/supervisord.conf /etc/supervisord.conf

WORKDIR /usr/src/app
ENV PYTHONPATH /usr/src/app

RUN pip3 install supervisor \
&& pip3 install -r requirements.txt --trusted-host https://repo.huaweicloud.com -i https://repo.huaweicloud.com/repository/pypi/simple

CMD ["supervisord", "-c", "/etc/supervisord.conf"]

这里利用华为的 alpine 源和 pypi

Supervisor

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
[unix_http_server]
file=/var/run/supervisor.sock

[supervisord]
logfile=/var/log/supervisord.log
logfile_maxbytes=50MB
logfile_backups=10
loglevel=info
pidfile=/var/run/supervisord.pid
nodaemon=true
silent=false
minfds=1024
minprocs=200

[rpcinterface:supervisor]
supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface

[supervisorctl]
serverurl=unix:///var/run/supervisor.sock

[program:sanic]
user = root
directory=/usr/src/app
command=sanic server.app --host=0.0.0.0 --port=8888 --workers=4
stdout_logfile_backups=20
stdout_logfile_maxbytes=50MB
stdout_logfile = /var/log/sanic.log
stderr_logfile = /var/log/sanic.err
autostart=true

Supervisor 配置参考


题外话:

容器运行 Supervisor 记得设置 nodaemon=true,或则会出现 Unlinking stale socket /var/run/supervisor.sock

因为 Supervisor 默认是 deamon 模式,启动命令结束后 Supervisor 会在后台运行,而容器运行启动命令返回 0 后自己关闭了,导致一直出现无法运行的现象

0%