#530 VPN 与 NAT

2021-05-09

我公司办公环境需要用到 OpenVPN,之前允许一个账号在两处登录,现在处于安全考虑只允许一处登录了。
然后,我就不方便了,因为我办公环境有两台电脑(台式机 Windows,笔记本 Ubuntu)都需要接入 VPN。

#527 Gin 框架自学: 基础

2021-05-06

先列一下目前最火的几个 Go 语言框架:

  • gin
    stars num forks num License
    stars
  • beego 国人开发
    stars num forks num License
    stars
  • iris The fastest HTTP/2 Go Web Framework.
    自称是 ExpressJS 和 Laravel 的继承者。
    stars num forks num License
  • echo High performance, minimalist Go web framework
    stars num forks num License
  • go-micro Microservice framework for Go
    stars num forks num License
    stars
  • fiber
    stars num forks num License
  • kratos A Go framework for microservices. B 站出品
    stars num forks num License
  • revel/revel
    stars num forks num License
  • go-zero 国人开发(七牛)
    stars num forks num License
    stars
  • martini Classy web framework for Go
    stars num forks num License
  • GoFrame Classy web framework for Go 国人开发
    stars num forks num License

Gin

http://gin-gonic.com

Gin LOGO

Gin 相比 Beego 非常轻量级,不带 ORM,不支持 Session,正则路由都不支持,需要自行实现。

最新版本 56 个 文件, 算上注释,大概七千行代码。

$ find . -name "*.go" -not -path "*_test.go" | cat | wc -l
56
$ find . -name "*.go" -not -path "*_test.go" | xargs cat | wc -l
6910
Tag CreatedAt ReleasedAt
v1.9.0 2023-02-21 2023-02-21
v1.8.2 2022-12-22 2022-12-22
v1.8.1 2022-06-06 2022-06-06
v1.8.0 2022-05-30 2022-05-30
v1.7.7 2021-11-24 2021-11-24
v1.7.6 2021-08-03 2021-11-23
v1.7.4 2021-08-03 2021-08-15
v1.7.3 2021-08-03 2021-08-03
v1.7.2 2021-05-21 2021-05-21
v1.7.1 2021-04-08 2021-04-08
v1.7.0 2021-04-08 2021-04-08
v1.6.3 2020-05-03 2020-05-03
v1.6.2 2020-03-27 2020-03-27
v1.6.1 2020-03-23 2020-03-23
v1.6.0 2020-03-22 2020-03-22
v1.5.0 2019-11-24 2019-11-28
v1.4.0 2019-05-07 2019-05-08
v1.3.0 2018-08-14 2018-08-14
v1.2 2017-07-02 2017-07-02
v1.1.4 2016-12-04 2016-12-05
v1.1.3 2016-12-03 2016-12-04

HTTP 参数

// c: *gin.Context
// func (c *gin.Context) {
//     balabala...
// }

c.Request.URL.Query()

c.Param(key string) string // 路径参数
c.Params.ByName(key)

c.Query(key string) string // 查询参数
c.Request.URL.Query().Get(key)
c.GetQuery(key)

c.QueryMap(key string) map[string][]string
c.GetQueryMap(key)

c.QueryArray(key string) []string

c.GetQuery(key string) (string, bool)
c.GetQueryArray(key) 的第 0 个元素

c.GetQueryMap(key string) (map[string]string, bool)
c.GetQueryArray(key string) (values []string, ok bool)
c.PostForm(key string) string
c.PostFormMap(key string) map[string][]string
c.PostFormArray(key string) []string
c.GetPostForm(key string) (string, bool)
c.GetPostFormMap(key string) (map[string]string, bool)
c.GetPostFormArray(key string) (values []string, ok bool)
c.FormFile(key string) (*multipart.FileHeader, error)
c.DefaultQuery(key string, defaultValue string) string
c.DefaultPostForm(key string, defaultValue string) string

快速启动

package main

import (
    "io"
    "net/http"
    "os"
    "path/filepath"

    "github.com/gin-gonic/gin"
)

