写在前面

本篇博客主要会记录和总结一些我平时在 Unix 环境下遇到的一些问题,包括但不局限于命令行,Git 操作等

CommandLine

可执行文件存放位置

如果我有一个可执行文件,我应该把它放在哪里
  • /usr/local/bin
    • 最推荐的位置
    • 所有用户都可访问
    • 该目录默认在 PATH
    • 适合系统级的第三方软件
  • $HOME/.local/bin
    • 用户级安装的推荐位置
    • 只对当前用户可用
    • 需要确保该目录在 PATH

要将文件复制到 /usr/local/bin,使用:

sudo cp 程序名 /usr/local/bin/
sudo chmod +x /usr/local/bin/程序名
Behind The Scenes

当你用 sudo cp 复制文件到 /usr/local/bin/ 时,复制后的文件所有者会变成 root,且文件的权限会继承源文件的权限 chmod +x 会同时修改三个组的权限:

  1. 所有者权限(owner/user)
  2. 组权限(group)
  3. 其他用户权限(others)

chmod +x 实际上等同于 chmod ugo+xchmod a+xa 表示 all

How to use rsync

rsync 是一个文件同步和传输工具

# 复制目录
rsync -av /source/folder/ /destination/folder/

# 带进度条
rsync -avP /source/folder/ /destination/folder/

# 本地到远程
rsync -avz /local/folder/ user@remote:/remote/folder/

# 远程到本地
rsync -avz user@remote:/remote/folder/ /local/folder/

# 使用 --exclude
rsync -av --exclude='*.txt' source/ destination/

# 多个排除
rsync -av --exclude='*.txt' --exclude='*.pdf' source/ destination/

设置项目级 ROOT 环境变量实现快速目录导航

原问题:如何实现在不同的项目中设置不同的 ROOT 环境变量,以便于我在某个项目中的子文件夹中能够迅速跳转到项目根目录

使用 direnv:

# 安装 direnv
sudo apt install direnv  # Ubuntu/Debian
brew install direnv      # MacOS

# 在 shell 配置文件(~/.bashrc 或 ~/.zshrc)中添加:
eval "$(direnv hook bash)"  # 或 zsh

# 在项目根目录创建 .envrc 文件
echo 'export ROOT=$PWD' > .envrc
direnv allow

然后就可以在项目中的任意一个位置通过 cd $ROOT 跳转到项目根目录了

direnv 的常用场景包括:

  1. 项目特定的环境变量:
# Node.js 项目
export NODE_ENV=development
export PORT=3000

# Python 项目
export PYTHONPATH=$PWD/src
export FLASK_ENV=development
  1. 工具路径和版本管理:
# 指定项目 Node 版本
use node 16.14.0

# 指定 Python 虚拟环境
layout python3

# Go 项目配置
export GOPATH=$PWD/.go
layout go
  1. API 密钥和配置:
export AWS_ACCESS_KEY_ID=xxx
export AWS_SECRET_ACCESS_KEY=xxx
export DATABASE_URL="postgresql://user:pass@localhost:5432/db"
  1. 项目别名和快捷命令:
alias run="npm run"
alias test="pytest"
alias db="psql $DATABASE_URL"
  1. 路径简写:
export SRC=$PWD/src
export DOCS=$PWD/docs
export CONFIG=$PWD/config

PATH_add scripts
PATH_add bin

.bash_profile 和 .bashrc 有什么区别

.bash_profile:

  • 用户登录时加载一次
  • 适用于登录shell(login shell)
  • 通常用于设置环境变量,如 PATH、JAVA_HOME 等
  • 典型场景:SSH 远程登录、图形界面登录时

.bashrc:

  • 每次打开新的终端窗口时都会加载
  • 适用于交互式非登录 shell(non-login shell)
  • 通常用于设置命令别名、shell 函数等交互相关的配置
  • 典型场景:在已登录系统后打开新终端窗口

为确保配置生效,.bash_profile 中通常会包含这样的代码来源引 .bashrc

if [ -f ~/.bashrc ]; then
    . ~/.bashrc
fi

所以个人的配置应该写到哪个文件里呢?

建议如下配置:

写在 .bashrc 中:

  • 命令别名 (alias)
  • shell 函数
  • 命令补全设置
  • 其他交互式使用的配置

