写在前面
本篇博客主要会记录和总结一些我平时在 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/程序名
当你用 sudo cp
复制文件到 /usr/local/bin/
时,复制后的文件所有者会变成 root,且文件的权限会继承源文件的权限
chmod +x
会同时修改三个组的权限:
- 所有者权限(owner/user)
- 组权限(group)
- 其他用户权限(others)
chmod +x
实际上等同于 chmod ugo+x
或 chmod a+x
(a
表示 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 的常用场景包括:
- 项目特定的环境变量:
# Node.js 项目
export NODE_ENV=development
export PORT=3000
# Python 项目
export PYTHONPATH=$PWD/src
export FLASK_ENV=development
- 工具路径和版本管理:
# 指定项目 Node 版本
use node 16.14.0
# 指定 Python 虚拟环境
layout python3
# Go 项目配置
export GOPATH=$PWD/.go
layout go
- API 密钥和配置:
export AWS_ACCESS_KEY_ID=xxx
export AWS_SECRET_ACCESS_KEY=xxx
export DATABASE_URL="postgresql://user:pass@localhost:5432/db"
- 项目别名和快捷命令:
alias run="npm run"
alias test="pytest"
alias db="psql $DATABASE_URL"
- 路径简写:
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 等程序路径
- 其他需要被所有子程序继承的环境变量
推荐做法:
- 将所有个人配置写在
.bashrc
中 - 在
.bash_profile
中只保留环境变量,并源引.bashrc
- 这样既确保环境变量只设置一次,又能让交互式配置在每个新终端中生效
如何在 ssh 中合并多条命令
SSH 中合并多条命令有以下几种方法:
- 使用分号分隔:
ssh user@host "command1; command2; command3"
# 可以使用换行符和引号包围多行命令
ssh user@host "cd /path/to/dir;
ls -l;
df -h"
- 使用 && 确保前一条命令成功才执行下一条:
ssh user@host "command1 && command2 && command3"
如何在脚本中实现自动输入 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 直接建立的终端的主要区别在于它们的工作方式和用途:
- SSH 默认终端(不带
-t
):
本地机器 SSH通道 远程机器
程序 --> SSH客户端 -----> SSH服务器 --> 远程程序
(标准输入输出重定向) (无终端环境)
# 特点:
- 只是简单的标准输入输出重定向
- 不支持终端特性(如光标控制)
- 适合运行非交互式命令
- 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 终端下工作
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
的输入流。
进程替换常见的使用场景包括将命令的输出传递给 read
、diff
、cat
等命令,尤其是那些期待文件路径或输入流的命令。
进程替换的工作原理是将命令的输出或输入重定向到一个临时文件或文件描述符(例如 /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 容器分类
守护式容器(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.
- 🚀 A single tool to replace
pip
,pip-tools
,pipx
,poetry
,pyenv
,twine
,virtualenv
, and more. - ⚡️ 10-100x faster than
pip
. - 🐍 Installs and manages Python versions.
- 🛠️ Runs and installs Python applications.
- ❇️ Runs single-file scripts, with support for inline dependency metadata.
- 🗂️ Provides comprehensive project management, with a universal lockfile.
- 🔩 Includes a pip-compatible interface for a performance boost with a familiar CLI.
- 🏢 Supports Cargo-style workspaces for scalable projects.
- 💾 Disk-space efficient, with a global cache for dependency deduplication.
- 🖥️ Supports macOS, Linux, and Windows.
The pip interface
- Creating a virtual environment
uv venv
uv venv my-name
uv venv --python 3.11
- Using a virtual environment
source .venv/bin/activate
- 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.
- ⚡️ 10-100x faster than existing linters (like Flake8) and formatters (like Black)
- 🐍 Installable via
pip
🛠️pyproject.toml
support - ⚖️ Drop-in parity with Flake8, isort, and Black
- 🔧 Fix support, for automatic error correction (e.g., automatically remove unused imports)
- 📏 Over 800 built-in rules, with native re-implementations of popular Flake8 plugins, like flake8-bugbear
# 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
作为仓库所有者,如何修改别人的 PR?
假设场景:
- 你的仓库名为
my-repo
- Pull Request 的发起者是
user_name
- Pull Request 的分支名为
branch_name
- Pull Request 的编号是
123
- 你的远程仓库名为
origin
步骤:
进入你的本地仓库目录:
cd path/to/your/my-repo
获取 Pull Request 的分支:
git fetch origin pull/123/head:pr-123-fix
origin
:你的远程仓库名。pull/123/head
:这是 GitHub 特定的语法,表示获取编号为123
的 Pull Request 的头部。pr-123-fix
:这是你本地要创建的分支名称,可以自定义,例如pr-fix
、fix-branch-name
等,建议命名能体现此分支的作用。
切换到新创建的本地分支:
git checkout pr-123-fix
进行修改并提交:
- 现在你在这个分支上,可以对代码进行任意修改。
- 修改完成后,使用常规的 Git 命令进行提交:
git add . git commit -m "Your descriptive commit message"
将修改推送到远程的原始分支:
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
通常在以下场景使用:
- 修复最后一次提交的拼写错误:
# 原提交信息打错了
git commit -m "fix: add user validaton" # validation 拼错了
# 修复
git commit --amend -m "fix: add user validation"
- 遗漏了文件或修改:
# 已经提交
git commit -m "feat: add login page"
# 发现忘了提交某个文件
git add forgotten_file.js
git commit --amend --no-edit
- 合并琐碎修改:
# 已经提交
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?
- Add the File to .gitignore
比如说需要忽略所有的 .bak
文件
.bak
- 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
,可能会被错误地解析为两个独立的文件my
和file.bak
- Commit the Changes
git commit -m "Stop tracking .bak files"
What are the best practices for naming git branches?
- 使用前缀结构
使用前缀来表示分支的类型和用途,这样可以更直观地了解分支的目的。常见的前缀包括:
feature/
:用于新功能开发。例如,feature/user-authenticationbugfix/
或fix/
:用于修复 bug。例如,bugfix/fix-login-issuehotfix/
:用于紧急修复线上问题。例如,hotfix/urgent-payment-fixrelease/
:用于发布准备和版本管理。例如,release/v1.0.0test/
:用于测试相关的代码或功能。例如,test/api-performance
- 使用简洁但具描述性的名字
分支名称应该简短但能准确描述该分支的目的。避免使用太笼统的名称,比如 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