func main() {
    r := gin.Default()

    // 处理 GET 请求中的 URL 参数
    r.GET("/hello", func(c *gin.Context) {
        name := c.Query("name")
        c.String(http.StatusOK, "Hello %s!", name)
    })

    // 处理带路径参数的 GET 请求
    r.GET("/users/:id", func(c *gin.Context) {
        id := c.Param("id")
        c.String(http.StatusOK, "User ID: %s", id)
    })

    // 处理 POST 请求中的 JSON 请求体
    r.POST("/data", func(c *gin.Context) {
        var json struct {
            Data string `json:"data"`
        }
        c.BindJSON(&json)
        c.JSON(http.StatusOK, gin.H{"message": "Received data: " + json.Data})
    })

    // 处理文件上传
    r.POST("/upload", func(c *gin.Context) {
        file, err := c.FormFile("file")
        if err != nil {
            c.String(http.StatusBadRequest, "File upload failed: %s", err.Error())
            return
        }

        // 将上传的文件保存到服务器上
        dst := filepath.Join("./uploads", file.Filename)
        if err := c.SaveUploadedFile(file, dst); err != nil {
            c.String(http.StatusInternalServerError, "File save failed: %s", err.Error())
            return
        }

        c.String(http.StatusOK, "File uploaded successfully")
    })

    // 处理返回文件
    r.GET("/download", func(c *gin.Context) {
        filename := "example.txt"
        filepath := "./downloads/" + filename

        // 打开文件
        file, err := os.Open(filepath)
        if err != nil {
            c.String(http.StatusInternalServerError, "Failed to open file: %s", err.Error())
            return
        }
        defer file.Close()

        // 设置响应头
        c.Header("Content-Disposition", "attachment; filename="+filename)
        c.Header("Content-Type", "application/octet-stream")
        c.Header("Content-Transfer-Encoding", "binary")

        // 将文件内容写入响应主体
        _, err = io.Copy(c.Writer, file)
        if err != nil {
            c.String(http.StatusInternalServerError, "Failed to write file: %s", err.Error())
        }
    })

    r.Run(":8080")
}

路由

设置路由不存在的处理方法:

r.NoRoute(func(c *gin.Context) {
    c.JSON(404, gin.H{"message": "page not found"})
})

限制请求大小:

r.MaxMultipartMemory = 10 << 20 // 10MB
// "github.com/gin-contrib/size"
r.Use(size.Check(size.MB * 10))
r.Use(requestSizeLimitMiddleware(10 << 20))
func requestSizeLimitMiddleware(maxSize int64) gin.HandlerFunc {
    return func(c *gin.Context) {
        if c.Request.ContentLength > maxSize {
            c.AbortWithStatusJSON(413, gin.H{"message": "request entity too large"})
            return
        }
        c.Next()
    }
}

#526 nmap 基本用法

2021-05-01

检查端口可连接性,并支持检测操作系统信息、占用端口的常见应用程序。

  • 开放(Open)
  • 关闭(Closed)
  • 过滤(Filtered)
  • 未过滤(Unfiltered)
  • 开放或过滤(Open|Filtered)
  • 关闭或过滤(Closed|Filtered)

  • 默认扫描常用端口

常用命令

nmap 192.168.31.0/24

# Ping扫描
nmap -sn 192.168.31.0/24
# 快速端口扫描
nmap -F  192.168.31.0/24

# 操作系统
sudo nmap -O -v 192.168.31.42
sudo nmap -A -v 192.168.31.42  # 输出更详细

# 常规检查:端口(-sV)和操作系统(-O)
sudo nmap -sV -O 192.168.31.42

# 检查指定端口
nmap -p 22 192.168.31.42
nmap -p 22,80,443,3306 192.168.31.42
# 所有端口
nmap -p 1-65525 192.168.31.42
nmap -p-        192.168.31.42
# 加上使用这个端口的服务版本信息
nmap -p 22 192.168.31.42 -sV