写在 .bash_profile 中:

  • PATH 环境变量
  • JAVA_HOME, MAVEN_HOME 等程序路径
  • 其他需要被所有子程序继承的环境变量
Summary

推荐做法:

  • 将所有个人配置写在 .bashrc
  • .bash_profile 中只保留环境变量,并源引 .bashrc
  • 这样既确保环境变量只设置一次,又能让交互式配置在每个新终端中生效

如何在 ssh 中合并多条命令

SSH 中合并多条命令有以下几种方法:

  1. 使用分号分隔:
ssh user@host "command1; command2; command3"

# 可以使用换行符和引号包围多行命令
ssh user@host "cd /path/to/dir;
ls -l;
df -h"
  1. 使用 && 确保前一条命令成功才执行下一条:
ssh user@host "command1 && command2 && command3"
Summary

几种常见分隔符的对比:

A ; B: 无论 A 成功失败,B 都执行 A && B: 只有 A 成功,B 才执行 A || B: 只有 A 失败,B 才执行

如何在脚本中实现自动输入 sudo 密码

echo "password" | sudo -S command
  • -S 选项告诉 sudo 从标准输入读取密码
  • | 管道符将 echo 的输出传给 sudo 命令

如果使用 ssh 在远程执行脚本的话:

ssh -t $node1 "echo '$PASSWORD' | sudo -S command"
  • 没有 -t 时,可能会遇到 “sudo: no tty present and no askpass program specified” 这样的错误
  • -t 选项强制 SSH 分配一个伪终端(pseudo-terminal, PTY)

PTY (Pseudo Terminal) 和 SSH 的工作原理:

PTY 和 SSH 直接建立的终端的主要区别在于它们的工作方式和用途:

  1. SSH 默认终端(不带 -t):
本地机器         SSH通道          远程机器
程序 --> SSH客户端 -----> SSH服务器 --> 远程程序
     (标准输入输出重定向)      (无终端环境)

# 特点:
- 只是简单的标准输入输出重定向
- 不支持终端特性(如光标控制)
- 适合运行非交互式命令
  1. PTY 终端(带 -t):
本地机器          SSH通道          远程机器
终端模拟器 --> SSH客户端 -----> SSH服务器 --> PTY --> 远程程序
     (完整终端环境模拟)        (完整终端环境)

# 特点:
- 完整的终端环境模拟
- 支持所有终端特性
- 适合交互式程序

PTY (Pseudo Terminal) 的概念:

实际终端设备        PTY主设备(master)    PTY从设备(slave)     应用程序
(keyboard/screen) <--> (/dev/ptmx) <--> (/dev/pts/N) <--> (如 bash, sudo)
  • PTY 是一对虚拟设备:主设备(master)和从设备(slave)
  • 主设备负责与实际终端设备通信
  • 从设备为应用程序提供一个类似实际终端的接口

SSH 终端分配过程:

本地机器                     远程机器
ssh client                  sshd
    |                         |
    |--- SSH连接请求 --------->|
    |<-- 认证握手 ------------>|
    |                         |
[带-t选项]:                    |
    |-- 请求PTY分配 ---------> |
    |                     创建PTY
    |                         |
    |<-- PTY信息 ------------- |
    |                         |
    |-- 启动shell或命令 ----->  |-- PTY从设备 --> 目标程序

为什么某些命令需要 PTY:

# sudo 需要 PTY 的原因:
- 安全考虑:确保是真实用户在操作
- 密码输入:需要控制终端来安全读取密码
- 信号处理:正确处理 Ctrl+C 等终端信号

# 示例:sudo 的行为差异
ssh server "sudo ls"          # 可能失败:no tty present
ssh -t server "sudo ls"       # 正常工作:有PTY支持

实际应用中的区别:

# 不需要 PTY 的命令
ssh server "ls -l"
ssh server "echo hello"

# 需要 PTY 的命令
ssh -t server "sudo apt update"
ssh -t server "vim file.txt"
ssh -t server "top"

环境变量对比:

# 不带 -t
$ ssh server "env | grep TERM"
# 可能为空或基础值

# 带 -t
$ ssh -t server "env | grep TERM"
TERM=xterm-256color

TERM 环境变量指定了当前终端的类型,它告诉程序如何正确地处理终端输出,比如颜色、光标移动等特性 TERM=xterm-256color 表示终端支持:

  • 256色显示
  • 光标定位
  • 清屏
  • 粗体/斜体
  • 鼠标事件
  • 特殊键(方向键等)

举一个最简单的例子:

# 不带 -t(无或基础 TERM)
$ ssh server "ls --color"
# 可能无颜色显示,因为程序检测不到终端支持颜色

# 带 -t(TERM=xterm-256color)
$ ssh -t server "ls --color"
# 显示完整的颜色输出

实际有什么影响呢?

# 不带 -t
$ ssh server "vim file.txt"
# 失败,因为 vim 需要知道终端类型来处理光标、颜色等

# 带 -t
$ ssh -t server "vim file.txt"
# 正常工作,vim 知道如何在 xterm-256color 终端下工作

在使用 tar 命令解压文件时,如果文件夹已经存在,是在此基础上添加,还是说会自动清空这个文件夹,再解压?

如果解压的文件与目标文件夹中的文件同名:

  • 会直接覆盖已存在的文件
  • 不会提示确认

如果是新文件:

  • 会直接添加到目标文件夹中
  • 原有的其他文件保持不变

Input/Output Redirection and Process Substitution

输入/输出重定向

  • 使用 < 将文件或数据传递给命令的标准输入
  • 使用 > 将命令的标准输出写入到一个文件中
    • >> 表示追加到文件中
# 将 file.txt 的内容传递给 sort 命令进行排序
sort < file.txt
# 将 ls 的输出重定向到 output.txt 文件
ls > output.txt
# 使用 >> 将 echo 的输出追加到 output.txt 文件
echo "New entry" >> output.txt

进程替换

进程替换(Process Substitution)是 Bash 提供的一种技术,用于将一个命令的输出作为文件名或标准输入来使用。这通常用于将命令的输出传递给其他接受文件或输入流的命令,允许我们实现更灵活的操作。

Bash 提供两种进程替换语法:

  • <(command):将命令 command 的输出重定向为输入流,可以把它当作一个“文件”来使用。
  • >(command):将输出重定向为命令 command 的输入流。

进程替换常见的使用场景包括将命令的输出传递给 readdiffcat 等命令,尤其是那些期待文件路径或输入流的命令。

进程替换的工作原理是将命令的输出或输入重定向到一个临时文件或文件描述符(例如 /dev/fd/63),并返回该文件描述符的路径,使得调用的命令能够读取或写入这个文件描述符。

当使用 <(command)>(command) 时,Bash 会自动创建一个临时文件描述符,并将其传递给外部命令。

因此,进程替换的实际效果就是使用命令的输出(或输入)而无需创建中间文件

# 假设我们想比较两个命令的输出,可以使用 `diff` 命令与进程替换来实现:
diff <(ls /path/to/dir1) <(ls /path/to/dir2)

# 进程替换可以用于将多个值传递给 `read` 命令,允许直接从命令输出中读取多个变量
read -r var1 var2 < <(echo "hello world")

# 更实用的一个例子是在脚本中,实现函数返回值的效果
read -r native_latency native_p99 < <(run_iot_test "native" "$thread")

详细解释一下最后一个例子:

  • read 从标准输入中读取数据
  • 我们可以用类似于 read < 的形式利用输入重定向从文件中读取数据
  • run_iot_test 函数中的最后一个语句为 echo "$latency $p99",所以我们使用一个单独的 <() 来将这个函数的标准输出重新定向到一个临时文件中

看起来像是把一个程序的标准输出喂到了另一个程序的标准输入中,为什么不能使用管道呢?

考虑下面这个简单脚本:

#!/bin/bash

echo "hello world" | read -r var1 var2

echo -n $var1
echo -n $var2

read 是在一个子 shell 中执行的,因此变量 var1 和 var2 的赋值在子 shell 中完成,而子 shell 中的变量无法传递回父 shell,因此在最后的 echo 中无法获得 var1 和 var2 的值

要想在同一个 shell 中将输出重定向到另一个程序的标准输入中,可以使用进程替换的方法