# 对端口进行探测
nmap -PS 192.168.31.42  # SYN
nmap -PA 192.168.31.42  # ASK
nmap -PU 192.168.31.42  # UDP

sudo nmap -sS 192.168.1.1-10   # SYN
sudo nmap -sA 192.168.31.0/24  # ACK
sudo nmap -sF 192.168.31.0/24  # FIN
sudo nmap -sN 192.168.31.0/24  # TCP Null
sudo nmap -sT 192.168.31.0/24  # TCP Connect
sudo nmap -sU 192.168.31.0/24  # UDP
sudo nmap -sO 192.168.31.0/24  # IP 协议扫描

nmap -O  192.168.0.1/16
nmap -sP 192.168.0.1/16

nmap -sSU 192.168.1.1/24
# Ping Scan
# Parallel DNS resolution
# SYN Stealth Scan
# UDP Scan

参考资料与拓展阅读

#525 Golang 自动过期 Map

2021-04-26
type ExpiringMap struct {
    m      sync.Map
    expiry time.Duration
}

func (e *ExpiringMap) set(key, value interface{}) {
    e.m.Store(key, value)

    go func() {
        time.Sleep(e.expiry)
        e.m.Delete(key)
    }()
}

改进:定时清理

package main

import (
    "sync"
    "time"
)

type Item struct {
    value      interface{}
    expiration int64
}

type ExpiringMap struct {
    m       sync.Mutex
    items   map[string]Item
    timeout time.Duration
}

func NewExpiringMap(timeout time.Duration) *ExpiringMap {
    em := &ExpiringMap{
        items:   make(map[string]Item),
        timeout: timeout,
    }
    go em.cleanup()
    return em
}

func (em *ExpiringMap) Set(key string, value interface{}) {
    em.m.Lock()
    defer em.m.Unlock()
    em.items[key] = Item{
        value:      value,
        expiration: time.Now().Add(em.timeout).UnixNano(),
    }
}

func (em *ExpiringMap) Get(key string) (interface{}, bool) {
    em.m.Lock()
    defer em.m.Unlock()
    item, found := em.items[key]
    if !found {
        return nil, false
    }
    if time.Now().UnixNano() > item.expiration {
        return nil, false
    }
    return item.value, true
}

func (em *ExpiringMap) Delete(key string) {
    em.m.Lock()
    defer em.m.Unlock()
    delete(em.items, key)
}

func (em *ExpiringMap) deleteExpiredKeys() {
    em.m.Lock()
    defer em.m.Unlock()
    now := time.Now().UnixNano()
    for key, item := range em.items {
        if now < item.expiration {
            break
        }
        delete(em.items, key)
    }
}

func (em *ExpiringMap) cleanup() {
    for {
        time.Sleep(em.timeout) // 阻塞协程
        em.deleteExpiredKeys()
    }
}

func main() {
    em := NewExpiringMap(time.Second * 10)

    time.Sleep(time.Second * 15)

    _, ok := em.Get("key1")
    if !ok {
        println("key1 is deleted")
    }

    _, ok = em.Get("key2")
    if !ok {
        println("key2 is deleted")
    }
}

#524 fasthttp: 十倍速

2021-04-26

https://github.com/valyala/fasthttp

fasthttp

package main

import (
    "flag"
    "fmt"
    "log"

    "github.com/valyala/fasthttp"
)

var (
    addr     = flag.String("addr", ":8080", "TCP address to listen to")
    compress = flag.Bool("compress", false, "Whether to enable transparent response compression")
)

func main() {
    flag.Parse()

    h := requestHandler
    if *compress {
        h = fasthttp.CompressHandler(h)
    }

    if err := fasthttp.ListenAndServe(*addr, h); err != nil {
        log.Fatalf("Error in ListenAndServe: %v", err)
    }
}

func requestHandler(ctx *fasthttp.RequestCtx) {
    fmt.Fprintf(ctx, "Hello, world!\n\n")

    fmt.Fprintf(ctx, "Request method is %q\n", ctx.Method())
    fmt.Fprintf(ctx, "RequestURI is %q\n", ctx.RequestURI())
    fmt.Fprintf(ctx, "Requested path is %q\n", ctx.Path())
    fmt.Fprintf(ctx, "Host is %q\n", ctx.Host())
    fmt.Fprintf(ctx, "Query string is %q\n", ctx.QueryArgs())
    fmt.Fprintf(ctx, "User-Agent is %q\n", ctx.UserAgent())
    fmt.Fprintf(ctx, "Connection has been established at %s\n", ctx.ConnTime())
    fmt.Fprintf(ctx, "Request has been started at %s\n", ctx.Time())
    fmt.Fprintf(ctx, "Serial request number for the current connection is %d\n", ctx.ConnRequestNum())
    fmt.Fprintf(ctx, "Your ip is %q\n\n", ctx.RemoteIP())

    fmt.Fprintf(ctx, "Raw request is:\n---CUT---\n%s\n---CUT---", &ctx.Request)

    ctx.SetContentType("text/plain; charset=utf8")

    // Set arbitrary headers
    ctx.Response.Header.Set("X-My-Header", "my-header-value")

    // Set cookies
    var c fasthttp.Cookie
    c.SetKey("cookie-name")
    c.SetValue("cookie-value")
    ctx.Response.Header.SetCookie(&c)
}
  • r.Body -> ctx.PostBody()
  • r.URL.Path -> ctx.Path()
  • r.URL -> ctx.URI()
  • r.Method -> ctx.Method()
  • r.Header -> ctx.Request.Header
  • r.Header.Get() -> ctx.Request.Header.Peek()
  • r.Host -> ctx.Host()
  • r.Form -> ctx.QueryArgs() + ctx.PostArgs()
  • r.PostForm -> ctx.PostArgs()
  • r.FormValue() -> ctx.FormValue()
  • r.FormFile() -> ctx.FormFile()
  • r.MultipartForm -> ctx.MultipartForm()
  • r.RemoteAddr -> ctx.RemoteAddr()
  • r.RequestURI -> ctx.RequestURI()
  • r.TLS -> ctx.IsTLS()
  • r.Cookie() -> ctx.Request.Header.Cookie()
  • r.Referer() -> ctx.Referer()
  • r.UserAgent() -> ctx.UserAgent()
  • w.Header() -> ctx.Response.Header
  • w.Header().Set() -> ctx.Response.Header.Set()
  • w.Header().Set("Content-Type") -> ctx.SetContentType()
  • w.Header().Set("Set-Cookie") -> ctx.Response.Header.SetCookie()
  • w.Write() -> ctx.Write(), ctx.SetBody(), ctx.SetBodyStream(), ctx.SetBodyStreamWriter()
  • w.WriteHeader() -> ctx.SetStatusCode()
  • w.(http.Hijacker).Hijack() -> ctx.Hijack()
  • http.Error() -> ctx.Error()
  • http.FileServer() -> fasthttp.FSHandler(), fasthttp.FS
  • http.ServeFile() -> fasthttp.ServeFile()
  • http.Redirect() -> ctx.Redirect()
  • http.NotFound() -> ctx.NotFound()
  • http.StripPrefix() -> fasthttp.PathRewriteFunc

fasthttp/router

  • /user/{user} named
  • /user/{user}_admin
  • /user/{user?} optional
  • /user/{name:[a-zA-Z]{5}} regex
  • /user/{name?:[a-zA-Z]{5}}
  • /user/{name:*} cache all

fasthttp/session

package main

import (
    "log"
    "time"

    "github.com/fasthttp/session/v2"
    "github.com/fasthttp/session/v2/providers/redis"
)

var serverSession *session.Session