也可以使用 Here String <<<$() > <<< $(...) 将命令的输出视为一个单行字符串输入。这种方式将命令的输出在进行重定向之前先执行命令替换(即 $(command)),然后将整个结果作为一行输入传递给 read。 如果 command 输出的是多行数据,这种写法只会读取第一行的内容,而后续的行将被忽略

Why rm "$tar_dir/iot-*.txt" didn’t work?

这句语句本来是打算删除 $tar_dir 文件夹下所有符合 iot-*.txt 模式的文件

但最终执行时却并没有生效,原因如下:

在 Shell(如 Bash)中,引号会影响通配符(如 *)的展开方式。具体来说:

  • 双引号 ":会保留大多数特殊字符的字面意义,但仍允许变量展开(如 $tar_dir)
  • 单引号 ':会将所有内容视为字面值,包括变量和通配符
  • 无引号:允许变量和通配符被展开

在这个例子中,双引号导致通配符无法被展开,我们可以修改这个操作

rm "$tar_dir"/iot-*.txt

Docker

如何使用 docker 限制一个容器能使用的核心数和内存大小

  • 限制 CPU:
# 限制使用 2 个 CPU 核心
docker run --cpus=2 镜像名称

# 或者使用 CPU 份额(默认1024)
docker run --cpu-shares=512 镜像名称

# 指定只能在 CPU 0 和 CPU 1 上运行
docker run --cpuset-cpus="0,1" 镜像名称
  • 限制内存
# 限制最大内存使用为 2GB
docker run -m 2g 镜像名称
# 或者使用 MB 单位
docker run --memory=2048m 镜像名称

# 设置 swap 限制为 1GB
docker run --memory-swap=1g 镜像名称
Info

  • 对于正在运行的容器,可以使用 docker update 命令来动态调整资源限制,不需要停止容器
  • 资源限制的更新是即时生效的,但不会影响容器内已经运行的进程

使用 docker update 更新资源限制时:

  • 新启动的进程会受到这些新限制的约束
  • 容器内已经运行的进程不会被强制终止或重启
    • 已分配的内存不会被立即回收
    • 如果进程已经使用了超过新限制的资源,它可以继续使用这些资源
Summary

  • 提高限制:进程可以立即使用新增的资源
  • 降低限制:已使用的资源不会被强制回收,只会限制新的资源申请

Docker 容器分类

守护式容器(Daemon Containers)

  • 特征:
    • 必须有一个前台进程(foreground process)持续运行
    • 如果主进程退出,容器就会停止
    • 通常使用 PID 1 进程
  • 常见用途:
    • Web 服务器(Nginx, Apache)
    • 数据库(MySQL, PostgreSQL)
    • 消息队列(RabbitMQ, Redis)
    • 应用服务器(Node.js, Java)

Dockerfile 示例:

FROM nginx
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

任务型容器(Task Containers)

  • 特征:
    • 运行完特定任务就退出
    • 通常结合 docker run --rm 使用,完成后自动删除容器
    • 经常用于 CI/CD 流程
  • 常见用途:
    • 数据备份
    • 代码编译
    • 数据处理
    • 定时任务

Dockerfile 示例:

FROM python
COPY script.py /
CMD ["python", "script.py"]

运行方式的区别

守护式容器:

# 后台运行
docker run -d nginx

# 查看日志
docker logs container_id

# 进入容器
docker exec -it container_id bash

任务型容器:

# 运行并自动删除
docker run --rm alpine echo "Hello World"

# 用作构建环境
docker run --rm -v $(pwd):/app node npm run build
如何让任务型容器长久运行呢?

docker run -d ubuntu tail -f /dev/null

tail -f 命令的工作原理:

  • 它会监视文件的变化
  • 当文件没有变化时,进程会进入睡眠状态
  • 几乎不消耗 CPU 资源

/dev/null 的特点:

  • 这是一个特殊的设备文件
  • 它永远不会有新内容

所以 tail -f 在监视它时会一直处于等待状态

服务器拉取不了镜像怎么办?使用 docker save 和 docker load

常见的加镜像源,挂梯子的办法这里就不说了,这里介绍一个「一次性」的方法