func init() {
    var provider session.Provider
    var err error
    encoder := session.MSGPEncode
    decoder := session.MSGPDecode
    provider, err = redis.New(redis.Config{
        KeyPrefix:   "session",
        Addr:        "127.0.0.1:6379",
        PoolSize:    8,
        IdleTimeout: 30 * time.Second,
    })
    if err != nil {
        log.Fatal(err)
    }
    cfg := session.NewDefaultConfig()
    cfg.EncodeFunc = encoder
    cfg.DecodeFunc = decoder
    serverSession = session.New(cfg)
    if err = serverSession.SetProvider(provider); err != nil {
        log.Fatal(err)
    }
}
// 获取 SessionStore
store, err := serverSession.Get(ctx)
if err != nil {
    ctx.Error(err.Error(), fasthttp.StatusInternalServerError)
    return
}
defer func() {
    if err := serverSession.Save(ctx, store); err != nil {
        ctx.Error(err.Error(), fasthttp.StatusInternalServerError)
    }
}()

// Set
store.Set("foo", "bar")

// Get
val := store.Get("foo")
if val == nil {
    // ...
}

store.Delete(key)

store.Set(bytesKey)
val := store.GetBytes(bytesKey)
store.DeleteBytes(bytesKey)

data := store.GetAll()  // Dict
data := store.Ptr()     // *Dict

store.Flush()
store.Reset()

// Session ID,默认 sessionid
id := store.GetSessionID()
store.SetSessionID(bytesID)

// Session 有效期
changed := store.HasExpirationChanged()
duration := store.GetExpiration()
err := store.SetExpiration(duration)

savsgio/atreugo

构建与 fasthttp 和 fasthttp/router 之上的 HTTP 框架,做了一些简单的封装工作。
提供了几个示例

基础用法:

package main

import (
    "github.com/savsgio/atreugo/v11"
)

func main() {
    config := atreugo.Config{
        Addr: "0.0.0.0:8000",
    }
    server := atreugo.New(config)

    server.GET("/", func(ctx *atreugo.RequestCtx) error {
        return ctx.TextResponse("Hello World")
    })

    server.GET("/echo/{path:*}", func(ctx *atreugo.RequestCtx) error {
        return ctx.TextResponse("Echo message: " + ctx.UserValue("path").(string))
    })

    v1 := server.NewGroupPath("/v1")
    v1.GET("/", func(ctx *atreugo.RequestCtx) error {
        return ctx.TextResponse("Hello V1 Group")
    })

    if err := server.ListenAndServe(); err != nil {
        panic(err)
    }
}

#523 Linux 工具箱 2:convert (图像处理)

2021-04-24

关于 convert 的文章,之前已经写过两篇:

安装 ImageMagick

sudo apt install imagemagick

convert -list type
convert -list font # 支持的字体

获取图片信息

identify markjour.png
identify -verbose markjour.png
identify -format "Size: %w x %h\n" markjour.png

# Exif 信息
identify -format '%[Exif:*]' ~/Pictures/Photos/2019-09-14-18-48-22.jpg

# sudo apt install exif
exif ~/Pictures/Photos/2019-09-14-18-48-22.jpg

清除所有 Exif 信息

convert -strip input.jpg out.jpg
convert +profile "*" input.jpg out.jpg

图片切割

<宽> x <高> + <X轴坐标> + <Y轴坐标>

  1. 如果没有指定坐标,那就是切割图片。
  2. 宽高可以是百分比(不能混用,只要一个百分号那就都是比例)。
convert -crop 300x400+10+10 src.jpg dest.jpg

# 指定中心位址为基准点:
convert -gravity center -crop 300x400+0+0 src.jpg dest.jpg

convert -crop 25%x100% src.jpg dest.jpg

图片合并

之前(convert 图片转换的一次示例)合并图片用的就是这个命令。

# 横向
convert +append markjour-l.jpg markjour-c.jpg markjour-r.jpg markjour.jpg
# 纵向
convert -append markjour-t.jpg markjour-c.jpg markjour-b.jpg markjour.jpg

图片旋转

convert -rotate 90 input.jpg output.jpg  # 顺时针
convert -rotate -90 input.jpg output.jpg # 逆时针

# 左右反转,镜像效果
convert -flop input.jpg output.jpg

# 上下反转,这个和旋转 270 效果还是不一样的
convert -flip input.jpg output.jpg