# 在能访问互联网的笔记本上
docker pull redis:latest
docker save redis:latest > redis.tar
# 或者压缩以减小体积
docker save redis:latest | gzip > redis.tar.gz

# 通过 scp 或其他方式传输到目标机器
scp redis.tar.gz remote:/path/to/

# 在目标机器上
docker load < redis.tar.gz

注意,上面这个简单的例子适用于笔记本和远端服务器架构相同的情况,比如都是 x86

如果你的笔记本是 M 芯片系列的 MacBook,你的架构是 arm64,直接套用上述方法会报错,需要修改一下 docker pull 的操作:

# 显式指定 linux/amd64 平台
docker pull --platform linux/amd64 apache/kvrocks

# 查看镜像确认架构
docker inspect apache/kvrocks | grep Architecture

后续操作不变

在 Docker 中,同一个镜像标签(例如 apache/kvrocks:latest)在本地只会存储一个平台的版本。当你使用 –platform 拉取镜像时,Docker 会替换掉本地已有的同名镜像。

Python

uv

Uv is an extremely fast Python package and project manager, written in Rust.

With Project

With pyprojects.toml

uv sync

The pip interface

  • Creating a virtual environment
uv venv
uv venv my-name
uv venv --python 3.11
  • Using a virtual environment
# bash
source .venv/bin/activate

# fish
source .venv/vin/activate.fish
  • Installing packages
uv pip install flask ruff
uv pip install -r requirements.txt

ruff

Ruff is an extremely fast Python linter and code formatter, written in Rust.

# With pip.
pip install ruff

# Lint all files in the current directory (and any subdirectories).
ruff check

# With automatic fix
ruff check --fix

# Format all files in the current directory (and any subdirectories).
ruff format

Git

Merge vs Rebase

See here for reference.

Git rebase is closer to a merge. The difference in rebase is:

  • the local commits are removed temporally from the branch.
  • run the git pull
  • insert again all your local commits.

So that means that all your local commits are moved to the end, after all the remote commits. If you have a merge conflict, you have to solve it too.

merge-rebase

git rebase helps create a linear project history by transferring your feature branch commits to the top of the main branch, effectively “replaying” changes as if they were applied sequentially after the main branch’s most recent commits. This linearity makes the history easier to read and understand.

Caution

DO NOT rebase in public branches!

如何区分 .gitignore 中忽略的是文件还是文件夹

区分文件和文件夹的方法:

  • 文件夹:在末尾添加斜杠 /,例如 node_modules/
  • 文件:直接写文件名,例如 config.json
Tip

当你忽略一个文件夹时(例如 node_modules/),该文件夹下的所有内容(包括子文件夹和文件)都会被自动忽略

如何确定一个文件是否被 .gitignore 忽略了

  1. 使用 git ls-files 检查特定文件
git ls-files 文件名
  • 如果文件被追踪,会显示文件名
  • 如果没有输出,说明文件未被追踪
  1. 使用 git check-ignore 检查文件是否被忽略
git check-ignore -v 文件名
  • 如果有输出,说明文件被 .gitignore 规则忽略
  • 如果没有输出且返回值为 1,说明文件没有被忽略规则匹配

作为仓库所有者,如何修改别人的 PR?

假设场景:

  • 你的仓库名为 my-repo
  • Pull Request 的发起者是 user_name
  • Pull Request 的分支名为 branch_name
  • Pull Request 的编号是 123
  • 你的远程仓库名为 origin

步骤:

  1. 进入你的本地仓库目录:

    cd path/to/your/my-repo
    
  2. 获取 Pull Request 的分支:

    git fetch origin pull/123/head:pr-123-fix
    
    • origin:你的远程仓库名。
    • pull/123/head:这是 GitHub 特定的语法,表示获取编号为 123 的 Pull Request 的头部。
    • pr-123-fix:这是你本地要创建的分支名称,可以自定义,例如 pr-fixfix-branch-name 等,建议命名能体现此分支的作用。
  3. 切换到新创建的本地分支:

    git checkout pr-123-fix
    
  4. 进行修改并提交:

    • 现在你在这个分支上,可以对代码进行任意修改。
    • 修改完成后,使用常规的 Git 命令进行提交:
    git add .
    git commit -m "Your descriptive commit message"
    
  5. 将修改推送到远程的原始分支:

    git push origin pr-123-fix:branch_name
    
    • origin:你的远程仓库名
    • pr-123-fix:你刚刚创建并修改的本地分支名
    • branch_name:发起 Pull Request 的用户(user_name) 的仓库中的原始分支名

    关键点解释: 这一步直接将你本地分支 pr-123-fix 的内容推送到远程仓库的 branch_name 分支。因为你是仓库所有者或协作者,你有权限直接推送到这个分支

简化命令 (可选):

如果你觉得 git fetch origin pull/123/head:pr-123-fix 太长,可以配置一下 git fetch 的 refspec, 这样下次就可以简化命令了

打开 .git/config 文件,找到 [remote "origin"] 部分,修改 fetch 这一行,添加 +refs/pull/*/head:refs/remotes/origin/pr/*

[remote "origin"]
        url = [email protected]:your_username/my-repo.git
        fetch = +refs/heads/*:refs/remotes/origin/*
        fetch = +refs/pull/*/head:refs/remotes/origin/pr/*

之后你就可以使用更简短的命令来获取 Pull Request 分支:

git fetch origin
git checkout -b pr-123-fix origin/pr/123

How to use git tag

常用操作如下:


# 列出所有标签
git tag

# 按模式列出标签
git tag -l "v0.4.*"

# 创建标签
git tag -a v0.4.0 -m "Version 0.4.0"

# 推送特定标签
git push origin v1.5

# 推送所有标签
git push origin --tags

# 删除本地标签:
git tag -d v1.4

# 删除远程标签:
git push origin --delete v1.4
git tag -a 是什么

git tag -a 中的 -a 选项表示创建一个"附注标签"(annotated tag)。这是 Git 中两种主要标签类型之一, 另一种是轻量标签(lightweight tag)。

附注标签(Annotated Tag):

  • 使用 -a 选项创建
  • 存储完整的对象,包含标签名、电子邮件、日期、标签信息等
  • 可以使用 GPG 签名和验证
  • 通常用于发布版本等重要节点
  • 创建命令: git tag -a v1.4.0 -m "Version 1.4"

轻量标签(Lightweight Tag):

  • 不使用 -a-s-m 选项
  • 只是特定提交的引用
  • 本质上是一个不会改变的分支
  • 创建命令: git tag v1.4-lw

How to use git commit --amend

git commit --amend 通常在以下场景使用:

  1. 修复最后一次提交的拼写错误:
# 原提交信息打错了
git commit -m "fix: add user validaton"  # validation 拼错了
# 修复
git commit --amend -m "fix: add user validation"
  1. 遗漏了文件或修改:
# 已经提交
git commit -m "feat: add login page"

# 发现忘了提交某个文件
git add forgotten_file.js
git commit --amend --no-edit
  1. 合并琐碎修改:
# 已经提交
git commit -m "feat: implement login logic"

# 发现一个小问题需要修复
vim login.js  # 修复问题
git add login.js
git commit --amend --no-edit  # 直接合并到上一次提交

以上操作针对于本地提交,如果更改已经被 git push 到了远端仓库中,则

git push --force-with-lease origin main

即使使用 –force-with-lease,也可能会覆盖别人的工作:

这里解释一下 --force-with-lease 的工作机制:

–force-with-lease 的检查机制

--force-with-lease 不是直接检查实际的远程仓库状态,而是检查本地 git 记录的远程分支状态(remote tracking reference)。

具体来说:

本地 git 维护了三个重要的引用:
1. 本地分支状态(HEAD)
2. 本地记录的远程分支状态(refs/remotes/origin/main)
3. 实际远程分支状态(在远程仓库上)

当你执行 git fetch 时,才会更新"本地记录的远程分支状态"

实际例子

初始状态:
- 远程仓库: commit A
- 本地分支: commit A
- 本地记录的远程状态: commit A

步骤1: 其他人推送 commit B
- 远程仓库: commit B
- 本地分支: commit A
- 本地记录的远程状态: commit A (未执行 fetch,所以没更新)

步骤2: 你修改并 amend
- 远程仓库: commit B
- 本地分支: commit A'
- 本地记录的远程状态: commit A (仍然未更新)

步骤3: 执行 push --force-with-lease
- git 只会检查"本地记录的远程状态"(commit A)
- 不会直接检查实际远程仓库的状态(commit B)

如何安全使用

为了安全起见,应该养成这样的习惯:

# 1. 先获取最新状态
git fetch

# 2. 如果需要,可以查看远程变化
git log origin/main

# 3. 然后再决定是否执行 force-with-lease
git push --force-with-lease origin main

或者使用更安全的组合命令:

# 这个命令会确保在 push 之前先更新本地的远程跟踪状态
git push --force-with-lease origin main --force-if-includes

这样就能真正达到保护远程分支的目的。

How to untrack a file in a git repo?

  1. Add the File to .gitignore

比如说需要忽略所有的 .bak 文件

.bak
  1. Remove the File from Git’s Tracking
git rm --cached <file>
# batch process
# untrack all files whose type is bak
fd --extension bak --type f -0 | xargs -0 git rm --cached

顺带说明一下这里为什么要使用 -0

-0 表示以空字符 (\0) 作为输入分隔符,而不是默认的空格或换行符 如 my file.bak,如果不使用 -0,可能会被错误地解析为两个独立的文件 myfile.bak

  1. Commit the Changes
git commit -m "Stop tracking .bak files"

What are the best practices for naming git branches?

  1. 使用前缀结构

使用前缀来表示分支的类型和用途,这样可以更直观地了解分支的目的。常见的前缀包括:

  • feature/:用于新功能开发。例如,feature/user-authentication
  • bugfix/fix/:用于修复 bug。例如,bugfix/fix-login-issue
  • hotfix/:用于紧急修复线上问题。例如,hotfix/urgent-payment-fix
  • release/:用于发布准备和版本管理。例如,release/v1.0.0
  • test/:用于测试相关的代码或功能。例如,test/api-performance
  1. 使用简洁但具描述性的名字

分支名称应该简短但能准确描述该分支的目的。避免使用太笼统的名称,比如 new-feature,而是采用具描述性的命名,如 feature/user-login-ui.

  • 在 Git 中,建议使用连字符(-)而不是下划线(_)或空格,这样分支名更易读,并且与常见的命名风格保持一致
  • 分支名称最好使用小写字母,避免使用大写字母或混合大小写,这可以保持风格一致且减少错误

Regex

Extract a number

要求从下面这个 iot-oreo-8.txt 文件中提取出 TXN 对应的 P99 Latency,即57727

# 省略许多行
TXN    - Takes(s): 34.1, Count: 9105, OPS: 266.6, Avg(us): 27405, Min(us): 21312, Max(us): 60639, 50th(us): 23071, 90th(us): 54943, 95th(us): 56511, 99th(us): 57727, 99.9th(us): 59263, 99.99th(us): 60383
TXN_ERROR - Takes(s): 34.1, Count: 895, OPS: 26.2, Avg(us): 22831, Min(us): 21104, Max(us): 56383, 50th(us): 22479, 90th(us): 23423, 95th(us): 23791, 99th(us): 25167, 99.9th(us): 56319, 99.99th(us): 56383

首先使用 rg 匹配到对应行:

rg '^TXN\s' iot-oreo-8.txt
  • ^ 代表匹配以 TXN 开头的
  • \s 表示匹配一个空格,与 TXN_ERROR 做区分

得到:

TXN    - Takes(s): 34.1, Count: 9105, OPS: 266.6, Avg(us): 27405, Min(us): 21312, Max(us): 60639, 50th(us): 23071, 90th(us): 54943, 95th(us): 56511, 99th(us): 57727, 99.9th(us): 59263, 99.99th(us): 60383

然后我们再来考虑如何匹配到 99th(us)

rg '^TXN\s' iot-oreo-8.txt | rg -o '\s99th\(us\): [0-9]+'
  • -o 表示只输出匹配的部分(否则会输出整个行)

得到:

 99th(us): 57727

最后再从这段文字中提取出数据

rg '^TXN\s' iot-oreo-8.txt | rg -o '\s99th\(us\): [0-9]+' | choose 1
  • choose 1 表示从当前行中选取第 2 个字段
  • 也可以使用 cut,但要注意这段文字最前面的空格,应该使用 cut -d' ' -f3

得到:

57727