图片缩放

# 限宽我很常用,控制页面图片尺寸
convert -resize 108x markjour.png markjour-108.png

convert -sample 50%  markjour.png markjour-new.png
convert -sample 200% markjour.png markjour-big.png

PS: 放大时 resize 会自动采样插值,而 sample 不会

图片压缩

convert input.jpg -quality 50 output.jpg

颜色

# 灰度,就是常见的黑白照片效果
convert -colorspace gray input.jpg output.jpg

# 分离 RGB 三个通道,输出三张图片,不知道为什么都是灰色
convert -separate input.png output.png

convert -threshold 40% input.png output.png # 也是一种常见效果,不知道叫什么
convert -negate input.png output.png # 反色

# 黑白(非灰度)sRGB -> Gray 2c
convert -monochrome input.png output.png

# 重新转成 sRGB 模式(但是颜色还是留在黑白两色)
convert -define png:color-type=2 input.png output.png
convert -colorspace sRGB -type truecolor input.jpg output.jpg

# 效果很奇特,可以试试:
convert -remap pattern:gray60 input.png output.png

# 替换
convert -fuzz 15% -fill white -opaque "rgb(143,141,250)" -opaque "rgb(216,217,62)" input.png output.png

滤镜

convert -blur 70 input.png output.png
# 后面的数字对模糊程度有着决定性作用
convert -blur 70x15 input.png output.png

convert -charcoal 2  input.png output.png # 铅笔画风格
convert -noise    3  input.png output.png
convert -paint    4  input.png output.png # 油画风格
convert -spread   50 input.png output.png # 毛玻璃
convert -swirl    60 input.png output.png # 扭曲

convert -paint 4 -raise 5x5 input.png output.png

# 调整透明度
# 先确保图片有 Alpha 通道
convert -define png:format=png32 input.png output.png
convert -channel alpha -fx "0.5" output.png output2.png

边框

# 加边框
convert -mattecolor "#000" -frame 60x60 input.png output.png
convert -mattecolor "#fff" -frame 60x60 input.png output.png

# 相同效果
convert -bordercolor "#fff" -border 60x60 input.png output.png

# 再配合上 raise:
convert -bordercolor "#fff" -border 10x10 input.png output.png
convert -raise 5x5 output.png output2.png

# 去掉边框:
convert -trim -fuzz 10% input.png output.png

水印

convert -fill "#1770CC" \
-font Ubuntu-Mono -pointsize 50 -draw 'text 130,50 "©"' \
-font 楷体_GB2312 -pointsize 40 -draw 'text 50,50 "码厩"' \
-gravity southeast \
input.png output.png

# 改用 RGBA 模式
convert -fill "rgba(23,112,204,0.25)" \
-font Ubuntu-Mono -pointsize 50 -draw 'text 130,50 "©"' \
-font 楷体_GB2312 -pointsize 40 -draw 'text 50,50 "码厩"' \
-gravity southeast \
input.png output.png

# 这个不错,京东那里学来的:
convert -size 100x100 -fill "#1770CC" -gravity center \
-font Ubuntu -pointsize 30 -draw 'rotate -45 text 0,0 "markjour"' \
xc:none miff:- | composite -tile -dissolve 25 - input.png output.png

# 图片水印
convert -size 150x50 -fill "#1770CC" -gravity center \
-font Ubuntu -pointsize 30 -draw 'text 0,0 "markjour"' \
xc:none /tmp/mark.png
convert input.png -gravity southeast -compose over /tmp/mark.png -composite output.png

其他

# 查看图片
# GNOME 桌面好像都是 eog
eog markjour.png

# 或者使用 ImageMagick 自带图片查看工具:
display markjour.png

# 查看颜色信息
convert xc:"#fff" -colorspace sRGB -format "%[pixel:u.p{0,0}]\n" txt:
convert xc:"#fff" -colorspace sRGB -format "%[pixel:u.p{0,0}]\n" info:

convert xc:"#fff" -colorspace sRGB -format "rgb(%[fx:int(255*r)],%[fx:int(255*g)],%[fx:int(255*b)])\n" info:

# 获取指定像素点的颜色(RGB)
convert "input.png[1x1+100+100]" -format "rgb(%[fx:int(255*r)],%[fx:int(255*g)],%[fx:int(255*b)])\n" info:

# 创建一张新图片
convert -size 1000x1000 xc:none /tmp/image.png
convert -size 1000x1000 xc:transparent /tmp/image.png
convert -size 1000x1000 xc:white /tmp/image.png

webp

sudo apt install -y webp
  • cwebp 转成 WEBP 格式
  • dwebp 转成别的格式
cwebp -o xxx.png xxx.webp
dwebp -o xxx.webp xxx.png

参考资料与拓展阅读

#521 正则表达式

2021-04-14
  1. https://zh.wikipedia.org/wiki/正则表达式
  2. 2021/12/19,正则表达式历史

正则表达式, Regular Expression, 简写: regex, regexp, 正则。
一般用来以指定模式对字符串进行查找,替换,切割。

基本知识

Linux 下, grep, find 等命令支持几种风格:

find -regextype help
# find: 未知的正则表达式类型 ‘help’;合法的类型是 ‘findutils-default’, ‘ed’, ‘emacs’, ‘gnu-awk’, ‘grep’, ‘posix-awk’, ‘awk’, ‘posix-basic’, ‘posix-egrep’, ‘egrep’, ‘posix-extended’, ‘posix-minimal-basic’, ‘sed’。
man grep | grep -E '\-regexp$'
#        -E, --extended-regexp
#        -G, --basic-regexp
#        -P, --perl-regexp
#        -w, --word-regexp
#        -x, --line-regexp

因为历史原因,大体上可以分为:

  • 基本型正则表达式(Basic Regular Expression,BRE)
  • 扩展型正则表达式(Extended Regular Express,ERE)
  • PCRE(Perl兼容正则表达式,Perl Compatible Regular Expressions)

我见过的编程语言中都是采用最为强大的 PCRE 风格:

  • PHP 是 pgeg_xxx 系列方法。
  • Python 是 re 包。
  • Go 是 regexp 包。
  • C 直接使用 regex.h (libc)
  • C 老版本也是 regex.h,或者使用第三方库(如 boost), C 11 之后就内置正则支持了(std::regex, 默认 ECMAScript 风格)

语法

特殊字符

| Characters / constructs | Corresponding article |
| :------------------------------------------------------------------------------------------------------------------------------------------------- | :---------------------------- | ----------------- |
| \ , . , \cX , \d , \D , \f , \n , \r , \s , \S , \t , \v ,
\w , \W , \0 , \xhh , \uhhhh , \uhhhhh , [\b] | Character classes |
| ^ , $ , x(?=y) , x(?!y) , (?<=y)x , (?<!y)x , \b , \B | Assertions |
| (x) , (?:x) , (?<Name>x) , x | y,[xyz],[^xyz],\Number | Groups and ranges |
| * , + , ? , x{n} , x{n,} , x{n,m} | Quantifiers |
| \p{UnicodeProperty} , \P{UnicodeProperty} | Unicode property escapes |

范围

  • .
  • \w , \d , \s , \W , \D , \S
  • [abc] [^abc] [a-z]

分组

  • (abc)
  • \1
  • (?:abc)
  • (?=abc)
  • (?!abc)

量词

  • a* , a+ , a?
  • a{7,9} , a{7} , a{7,}
  • a+? , a{2,}?

其他

  • ^ , $
  • ab|cd or
  • \b , \B word

修饰符

JavaScript:

  • g global
  • i case insensitive
  • m multiline
  • s single line (dotall)
  • u unicode
  • y sticky

Go:

  • i
  • m
  • s
  • U

Python:

  • re.A, re.ASCII
  • re.I, re.IGNORECASE
  • re.M, re.MULTILINE
  • re.S, re.DOTALL
  • re.DEBUG
  • re.X, re.VERBOSE

参考资料与拓展阅读