知识广场
按学科筛选:计算机科学 / 软件工程 / 工具与效率
«计算机科学 / 软件工程 / 工具与效率» 分类下共 13 篇帖子
## 起因 shell 历史的痛点: - 几台机器(笔记本 / 台式 / 远程 server)历史不互通 - Ctrl-R 反搜慢 + 一次显一条 - 历史没 metadata(哪个目录跑的?exit code?耗时?) - 关闭终端 history 偶尔丢 `atuin` 是 Rust 写的 shell history 替代: - 历史存 SQLite,带元数据(cwd、exit、duration、host、session) - E2E 加密同步到 server(自己 host 或官方) - Ctrl-R 替换成全屏 fuzzy search UI ## 装 ```bash brew install atuin curl --proto '=https' --tlsv1.2 -LsSf https://setup.atuin.sh | sh # shell 集成 atuin init zsh >> ~/.zshrc atuin init bash >> ~/.bashrc atuin init fish | source ``` import 现有历史: ```bash atuin import auto # 检测 shell 自动 import ``` ## 第一次用 Ctrl-R 启动 atuin 全屏 search: ``` atuin search ~/proj/myapp ──────────────────────────────────────── > docker 10s /home/u/proj docker compose up -d ✓ 2m /home/u/other-proj docker logs api -f ✓ 1h /home/u/proj docker compose ps ✓ yesterday /home/u/proj docker compose build ✓ ──────────────────────────────────────────────────────────────────── filter <ctrl-r> | invert <ctrl-a> | exit <esc> ``` - 实时 fuzzy 过滤 - 显示运行时间 / cwd / 是否成功(✓/✗) - enter 执行 / tab 编辑 `Ctrl-R` 一秒查 10 年前在哪台机器跑过啥命令。 ## sync 同步 注册账号: ```bash atuin register -u me -e [email protected] atuin login -u me # import 上来的 + 之后的命令都加密同步到 cloud ``` E2E 加密:服务端只存 ciphertext,端上解密。 默认用 atuin.sh 官方服务(免费);介意 → 自部署: ```bash docker run -d -p 8888:8888 ghcr.io/atuinsh/atuin server start ``` `~/.config/atuin/config.toml`: ```toml sync_address = "https://your-server.com" ``` ## 跨机器登录 新机器: ```bash brew install atuin atuin login -u me -p <password> -k <encryption-key> atuin sync ``` `-k` 是首次注册时 atuin 显示的加密 key,**自己存好**(atuin server 无法恢复 key)。 `atuin sync` 把云端历史拉下来 + 解密 → 立刻有所有机器的命令。 ## 配置过滤 不想同步某些命令: ```toml # ~/.config/atuin/config.toml # 不记录 secret 命令 history_filter = [ "^secret-cmd", "^aws.*--secret", ] # 不在公共 wifi 同步 sync_address = "..." auto_sync = true sync_frequency = "5m" ``` ## stats ```bash $ atuin stats Top commands: 1. git status (1234) 2. ls (987) 3. cd .. (876) ... Total commands: 45678 First command: 2022-03-14 ``` 或者: ```bash $ atuin stats day $ atuin stats week $ atuin stats --since '1 month ago' ``` 看一个月你跑啥最多。会启发优化 alias / 工作流。 ## 各机器 history 分别看 ```bash atuin search --cwd . # 当前目录历史 atuin search --hostname laptop # 在 laptop 跑的 atuin search --exit 0 # 只成功的 atuin search 'docker' --before '1 week ago' ``` ## 内置 vs atuin | | shell 内置 history | atuin | |---|---|---| | 存储 | ~/.zsh_history (text) | SQLite | | 同步 | 无 | 跨机器加密 | | 元数据 | 无 | cwd / exit / duration | | 搜索 | 反搜(单行) | TUI fuzzy | | 容量 | 几千-几万行 | 无限 | | 性能 | 文件 grep | 索引 query | 不会用 atuin 后就回不去了。 ## 性能 `atuin search` 上百万 history entry sub-50ms 响应。SQLite 索引 + Rust 解析快。 启动开销:shell 集成 ~5-10ms(vs no atuin ~0ms)。基本无感。 ## 替代品 - **mcfly**(rust):类似 atuin,没 sync - **hstr**:传统 TUI,无元数据 - **fzf + fc**:fzf 模糊搜历史(无元数据) atuin 是目前最完整方案。 ## 退出策略 万一不喜欢: ```bash # 取消 shell 集成(从 .zshrc 删 atuin init) # 导出回 plain history atuin export csv > history.csv # 删 SQLite rm -rf ~/.local/share/atuin ``` history 不会"绑定"住你。 ## 隐私 / 安全 - 数据库本地:`~/.local/share/atuin/history.db`,未加密 → 笔记本被偷 对方能读历史 - 云端:E2E 加密,server 看不到内容 - 命令含 secret:`history_filter` 排除 担心本地泄密 → FileVault / LUKS 全盘加密 + atuin filter sensitive 命令。 ## 一个 case:debug 老问题 3 个月后客户报"上次配的某个 nginx 设置不工作了"。 我不记得当时改了啥。 ```bash $ atuin search --since '4 months ago' --before '2 months ago' nginx ``` 10 个命令列出来:当时 edit 啥配置、reload、看 log。 2 分钟还原情境。比"翻 commit / 看 wiki"快多了。 shell history 是被低估的 personal knowledge base。 ## 踩过的坑 1. **首次同步慢**:百万 entry 上传几分钟。耐心等。 2. **重复 history**:bash + zsh 都用 atuin → 同命令记两次。每 shell 只用一个。 3. **导入 fish 历史 format 不对**:fish 用 yaml 历史,老版本 atuin import 解析坑。新版本修了。 4. **`Up` 键被吃**:atuin 默认绑 `Up` 显示历史 → 跟 vi mode 冲突。 `disable_up_arrow = true` 关。 5. **加密 key 丢了**:服务端有数据但解密不了。**`atuin register` 时 立刻把 key 存密码管理器**。
## 起因 想知道哪个命令更快: - `find . -name '*.py'` vs `fd -e py` - `cat file | grep foo` vs `rg foo file` - 两个不同 build 工具 老办法 `time cmd`:跑一次结果有噪声,不科学。 `hyperfine`(Rust)专门 benchmark:自动多次跑 + warmup + 统计 + 对比 + 优雅输出。 ## 装 ```bash brew install hyperfine cargo install hyperfine ``` ## 基本 ```bash $ hyperfine 'find . -name "*.py"' Benchmark 1: find . -name "*.py" Time (mean ± σ): 345.2 ms ± 12.3 ms [User: 90.1 ms, System: 245.6 ms] Range (min … max): 330.1 ms … 365.8 ms 10 runs ``` 自动跑 10 次 + mean / σ / range。 ## 对比两命令 ```bash $ hyperfine 'find . -name "*.py"' 'fd -e py' Benchmark 1: find . -name "*.py" Time (mean ± σ): 345.2 ms ± 12.3 ms Benchmark 2: fd -e py Time (mean ± σ): 62.1 ms ± 3.4 ms Summary 'fd -e py' ran 5.56 ± 0.36 times faster than 'find . -name "*.py"' ``` 直接看 fd 比 find 5.5x 快。 ## warmup 第一次跑可能 cold cache: ```bash hyperfine --warmup 3 'cmd' ``` 跑 3 次 warmup(不计时)+ 后面 10 次计时。 避免 disk cache miss 干扰。 ## prepare / cleanup ```bash hyperfine --prepare 'sync; echo 3 > /proc/sys/vm/drop_caches' 'cmd' ``` 每次 run 前 prepare(drop cache 测 cold path)。 或者每次后 cleanup(删 output file 之类)。 ```bash hyperfine --cleanup 'rm output.txt' 'process input > output.txt' ``` ## 参数 sweep ```bash hyperfine --parameter-list n 100,1000,10000 'sleep 0.{n}' ``` 跑 sleep 0.100 / 0.1000 / 0.10000 各 10 次 → 输出 sweep 表格。 例:测不同 thread 数: ```bash hyperfine --parameter-scan threads 1 8 'make -j{threads}' ``` 1-8 thread 各跑 make → 看 sweet spot。 ## export 数据 ```bash hyperfine 'cmd1' 'cmd2' --export-markdown result.md hyperfine 'cmd1' 'cmd2' --export-json result.json hyperfine 'cmd1' 'cmd2' --export-csv result.csv ``` Markdown 直接贴 PR 比较 before/after。 ## 真实 case 1:CI 测试加速 CI 跑 pytest 改 plugin: ```bash hyperfine --warmup 1 \ 'pytest -p no:cacheprovider' \ 'pytest' \ 'pytest -n 4' \ 'pytest -n auto' ``` | | mean | |---|---| | pytest (no cache) | 45s | | pytest (cache) | 38s | | pytest -n 4 | 14s | | pytest -n auto (8 cores) | 9s | 加 -n auto = 5x 加速 → CI yaml 一行改动收益巨大。 ## 真实 case 2:build 工具对比 ```bash hyperfine --warmup 2 --prepare 'rm -rf node_modules dist' \ 'npm install && npm run build' \ 'pnpm install && pnpm build' \ 'bun install && bun run build' ``` 得出"对此项目 pnpm 比 npm 快 2x"。 凭印象不如实测。 ## ignore failure ```bash hyperfine --ignore-failure 'might_fail_cmd' ``` 某些工具偶尔 fail 但你想 measure 成功的部分。 ## 数据可视化 ```bash hyperfine 'cmd1' 'cmd2' --export-json result.json python -m hyperfine.plot.histogram result.json ``` 直方图看 distribution(不只是 mean)。 发现"通常 1s 偶尔 10s" 的长尾。 ## warmup 不够准 ```bash hyperfine --runs 100 'cmd' ``` 跑 100 次 → CLT 收敛 → 更准 mean / σ。 缺点:慢命令做不到。 ## 与 perf / criterion 对比 - `perf stat cmd`:Linux 性能 counter(cache miss / branch misses 等),更深 - `criterion`(Rust lib):微基准,函数级别 - `hyperfine`:命令级别比较 hyperfine 是"对比工具 / shell 命令"的瑞士军刀。 深入 profile 用 perf。 ## 与 ab / wrk 对比 `ab` / `wrk` 测 HTTP server(并发 + RPS)。 hyperfine 测**单 invocation 时间**。 ```bash # 测 server 响应(应该用 wrk) wrk -t 4 -c 100 -d 30s http://localhost:8000/ # 测命令耗时 hyperfine 'curl http://localhost:8000/' ``` 不同用途。 ## 我的常用 alias ```bash alias bench='hyperfine --warmup 2' alias bench-cold='hyperfine --warmup 0 --prepare "sync; echo 3 | sudo tee /proc/sys/vm/drop_caches"' ``` 测优化前后效果: ```bash git stash # 老代码 bench 'cmd' git stash pop # 新代码 bench 'cmd' ``` 或者一次同时跑: ```bash git stash hyperfine 'cmd' --export-json before.json git stash pop hyperfine 'cmd' --export-json after.json hyperfine-compare before.json after.json ``` ## 踩过的坑 1. **stdout 输出大**:命令 print 几 MB → buffer 影响 measurement。 `> /dev/null` redirect。 2. **shell startup overhead**:测 sub-ms 命令 → shell fork 本身就 几 ms。极短命令 hyperfine 不准。 3. **multi-core 干扰**:测时其它进程跑 → 数字飘。`taskset 0x1 hyperfine ...` 绑核 isolation。 4. **测试 build 重复 work**:第二次 build incremental → 比第一次快。 `--prepare 'rm -rf build/'` 公平。 5. **system noise**:disk encryption / antivirus / Time Machine 等 背景影响。多次跑 + 看 σ。
## 起因 git 命令多 + 难记。常见操作经常要 4-5 个步骤: - 看 diff → stage 部分 → unstage 错的 → commit → push - 找某个 commit 看做了啥 → cherry-pick → push - rebase 时改某个 commit message - 一个文件 history 看每行 blame 老办法:`git status` + `git diff` + `git add -p`(交互 hunk)+ `git commit` ... 一连串。 GUI 方案(GitKraken / Tower / Sourcetree)功能强但要离开终端 + 收费 + 启动慢。 `lazygit` 是终端 TUI,5 秒启动 + 键盘操作 + 覆盖 90% git 工作流。 ## 装 ```bash brew install lazygit # 或 apt install lazygit # ubuntu 22.04+ ppa ``` ## 启动 仓库目录里: ```bash lazygit ``` 或者 alias `lg='lazygit'`。 UI 5 个 panel: ``` ┌─Status─────┬─Files──────────────────────────────────┐ │ master │ M app/views.py │ │ │ ?? new_file.md │ ├─Branches───┤ │ │ master ├─Diff/Output──────────────────────────┤ │ feature │ + new line │ │ │ - old line │ ├─Commits────┤ │ │ abc Update │ │ │ def Fix │ │ └────────────┴──────────────────────────────────────┘ ``` `Tab` 切 panel,`?` 看快捷键。 ## 常用操作 ### stage / commit / push ``` 1. j/k 在 Files panel 选文件 2. space 切换 stage / unstage 3. enter 进 hunk view,space stage 单个 hunk 4. c 写 commit message 5. P push ``` vs `git add -p` 交互的痛苦:lazygit 可视化 + 按键即操作。 ### 看 diff 选文件 → 右边 panel 自动显示 diff。 高亮 / 上下滚(PgUp/PgDn) / 同步两窗口(vimdiff like 用 i 触发)。 ### 切换分支 Branches panel: ``` 1. Tab 到 Branches 2. j/k 选分支 3. space checkout 4. n 新建分支 5. d 删除 ``` ### rebase / cherry-pick Commits panel: ``` 1. Tab 到 Commits 2. r 选中 commit 改 message 3. s squash 进上一个 commit 4. f fixup(squash + 用上一个 message) 5. e edit commit(修改文件后 continue) 6. d drop commit ``` interactive rebase 不用记 `git rebase -i HEAD~5` + vim 编辑 todo list。 lazygit 里上下选 + 按键。 ### stash 操作 ``` S # stash all g # 弹 stash 列表 space # pop ``` ### log + blame ``` y # show log graph b # blame current file ``` ### remote 操作 ``` P # push(可选 force / 不同 remote) p # pull f # fetch ``` ## 配置 `~/.config/lazygit/config.yml`: ```yaml gui: theme: lightTheme: false showCommandLog: false nerdFontsVersion: "3" # 用 NerdFont 图标 git: autoFetch: false # 默认每分钟 fetch,关掉省网络 paging: colorArg: always pager: delta --paging=never # 用 delta 渲染 diff keybinding: files: commitChanges: "c" customCommands: - key: "C" description: "Commit conventional" command: "git commit -m '{{.Form.Type}}({{.Form.Scope}}): {{.Form.Msg}}'" context: "files" prompts: - type: "menu" title: "Type" options: - { name: "feat", value: "feat" } - { name: "fix", value: "fix" } - { name: "chore", value: "chore" } - type: "input" title: "Scope" key: "Scope" - type: "input" title: "Message" key: "Msg" ``` customCommands 强大:自定义快捷工作流。`Shift-C` 启动 conventional commit prompt。 ## 与 git 命令对比 | 操作 | git 命令 | lazygit | |---|---|---| | stage 部分 | `git add -p` + y/n | space space space | | commit | `git commit -m "..."` | c → 输入 | | 切分支 | `git checkout xxx` | Tab → 选 → space | | rebase 改 message | `git rebase -i HEAD~3` + edit | r | | stash pop | `git stash pop` | g → space | | 看历史 | `git log --oneline` | 主屏右下自动 | 熟练后 lazygit **快 2-3x**。新人上手时间从 git CLI 几周 → lazygit 几小时。 ## 与 GitUI / tig / gitk 对比 - **GitUI**:Rust 写,比 lazygit 启动还快,但功能略少(无 customCommands) - **tig**:老牌,只读 history 浏览强,操作交互弱 - **gitk**:Tcl/Tk GUI,老土但 history graph 经典 我用 lazygit 做日常 + tig 看复杂 history graph。 ## 与 IDE 内置 git 工具对比 VS Code / JetBrains 自带 git GUI 也好用。 lazygit 优势: - 在终端里(vim / tmux 用户友好) - 跨编辑器一致体验 - 远程 ssh 服务器也能用(GUI 没法用) 我同时用:IDE 看 diff / cherry-pick UI;lazygit 做 rebase / stage。 ## tmux popup 启动 `~/.tmux.conf`: ``` bind g display-popup -E -w 90% -h 90% lazygit ``` 任意窗口 `prefix+g` 弹 lazygit popup → 操作完关掉。完美。 ## 踩过的坑 1. **大仓库慢**:几十万 commit 的 repo(如 chromium)lazygit 启动几秒 load。`logCmd` config 可以限制。 2. **submodule 不直观**:lazygit 对 submodule 支持有限。 submodule 切换要 enter 进 sub view。 3. **conflict 解决**:merge conflict 时 lazygit 提示要手动编辑文件再回来。 有简单的 file marker view 但复杂 conflict 还是开编辑器。 4. **commit 模板没用上**:git `commit.template` 配的模板 lazygit commit 命令不自动用。要在 lazygit config 里另外配 `commitPrefix`。 5. **远程 / fetch 不显**:拉新分支后 lazygit 没自动刷新。R refresh 一下。
## 起因 每个项目都有一堆"开发命令": ```bash pnpm dev docker compose up -d uv run pytest ruff check . && mypy . docker compose exec api python manage.py shell ``` 放哪? - `package.json` scripts:JS 项目 OK,但 Python / Rust 项目不自然 - `Makefile`:tab vs space / 老语法 / shell 转义恶心 / 不跨平台 - README 里 copy-paste:散 - shell alias:项目特定的不该污染全局 `just` 是 Rust 写的命令记事本 / task runner。`justfile` 取代 Makefile, 专门为"跑命令" 设计(不为编译 build artifact)。 ## 装 ```bash brew install just # macOS cargo install just # 通用 sudo apt install just # ubuntu 22.04+ ``` ## 基础 justfile ```just # justfile(项目根) # 默认 task:列出所有 default: @just --list # 装依赖 install: uv sync pnpm install # 启动 dev dev: docker compose up -d db redis uv run python manage.py runserver # 跑测试 test: uv run pytest -x # 检查代码质量 lint: ruff check . mypy . pnpm tsc --noEmit ``` 跑: ```bash $ just # 默认 → just --list $ just install $ just test $ just lint ``` ## vs Makefile ```make # Makefile 等价 .PHONY: install test install: uv sync pnpm install test: uv run pytest -x ``` - Makefile 要 **tab** 缩进(编辑器配错就崩) - Makefile 假设是"build artifact"(filename dependency),shell 当 side effect - `$(VAR)` `$$VAR` 转义恶心 - 跨平台不一致(GNU make vs BSD make) `just` 直接 shell 命令 / 空格缩进 / 跨平台一致 / 默认无 dependency 跟踪(这是 feature,不是 bug,跑命令不需要)。 ## 参数 ```just # 带参数 greet name='world': echo "hello {{name}}" # 多 arg deploy env target: ./deploy.sh {{env}} {{target}} ``` ```bash $ just greet hello world $ just greet Alice hello Alice $ just deploy prod web ``` 参数变量用 `{{name}}` 引用(Mustache 风格)。 ## recipe 调 recipe ```just build: pnpm build deploy: build ./deploy.sh # 或者显式 release: just lint just test just build ./release.sh ``` ## 用 python / node 写 recipe ```just # bash(默认) hello: echo "hello" # python analyze: #!/usr/bin/env python3 import json with open('data.json') as f: data = json.load(f) print(f"items: {len(data)}") # node gen: #!/usr/bin/env node console.log('hi from node'); ``` `#!` 第一行指定 shebang → 整个 recipe 当一个 script 跑(不是逐行)。 ## 环境变量 ```just # justfile 顶部 set set dotenv-load # 自动加载 .env # 默认变量 NAME := "myapp" PORT := env_var_or_default("PORT", "8000") run: PORT={{PORT}} ./{{NAME}} # Linux specific [linux] clean: rm -rf build/ [macos] clean: rm -rf build/ .DS_Store ``` `[linux]` `[macos]` recipe attribute 让同 recipe 在不同平台用不同命令。 ## 列出 + help ```bash $ just --list Available recipes: default # 列出所有 install # 装依赖 test # 跑测试 # recipe 上面的注释自动当 docstring ``` 每个 recipe 顶部加注释 → 自动是描述。新人 clone 项目 `just` 一下立刻 知道有什么任务。 ## 我的常用模板 ```just # justfile set dotenv-load set positional-arguments PYTHON := "uv run" default: @just --list # 初始化(一键 onboarding) init: uv sync pnpm install docker compose up -d db {{PYTHON}} python manage.py migrate {{PYTHON}} python manage.py createsuperuser --noinput || true # 开发 dev: docker compose up -d db redis {{PYTHON}} python manage.py runserver 0.0.0.0:8000 # 测试 + 覆盖率 test *args='': {{PYTHON}} pytest -x --cov=. {{args}} # 代码检查(CI 同样跑) lint: {{PYTHON}} ruff check . {{PYTHON}} ruff format --check . {{PYTHON}} mypy . # 自动 fix fix: {{PYTHON}} ruff check . --fix {{PYTHON}} ruff format . # DB migrate / shell / etc migrate: {{PYTHON}} python manage.py migrate shell: {{PYTHON}} python manage.py shell makemigrations *args='': {{PYTHON}} python manage.py makemigrations {{args}} # 部署相关 deploy env: @echo "deploying to {{env}}..." ./scripts/deploy.sh {{env}} ``` `*args=''` 收尾通过位置传给 recipe(如 `just test forum/tests.py`)。 ## 与 npm scripts 对比 `package.json`: ```json { "scripts": { "dev": "next dev", "build": "next build", "test": "vitest" } } ``` - JSON 单行字符串 → 复杂命令难看 - 跨平台 (Windows) 转义不一致 - 只能 cd 到 package.json 所在目录跑 `just` 更适合多 layer / 多语言项目。npm script 适合纯 JS 单项目。 很多项目我两个都用:`package.json` 给 JS 的 thin wrapper,`justfile` 给跨语言 orchestration(justfile 调 npm script)。 ## CI 集成 GitHub Actions: ```yaml - uses: extractions/setup-just@v2 - run: just lint - run: just test ``` CI = 本地的 `just`,绝对一致。 ## 与 mage / task / make 对比 | | just | make | task (taskfile.dev) | mage | |---|---|---|---|---| | 语言 | rust | C | go | go | | 配置 | justfile | Makefile | Taskfile.yml | magefile.go | | 跨平台 | ✅ | 弱 | ✅ | ✅ | | 学习曲线 | 低 | 中 | 低 | 中 | | 适合 | 通用任务跑 | C/C++ build | 现代替代 make | go 项目 | just 是"现代 Make 替代品 for 命令"。如果你在做实际编译依赖跟踪(如 C/C++ build),用 make 或 cmake。 ## 踩过的坑 1. **PATH 不一致**:CI 跑 just → recipe 里 `npx` 找不到。`set shell := ["bash", "-cu"]` + ensure tool 装好。 2. **dotenv 自动加载敏感变量到子进程**:`set dotenv-load` 让 `.env` 变量进所有 recipe shell。生产 secrets 不要放项目根 `.env`,分开。 3. **recipe shell 默认 sh 不是 bash**:`pipefail` 等 bash-only。 `set shell := ["bash", "-cu"]` 改。 4. **变量插值与 shell 冲突**:`{{VAR}}` 是 just 模板,shell 里用 `${VAR}` 是 shell 变量。混着用要小心。 5. **递归 just 调 just**:可以但易嵌套深 → 跑得乱。一般保持扁平结构。
## 起因 git diff 经典痛点: ```diff - function process(items) { + async function process(items, options = {}) { ``` 实际只加了 `async` 和 `options` 参数,但 diff 显示整行替换。 review 慢 + 看不出真正变化。 更糟:reformatter(prettier / black)改空白 → diff 一片噪音,藏住 真正逻辑改动。 **difftastic** (`difft`) 用 tree-sitter 解析语法 → 按 AST 节点 diff → 真正的"结构化" diff。 ## 装 ```bash brew install difftastic cargo install difftastic ``` ## 单 file diff ```bash $ difft old.js new.js ``` 输出 side-by-side,**按 AST 节点高亮变化**: ``` old.js new.js function process(items) { async function process(items, options = {}) { ^^^^ ^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^ (unchanged) new new ... ``` 只高亮变化的 token,不变的部分原样。 看一眼就知道 "加了 async" + "加了 options 参数"。 ## git 集成 `~/.gitconfig`: ```ini [diff] external = difft [pager] difftool = false [difftool] prompt = false [difftool "difftastic"] cmd = difft "$LOCAL" "$REMOTE" ``` 或者只在需要时用: ```bash GIT_EXTERNAL_DIFF=difft git diff GIT_EXTERNAL_DIFF=difft git log -p ``` alias 一下: ```bash alias gdt='GIT_EXTERNAL_DIFF=difft git diff' alias glogt='GIT_EXTERNAL_DIFF=difft git log -p' ``` ## 跨语言 difftastic 用 tree-sitter,支持几十种语言: - Python / JS / TS / Rust / Go / Java / C / C++ - HTML / CSS / Markdown - JSON / YAML / TOML - SQL / GraphQL 每语言知道 "function" / "class" / "import" 等结构。 ## 实际效果 reformatter run 改空白: ```diff # 普通 git diff - def foo(x): - return x + 1 + def foo(x): + return x + 1 # difftastic [no changes detected] ``` 啥都不改的格式化 → difftastic 不显,git diff 显一堆假动。 ## 重命名变量 ```python - def process(data): - result = data * 2 - return result + def process(items): + output = items * 2 + return output ``` 普通 git diff 看不出"几个变量重命名",看到"3 行换 3 行"。 difftastic 高亮:data→items, result→output。 理解快。 ## 大改动也清晰 ```js // before const x = a + b * c; // after const x = (a + b) * c; ``` 普通 diff: `- ... + ...` 整行。 difftastic 高亮加的括号 → 一眼看出。 ## 限制 - **性能**:大 file(>1MB)解析慢。git log -p 多 commit 时累。 - **只 syntax**:不是 semantic diff(不知 var rename 是同 var)。 - **某些语言 parse 错**:tree-sitter parser 偶有 bug。 不 fit 时降级 `git --no-external-diff diff`。 ## 与 delta 对比 `delta`(dandavison/delta)是另一 diff 工具: - 漂亮 syntax highlighting(仍是行级 diff) - side-by-side / line numbers - 不是真 AST diff,但视觉好 | | delta | difftastic | |---|---|---| | 方式 | 行级 + highlighting | AST 结构 | | 速度 | 快 | 中 | | 噪音 (whitespace) | 仍显示 | 跳过 | | 重命名 | 不识别 | 识别 | 我 daily git diff 用 delta(速度 + 通用),复杂 PR review 用 difftastic。 ## 配 GitHub Actions PR review ```yaml - run: | GIT_EXTERNAL_DIFF=difft git diff origin/main...HEAD > review.txt - uses: actions/upload-artifact@v4 with: name: structural-diff path: review.txt ``` CI 生成结构化 diff,review 时下载看。 ## 配 fzf ```bash # 选 commit 看 difft git log --oneline | fzf --preview 'GIT_EXTERNAL_DIFF=difft git show {1}' ``` ## 与 IDE diff 对比 VS Code / JetBrains 内置 diff 视图也强。 - 行级 + 块对比 - inline editing - 但不识别 AST 结构 difftastic 在 terminal / PR review 流程中 fill gap。 ## 真实 case 某 PR refactor 1000 行: - 普通 diff 显 800 行变化(多数是 whitespace + import 顺序) - difftastic 显 50 行真实结构变化 review 时间从 1 小时 → 15 分钟。 看到真正改的逻辑而不是噪声。 ## ast-grep 替代某些场景 semantic refactor 看 `ast-grep` (sg): ```bash sg --pattern 'console.log($A)' --rewrite 'logger.info($A)' ``` 按 AST pattern 替换。 不是 diff 工具但相关:semantic understanding of code。 ## 踩过的坑 1. **git log -p 慢**:每 commit difft → 100 commit 几分钟。 只在需要时用,不要全局 default。 2. **alias 没生效**:`GIT_EXTERNAL_DIFF=difft` 必须 env var, alias `git diff` 不带 env 不行。用 `git -c diff.external=difft diff`。 3. **某语言不识别**:esoteric 文件 difft 不解析 → 行级 fallback。 仍可用,但失去 AST 优势。 4. **side-by-side 太宽**:窄终端被裁。`--display inline` 用 inline 模式。 5. **PR tool 集成弱**:GitHub PR review UI 不能用 difftastic(GitHub 是 server-side)。本地拉 PR 后 difftastic 看。
## 起因 shell prompt 需要显示: - 当前 git 分支 / 状态 - 当前 Python venv / Node 版本 - 上一命令 exit code - 是不是 ssh - 当前 k8s context 老办法: - bash 写 `PS1='...'` 一长串 escape - zsh 装 oh-my-zsh + theme - 每个 shell(bash / zsh / fish / nu)都要重新配 `starship`(Rust)是**跨 shell 统一 prompt engine**。一份 TOML 配置 所有 shell 受用 + 快 + 美观。 ## 装 ```bash brew install starship curl -sS https://starship.rs/install.sh | sh # 集成 eval "$(starship init bash)" # bash eval "$(starship init zsh)" # zsh starship init fish | source # fish # 加到 rc file echo 'eval "$(starship init zsh)"' >> ~/.zshrc ``` ## 默认效果 ``` ~/projects/myapp on feature/auth [✱] ❯ ``` 显示: - 路径 - git 分支 + 修改标记 - 自动检测项目类型(如 Node project 显示 node 版本,Rust 显示 cargo 版本) ## 自定义 ~/.config/starship.toml ```toml # 整体格式 format = """ $directory\ $git_branch$git_status\ $python$nodejs$rust\ $kubernetes\ $line_break\ $character""" # 单组件配置 [directory] truncation_length = 3 truncate_to_repo = true style = "bold cyan" [git_branch] symbol = " " style = "bold green" [git_status] modified = "*" staged = "+" untracked = "?" ahead = "⇡${count}" behind = "⇣${count}" diverged = "⇕" [python] symbol = " " format = '[$symbol$pyenv_prefix($version )(\($virtualenv\) )]($style)' [nodejs] symbol = " " disabled = false [kubernetes] disabled = false format = '[$symbol$context( \($namespace\))]($style) ' [character] success_symbol = "[❯](green)" error_symbol = "[❯](red)" ``` ## 性能 starship 测量自己执行时间: ```bash starship time ``` 100-200ms 慢的话,prompt 觉得卡。常见原因: - git status 在大 repo 慢 - python module check 慢 优化: ```toml [git_status] disabled = false ignore_submodules = true # 大 submodule repo 跳过 [python] disabled = true # 不显 python 版本 ``` 或者用 `command_timeout = 500`(毫秒)限制慢操作。 ## 项目类型自动检测 starship 在每次按回车跑一次,判断当前目录类型: - `package.json` → 显 node 版本 - `pyproject.toml` / `requirements.txt` → 显 python - `Cargo.toml` → rust - `go.mod` → go - `Dockerfile` → docker - `Gemfile` → ruby - ... 不在项目里时不显(不噪音)。 ## 多线设计 ```toml format = """ [╭─](bold yellow) $username@$hostname $directory\ $git_branch$git_status$git_state$git_metrics [╰─](bold yellow)$character""" ``` ``` ╭─ alice@laptop ~/proj/myapp on main ╰─❯ ``` 第二行只放 prompt 字符 → 命令永远在固定位置,长 prompt 不挤右侧。 ## right prompt ```toml right_format = "$cmd_duration $time" [cmd_duration] min_time = 500 # > 500ms 才显 format = "took [$duration](bold yellow) " [time] disabled = false format = '[$time]($style) ' time_format = "%R" ``` ``` ~/proj/myapp on main took 2.3s 15:42 ❯ ``` 长命令耗时 + 时间右侧显示。 ## 通过 ssh 改色 ```toml [hostname] ssh_only = true format = "[$hostname](bold red) " ``` 只在 ssh 里显主机名,本地不显。提醒"我在远程"。 ## kubernetes context ```toml [kubernetes] disabled = false format = '⛵ [$context(\($namespace\))]($style) ' style = "purple" [kubernetes.context_aliases] "gke_my-project-prod_us-central1_cluster" = "prod" "gke_my-project-staging_us-central1_cluster" = "stg" ``` ``` ⛵ prod(default) ~/k8s on main ❯ ``` 不同 context 染色 → 误操作 prod 之前肉眼能注意到。 ## env_var ```toml [env_var.AWS_PROFILE] format = "AWS:[$env_value](bold blue) " default = "" ``` 显当前 AWS profile(避免 prod 误操作)。 ## 与 zsh theme 对比 | | starship | powerlevel10k | oh-my-zsh themes | |---|---|---|---| | 跨 shell | ✅ | ❌(zsh only) | ❌ | | 速度 | 快(rust) | 极快(缓存) | 慢 | | 配置 | TOML | zsh wizard | shell script | | 可读性 | 高 | 中 | 中 | | 新人友好 | 高 | 中 | 中 | p10k 在 zsh 里最快(用 instant prompt)。 starship 跨 shell + 简单配置。我用 starship。 ## fish 友好 fish 用户最爱 starship。`fish_prompt` 自定义很麻烦,starship 一行接管。 ## nushell nushell 也支持: ```nu # ~/.config/nushell/config.nu $env.PROMPT_COMMAND = { || starship prompt } ``` ## 我的精简配置 ```toml # ~/.config/starship.toml format = """ $directory$git_branch$git_status$python$nodejs$kubernetes $character""" right_format = "$cmd_duration" [directory] truncation_length = 4 truncate_to_repo = true [git_branch] symbol = " " format = "on [$symbol$branch]($style) " [git_status] format = '([\[$all_status$ahead_behind\]]($style)) ' [python] symbol = "py " format = '[$symbol(\($virtualenv\) )]($style)' [nodejs] symbol = "node " detect_files = ["package.json"] [kubernetes] disabled = false symbol = "⎈ " format = '[$symbol$context]($style) ' [cmd_duration] min_time = 1000 format = "[$duration]($style) " [character] success_symbol = "[❯](green)" error_symbol = "[❯](red)" vimcmd_symbol = "[❮](green)" ``` 效果: ``` ~/proj/myapp on main [*+] ⎈ prod ❯ ``` 干净 + 信息量足。 ## 踩过的坑 1. **NerdFont 字符不显**:图标方块乱码。换支持 NerdFont 的字体 (JetBrainsMono Nerd / Hack Nerd / Iosevka)。 2. **大 monorepo git status 慢**:每次回车 100-500ms。 `disabled = true` 或限 `ignore_submodules = true`。 3. **conda env 没显**:默认 starship 不显 conda。`[conda] disabled = false`。 4. **远程 ssh prompt 不换色**:忘配 `[hostname] ssh_only = true`。 5. **shell init 慢**:`time zsh -i -c exit`。starship init 一般 < 10ms, 慢的话 starship 版本太老 / 配置 toml 错。
## 起因 `cat` / `ls` / `sed` / `du` / `ps` / `df` 用了几十年。够用,但: - 默认输出朴素,看不出结构 - 性能没跟上多核 CPU - 选项 / 标志难记 Rust / Go 写的现代替代品,**默认就好用**: | 老 | 新 | 干啥 | |---|---|---| | cat | bat | 查看文件 + 高亮 | | ls | eza (前 exa) | 列目录 + 图标 + git status | | sed | sd | 简单替换 | | du | dust | 磁盘占用 visualization | | df | duf | 磁盘空间分区 | | ps | procs | 进程列表 | | find | fd | 文件搜索 | | grep | rg | 文本搜索 | | top | btop | 系统监控 | | tldr | tealdeer | man 替代(例子) | 下面挨个看。 ## bat (cat 替代) ```bash brew install bat ``` ```bash $ bat README.md ``` - 语法高亮(300+ 语言) - 显示行号 - 长文件自动 pager(按 q 退) - git diff 标记(左侧 +/-) ```bash bat -p file.json # 不要 pager / 行号 bat -A file.txt # 显示空白字符(debug 缩进) bat --diff # diff 高亮 ``` alias: ```bash alias cat='bat -p' # 当 cat 用 alias less='bat' # 当 pager 用 ``` vim 类似 less 但更好。 ## eza (ls 替代) ```bash brew install eza ``` ```bash $ eza -l --icons --git .rw-r--r-- u 1.2K Mar 14 10:23 README.md .rw-r--r-- u 234 Mar 14 10:25 package.json M drwxr-xr-x u - Mar 14 10:25 src -- 1 ``` - 颜色按文件类型 - NerdFont 图标 - git status 集成 - tree 模式:`eza --tree` 我的 alias: ```bash alias ls='eza --group-directories-first' alias ll='eza -l --icons --git' alias lt='eza --tree -L 2' alias la='eza -la --icons' ``` ## sd (sed 替代) ```bash brew install sd cargo install sd ``` 简单替换,语法直觉: ```bash # sed sed -i 's/foo/bar/g' file.txt # sd sd 'foo' 'bar' file.txt ``` - 默认 PCRE regex(不像 sed POSIX) - 不用 `\(\)`,用 `()` - 不用 escape `/` - 默认 in-place ```bash sd 'class (\w+)' 'struct $1' src/*.rs # 替换 + capture group sd -p 'foo' 'bar' file.txt # preview 不写 ``` sed 仍胜的场景:复杂 stream editor(s/// + d + p 命令组合)。 90% 简单替换 → sd。 ## dust (du 替代) ```bash brew install dust ``` ```bash $ dust 9.0G ┌── node_modules 14.0G ┌─┴ project_a 5.0G ┌─┴ projects 24.0G ┴ /home ``` ASCII 树 + 大小条 + 自动按大小排序。比 `du -sh * | sort -h` 直接。 ```bash dust -d 3 # 深度 3 dust -n 30 # 显前 30 大 dust -X .git # 排除 ``` ## duf (df 替代) ```bash brew install duf ``` ``` ╭────────────────────────────────────────────────────────────────╮ │ 3 local devices │ ├──────────────┬──────────┬────────┬─────────┬───────┬───────────┤ │ MOUNTED ON │ SIZE │ USED │ AVAIL │ USE% │ FILESYS │ ├──────────────┼──────────┼────────┼─────────┼───────┼───────────┤ │ / │ 466.0 GB │ 200 GB │ 246 GB │ [████░░░░░░] 45% │ │ /boot/efi │ 100.0 MB │ 30 MB │ 70 MB │ [████░░░░░░] 31% │ ╰──────────────┴──────────┴────────┴─────────┴───────┴───────────╯ ``` 彩色 bar + 按 mount point 分组。`df` 的丑表格再见。 ## procs (ps 替代) ```bash brew install procs ``` ```bash $ procs node PID: 1234 USER: alice CPU: 12.3% MEM: 234MB START: 10:23 STATE: Sleeping CMD: node server.js ``` 彩色 + 友好单位 + tree mode: ```bash procs --tree procs --sortd cpu # 按 CPU 降序 ``` ## btop (top / htop 替代) ```bash brew install btop ``` GUI-like TUI 监控,键盘鼠标都能用。比 htop 更现代。 ``` ╔═CPU═══════════════════════╗ ╔═MEM═════════════╗ ║ 1 ███████████░░ 56% ║ ║ Total 16.0 GB ║ ║ 2 ████░░░░░░░░░ 22% ║ ║ Free 3.2 GB ║ ║ 3 ███░░░░░░░░░░ 15% ║ ║ Cached 4.5 GB ║ ║ 4 ██░░░░░░░░░░░ 10% ║ ╚═════════════════╝ ╚═══════════════════════════╝ ``` ## tldr (man 替代,例子) ```bash brew install tealdeer # Rust 实现,比 nodejs 版快 ``` ```bash $ tldr tar tar - Archiving utility. - Create an archive from files: tar -cf path/to/target.tar path/to/file1 path/to/file2 - Extract: tar -xf path/to/source.tar - List contents: tar -tvf path/to/source.tar ``` 5 个常用例子,不像 man 一屏密密麻麻看不进去。 ## 我的 .zshrc 完整 alias 块 ```bash # Modern CLI replacements # cat → bat command -v bat &>/dev/null && alias cat='bat -p' # ls → eza if command -v eza &>/dev/null; then alias ls='eza --group-directories-first' alias ll='eza -l --icons --git' alias la='eza -la --icons --git' alias lt='eza --tree -L 2' fi # find → fd command -v fd &>/dev/null && alias find='echo "use fd";' # grep → rg # (don't alias, keep grep for scripts) # du → dust command -v dust &>/dev/null && alias du='dust' # df → duf command -v duf &>/dev/null && alias df='duf' # ps → procs (for human use, scripts keep ps) # don't alias # top → btop command -v btop &>/dev/null && alias top='btop' ``` ## 不替代 / 谨慎替代 - **grep**:很多 script 用 `grep -E` 之类,alias rg 会破。手动用 `rg`。 - **find**:很多 script 用 `find -exec`,alias fd 会破。 - **ps**:进程间通信 / script 解析常用 ps 的 well-known column 输出。 不要 alias。 - **sed**:stream pipeline 中 sed 仍最广。 **人交互用新 / script 用老**。alias 加 `if [[ -t 1 ]]` 检查 tty, 避免 pipeline 触发。 ## 一次性试 不想 alias 永久 → 装了直接调名字: ```bash $ bat file.md $ eza -l $ dust ``` 朴素需求老命令、想要好看 / 信息多用新工具。 ## 性能 / 资源 这些 Rust / Go 工具普遍比传统 C 实现快 1.5-5x。 启动开销小(无 JVM 之类)。 单 binary 没 dep,下个 portable 文件就跑。 ## 踩过的坑 1. **NerdFont 字符方块**:eza --icons 显示乱码 → 终端字体没装 NerdFont。 2. **CI 没装替代品**:本地 alias `find=fd` 习惯后写 script `find` → CI 报错。 alias 只在 interactive shell 加。 3. **eza 旧版叫 exa**:教程到处写 exa,已 abandoned 改 eza。fork 后 保持兼容。 4. **bat 没装 themes**:默认 monokai。喜欢 dracula / nord 等 → `bat --list-themes` + `bat --theme=Dracula file`。 5. **远程 server 没装**:ssh 上去 alias 不在。要么 dotfiles 同步 + ssh 自动装。
## 起因 代码搜索 / log 分析 / 文本处理,老朋友 grep 一直在。但实际开发场景: - 项目目录 grep:"grep -r 'foo' ." 默认搜 node_modules / .git → 慢 + 噪音 - 多文件类型:"grep --include='*.py'" 语法不顺 - regex 不一致(POSIX vs PCRE vs Perl) `ripgrep`(`rg`)是 Rust 写的,**默认尊重 .gitignore + 多线程 + PCRE2**。 工程师日常用比 grep 强百倍。 ## 装 ```bash brew install ripgrep apt install ripgrep cargo install ripgrep ``` ## 基础 ```bash # 当前目录搜 "foo" rg foo # 指定目录 rg foo src/ # 只搜某文件类型 rg foo -t py rg foo -tpy # 排除某类型 rg foo -T js # 列出支持的类型 rg --type-list ``` ## 默认行为对比 grep | | grep -r | rg | |---|---|---| | 默认递归 | 否(要 -r) | 是 | | 尊重 .gitignore | 不 | 是 | | 二进制文件 | 默认搜 | 默认跳过 | | 隐藏文件 | 默认搜 | 默认跳过(要 --hidden) | | 多线程 | 否 | 是 | | Unicode | 不完美 | 完美 | | 文件名颜色 | 看 alias | 默认彩 | | 速度 | 1x | 5-100x(看场景) | 在一个 100w 行 / 1 GB / 15k 文件的 monorepo 搜某 function: ```bash $ time grep -r "func MakeWidget" . real 0m12.345s $ time rg "func MakeWidget" real 0m0.143s ``` 差不多 100x(grep 还搜了 node_modules / dist 噪音输出)。 ## 常用 flag ```bash # 上下文 rg foo -A 3 # 后 3 行 rg foo -B 2 # 前 2 行 rg foo -C 5 # 前后各 5 行 # 不区分大小写 rg -i foo # 完整词 rg -w foo # 不匹配 foobar # 替换(预览,不写入) rg foo --replace bar # 写入:用 sd 或者 rg + xargs # 列文件名,不显内容 rg foo -l # 不显文件名(pipeline 用) rg foo --no-filename # count rg foo -c # 每文件 match 数 rg foo --count-matches # 总 match 数 ``` ## 玩 regex PCRE2 模式: ```bash rg -P 'func\s+(\w+)\s*\(' src/ # 复杂 PCRE # 多 line(跨行 match) rg --multiline 'class\s+\w+\s*\{[^}]*\}' # JSON path-like rg -P '"name":\s*"\w+"' ``` PCRE2 (-P) 支持 lookbehind / lookahead / named group。POSIX regex (-e) 简单但不够。 ## 在 vim / VS Code 用 vim: ```vim " .vimrc set grepprg=rg\ --vimgrep\ --no-heading :grep "TODO" " 用 rg 而不是 grep ``` VS Code:搜索框默认就是 ripgrep(VSCode 内置)。配置可以加 `search.useRipgrep` 之类(其实默认开)。 ## 配合其它工具 ```bash # fzf 实时搜 rg foo | fzf # fzf preview 找文件 + 高亮内容 rg foo -l | fzf --preview 'rg --color always -n foo {}' # xargs 批改 rg -l 'oldFunc' | xargs sed -i 's/oldFunc/newFunc/g' # 或者用 sd(rust sed) rg -l 'oldFunc' | xargs sd 'oldFunc' 'newFunc' ``` ## .ignore + .rgignore `.gitignore` ✓ 默认尊重。 项目里加 `.ignore` 或 `.rgignore` 文件,让 rg 额外排除: ``` # .ignore(rg / fd 都用) generated/ dist/ build/ *.lock ``` git 不用排但搜索不想看的。 ## 类型自定义 ```bash # 注册自定义类型 rg --type-add 'config:*.{yaml,yml,toml,json}' rg 'redis' -t config ``` 放 alias / config file: ```bash # ~/.config/ripgrep/config --type-add=config:*.{yaml,yml,toml,json} --smart-case --max-columns=200 --max-columns-preview ``` `export RIPGREP_CONFIG_PATH=~/.config/ripgrep/config` 让 rg 自动加载。 ## 性能数据 在 Linux kernel source(约 70k 文件,约 1.3 GB): ```bash $ time rg PM_RESUME 0.12s $ time grep -r PM_RESUME 3.42s $ time ag PM_RESUME # the_silver_searcher 0.45s ``` rg 是当前最快的代码搜索工具。 ## ag / ack 怎么样 - `ag`(the silver searcher):5 年前是黄金标准,今天 rg 全面超越(速度 + 功能) - `ack`:老 perl 写的,速度慢一档 - `rg` 是事实标准 ## 一个真实工作流 debug 时找"为啥某变量是这值": ```bash # 1. 哪写过这变量 rg "user_score" --type py # 2. 哪写这值 rg "user_score\s*=" --type py # 3. 函数调链 rg "calc_user_score|update_user_score" --type py -A 5 # 4. 用 vimgrep 格式给 vim rg --vimgrep "user_score" --type py > /tmp/grep.out # 然后 vim -q /tmp/grep.out 跳 quickfix list ``` 10 秒解决"找全部相关代码"。grep 同样要分多次 + 慢 + 噪音。 ## 跟 git grep 对比 `git grep` 也很快(multi-thread + 知道哪些是 tracked file): ```bash git grep "foo" # 只搜 tracked git grep -W "foo" # 显示完整函数 ``` 优势:知道 git 跟踪状态。 劣势:只搜 tracked,不能搜 untracked / ignored;项目外目录用不了。 我通常 `rg` 90% + `git grep` 10%(review 提交时)。 ## 踩过的坑 1. **隐藏文件不搜**:搜 `.env` 找不到 → `rg --hidden`。 2. **.gitignore 太严**:项目里 ignore 了 generated code → rg 也不搜。 `rg -uu` 跳过 ignore;`--no-ignore` 完全不读 .gitignore。 3. **PCRE2 没编译**:自己编译的 rg 可能没 PCRE2 support → `-P` 报错。 `rg --pcre2-version` 检查;用 prebuild。 4. **大 binary file 误判**:rg 用 NUL byte 检测 binary,某些 utf-16 文件被判 binary 跳过。`-a` 强制当文本搜。 5. **smart-case 反直觉**:默认 `--smart-case`:全小写 = 不区分大小写; 有大写 = 区分。我有时想要小写也区分大小写 → `-s`。
## 起因 每个项目不同 env: - 项目 A:DATABASE_URL=postgres://localhost/a, API_KEY=xxx - 项目 B:DATABASE_URL=postgres://localhost/b, AWS_PROFILE=client 老办法: - `source .env`(每次开新 shell 要重 source) - `.envrc` shell script `. ./envrc`(容易忘) - `dotenv` lib 在应用里读(CLI 工具读不到) `direnv` 解决:进目录自动 export env,出目录自动 unset。 ## 装 ```bash brew install direnv apt install direnv # shell 集成 eval "$(direnv hook zsh)" # 加到 .zshrc ``` ## 用 ```bash cd ~/projects/myapp echo 'export DATABASE_URL=postgres://localhost/myapp' > .envrc direnv allow # 首次要批准 cd .. # DATABASE_URL unset cd ~/projects/myapp # DATABASE_URL 自动 set ``` `.envrc` 是 bash script,可以做任何事。 ## 安全 `.envrc` 是任意 shell code → 安全风险。 direnv 强制 `direnv allow` 才执行 → 你 review 后启用。 文件改了 → 重新 allow。 ```bash cd ~/some-random-project # direnv: error .envrc is blocked. Run `direnv allow` to approve its content ``` ## 加载 .env ```bash # .envrc dotenv # 加载同目录 .env 文件 ``` 或者: ```bash dotenv .env.local ``` 支持标准 .env 格式: ``` DATABASE_URL=postgres://... SECRET_KEY=abc ``` ## venv 自动 activate ```bash # .envrc layout python python3.12 # 自动建 venv + activate ``` 或者用现有 venv: ```bash # .envrc source .venv/bin/activate ``` 进项目目录 → venv 自动激活 + env vars 加载。 不用记 `source .venv/bin/activate`。 ## Node 版本 ```bash # .envrc use node 20 # mise / nvm 集成 ``` 或者: ```bash # .envrc PATH_add ./node_modules/.bin # 加到 PATH,能调 local tool ``` `PATH_add` 在前 → 项目 local 工具优先。 ## 多层 .envrc ``` ~/.envrc # 全局(如 EDITOR=nvim) ~/projects/.envrc # 所有 project 共享(如 PNPM_HOME=...) ~/projects/myapp/.envrc # 项目特定 ``` 进 myapp → 3 个 .envrc 合并(深层覆盖浅层)。 ## 与 dotenv-cli / 应用层 dotenv 应用层 `dotenv` lib 只对应用进程有效。 direnv 对 **shell 内任何命令** 有效(如手动 `psql`, `aws cli` 等)。 混用:direnv 给 shell + 应用 dotenv 给 production deploy(不用 direnv 在 server)。 ## 与 mise mise 也支持 dotenv-load: ```toml # .mise.toml [env] _.file = ".env" ``` 两个工具能 overlap。我用:mise 管 runtime 版本,direnv 管 env vars。 都装 fine(启动 hook 不冲突)。 ## 安全 - secret in git `.env` 通常 gitignore,`.envrc` 也 gitignore 较好(避免 secret 进 git)。 template: ```bash # .envrc.example (commit 这个) export DATABASE_URL=postgres://localhost/myapp export API_KEY=<your-key> # .envrc (gitignore,每人自己 cp 自己改) ``` 或者用 1Password CLI 拉 secret: ```bash # .envrc export API_KEY="$(op read 'op://Personal/myapp/api_key')" ``` 执行 direnv 时去 1Password 拉,不 commit 实际值。 ## CI CI 通常不用 direnv(用 env var injection)。但 direnv 的 .envrc 可以 被 CI 间接利用: ```bash # CI step source .envrc ``` 或者把 .envrc 改成 plain .env 用 dotenv CLI 跑。 ## 真实工作流 我每个项目 .envrc: ```bash dotenv .env.local # local 配置 layout python # venv PATH_add ./bin # 项目 script PATH_add ./node_modules/.bin export PYTHONUNBUFFERED=1 export AWS_PROFILE=$(basename "$PWD") # AWS profile = 项目名 ``` 进项目 → venv + env + AWS profile + PATH 一键就绪。 1 秒切换项目 context。 ## 与 docker compose env_file 对比 ```yaml # docker-compose.yml services: app: env_file: .env ``` compose 自动 load .env。但只对 compose 启动的容器有效。 shell 里手动 psql / debug 仍要 direnv。 ## 性能 direnv hook 在 shell prompt 前跑(PROMPT_COMMAND)。 单次 < 5ms(cache)。 首次进新目录加载 .envrc 慢点(看 .envrc 复杂度)。 ## 踩过的坑 1. **没装 hook**:source 了 hook 但没重启 shell → direnv 不生效。 `exec zsh` 或开新 terminal。 2. **`.envrc` 改了忘 allow**:env 用旧值。每次改后 `direnv allow`。 工具会提示。 3. **export 漏**:bash 习惯 `KEY=value` 而非 `export KEY=value` → 子 进程拿不到。`.envrc` 内必须 export。 4. **dotenv 顺序**:`.envrc` 内 `dotenv` 后又 `export` 覆盖。后者 生效。 5. **cd 进子目录 env 不变**:direnv 按 .envrc 文件位置加载。子目录 没 .envrc 沿用父。OK 但有时困惑。
## 起因 终端里"找东西"老操作: - `cd ../../../some/path` —— 路径长 + 容易记错 - `Ctrl-R` 反向搜历史 —— 一次只能展示一条 - `git checkout <Tab>` —— 分支多了 tab 全列出来 - `kill <pid>` —— 先 `ps` 查 PID 再 kill `fzf` 给所有这些场景加了模糊搜索 + 实时筛选 + 多选。装一次,所有地方 都香。 ## 装 ```bash # macOS brew install fzf $(brew --prefix)/opt/fzf/install # 装 shell 集成 # Ubuntu/Debian apt install fzf # 或者源码(最新版) git clone --depth 1 https://github.com/junegunn/fzf.git ~/.fzf ~/.fzf/install ``` `install` 脚本问你装不装 `Ctrl-T`(粘文件)/`Ctrl-R`(搜历史)/`Alt-C` (cd 目录)键绑定 → 全选 yes。 ## 基础体验 shell 里随便敲: ```bash $ fzf ``` 打开一个全屏列表(默认是当前目录所有文件),上下选 / 输入字符模糊 过滤 / 回车确认。 ```bash $ vim $(fzf) # 模糊找文件然后 vim 打开 $ cat $(fzf) ``` ## 必装的 shell 绑定 ```bash Ctrl-T # 触发"插入文件路径"模式(任何 cmd 后都能用) Ctrl-R # 反向搜命令历史,模糊匹配 Alt-C # cd 到模糊选中的子目录 ``` 例: ```bash $ git diff <Ctrl-T> # 弹列表选文件 → 插入路径 $ vim <Ctrl-T> # 同上 ``` `Ctrl-R` 把内建反搜替换成多行筛选,找老命令快 10 倍。 ## 与 ripgrep / fd 配合 `fd`(rust find)+ `rg`(ripgrep)+ `fzf` 三件套: ```bash # ~/.zshrc export FZF_DEFAULT_COMMAND='fd --type f --hidden --exclude .git' export FZF_CTRL_T_COMMAND="$FZF_DEFAULT_COMMAND" export FZF_ALT_C_COMMAND='fd --type d --hidden --exclude .git' ``` `Ctrl-T` 只列文件 + 隐藏文件 + 排除 .git,速度起飞(fd 比 find 10x 快)。 ## 实战 alias ```bash # kill 进程 with fzf alias fkill='ps -ef | fzf --multi | awk "{print \$2}" | xargs kill' # git checkout 分支模糊选 gco() { local branch=$(git branch --all | grep -v HEAD | sed 's/^..//' | fzf) git checkout "$branch" } # cd 到最近用过的目录 cdh() { local dir=$(dirs -p | uniq | fzf) cd "$dir" } # kubectl pod fzf kpod() { kubectl get pods --all-namespaces | fzf } # tmux session 切换 tms() { local session=$(tmux ls -F '#{session_name}' | fzf) tmux switch-client -t "$session" } ``` 每条都是日常高频,5 个字符内启动 + 模糊选 + 操作。 ## preview 窗口 最强 feature:右侧实时显示选中项预览。 ```bash fzf --preview 'cat {}' # 文件用 bat(高亮) fzf --preview 'bat --color=always {}' --preview-window right:60% # git log fzf git log --oneline | fzf --preview 'git show --color {1}' # JSON file 用 jq fzf --preview 'jq -C . {}' ``` `{}` 是 placeholder 选中项。`{1}` 是第一字段(空格分割)。 ## 多选 `--multi` / `-m` 模式,`Tab` 选 / `Shift-Tab` 反选。 ```bash # 选多个文件 git add git add $(git status --short | awk '{print $2}' | fzf -m) # 选多个 PR 关闭 gh pr list | fzf -m | awk '{print $1}' | xargs -I{} gh pr close {} ``` ## fzf 嵌入工具 很多 CLI 把 fzf 当 picker: - `forgit`:git 操作 + fzf 选 hunk / commit - `fz`:autojump 替代,fzf 选历史目录 - `peco`:fzf 的另一选择(go 写) - `sk`(skim):rust 写的 fzf 兼容 ## fzf as filter(programmable) 不只是交互工具,也是 Unix filter: ```bash echo -e "apple\nbanana\norange" | fzf # 交互选 echo -e "apple\nbanana\norange" | fzf -f "ap" # 非交互过滤 echo -e "apple\nbanana\norange" | fzf -f "ap" --print-query ``` `--filter`/`-f` 非交互模式,可以塞 pipeline。 ## tmux 集成 ```bash fzf-tmux -p 70%,70% # 在 tmux popup 里跑 fzf ``` 不会污染当前窗口。tmux 3.2+ 才有 popup 支持。 ## 性能 fzf 是 Go 写的,单线程能扫几百万行(typical < 100ms)。 benchmark: | 文件数 | 启动时间 | |---|---| | 10k | 30ms | | 100k | 100ms | | 1M | 800ms | 100w 文件项目(monorepo)也 sub-second。 ## 与替代品对比 - **peco**(go):同代,UI 略不同,更新少 - **sk / skim**(rust):兼容 fzf,速度更快,preview 略弱 - **selecta**(ruby):老 + 慢,过时 fzf 仍是事实标准。 ## 我的 .zshrc 完整片段 ```bash # fzf [ -f ~/.fzf.zsh ] && source ~/.fzf.zsh export FZF_DEFAULT_COMMAND='fd --type f --hidden --exclude .git' export FZF_DEFAULT_OPTS='--height 50% --layout=reverse --border --preview-window right:50%' export FZF_CTRL_T_OPTS="--preview 'bat --color=always --line-range :100 {}'" export FZF_ALT_C_OPTS="--preview 'eza --tree --color=always {} | head -50'" export FZF_CTRL_R_OPTS="--preview 'echo {}' --preview-window down:3:wrap" # alias alias gco='git_checkout_fzf' alias gst='git_status_fzf' git_checkout_fzf() { git branch --all --color=always | grep -v HEAD | \ fzf --ansi --preview 'git log --color {-1} -10 --oneline' | \ sed 's/.* //' | xargs git checkout } ``` ## 踩过的坑 1. **shell 启动慢**:fzf 装多个集成 → zsh 启动 +200ms。 `time zsh -i -c exit` 测一下,找 culprit。 2. **`Ctrl-R` 跟其它工具冲突**:tmux / vim / readline 都用 Ctrl-R。 fzf 集成默认覆盖 bash/zsh 的内建,要小心副作用。 3. **`fd` 在 Debian 名字是 `fdfind`**:装时 `apt install fd-find` → 命令叫 `fdfind`。alias 一下 `alias fd=fdfind`。 4. **preview 卡**:preview 命令慢(如 git show 大 commit)→ fzf 看着卡。 `--preview-window noborder` + 优化 preview 命令。 5. **macOS 与 zsh 5.9 兼容**:老 zsh 可能 fzf widget 报错。升 zsh / brew upgrade zsh。
## 起因 每个项目用不同版本的 runtime / SDK: - 项目 A:Node 18 + Python 3.11 - 项目 B:Node 20 + Python 3.12 - 项目 C:Go 1.22 + Bun 1.1 老办法各种 version manager: - nvm(Node) - pyenv(Python) - rbenv(Ruby) - gvm / goenv(Go) 每个工具一套 shell 集成 / shim 机制 → 启动慢 + 配置碎。 `mise`(Rust 写的,前身 rtx,前前身 asdf 的现代 fork)是**一个工具 管理所有版本**。`.mise.toml` 文件描述项目所需版本,进目录自动切。 ## 装 ```bash # macOS brew install mise # Linux curl https://mise.run | sh # 集成 shell echo 'eval "$(mise activate zsh)"' >> ~/.zshrc ``` ## 项目用 ```bash cd ~/projects/myapp # 给项目指定 Node 20 + Python 3.12 mise use node@20 [email protected] # 自动写到 .mise.toml cat .mise.toml # [tools] # node = "20" # python = "3.12" # 装这俩 mise install ``` 进入这目录 → mise 自动激活对应版本: ```bash $ cd ~/projects/myapp $ node --version v20.18.0 $ python --version Python 3.12.4 $ cd ~/projects/other $ node --version v18.20.0 # 自动切换 ``` ## .mise.toml 完整例 ```toml # .mise.toml(项目根,commit 进 git) [tools] node = "20.18.0" # 精确版本 python = "3.12" # 主版本 go = "1.22" bun = "latest" # 最新 "npm:pnpm" = "9" # 通过 npm 装 pnpm # 环境变量 [env] NODE_ENV = "development" PYTHONUNBUFFERED = "1" _.file = ".env" # 加载 .env 文件 # 项目 task(取代 justfile / Makefile,简单场景) [tasks.dev] run = "node server.js" [tasks.test] run = "pnpm test" ``` `mise install` 装全部 → 进目录自动用。 ## 比 asdf 快多少 mise 是 asdf 的 Rust rewrite + 改进: | | asdf | mise | |---|---|---| | 语言 | Bash | Rust | | shell hook 延迟 | 50-200ms | < 5ms | | 工具机制 | shim | env-based + shim | | 配置 | .tool-versions | .mise.toml | | 任务系统 | 无 | 有(取代 just) | | 装速度 | 慢 | 快 | cd 到项目目录,asdf 的 shell hook 拖 100ms+。mise 几乎无感。 ## 多 runtime + 同时 ```toml [tools] node = ["20", "22"] # 两个版本都装 # 默认用第一个 → node 是 20 # 调 22:mise exec node@22 -- node --version ``` ## 共享 mise.toml + 个人 override ```toml # .mise.toml(commit) [tools] node = "20" python = "3.12" # .mise.local.toml(gitignore,个人覆盖) [tools] python = "3.13" # 我本地试 3.13 ``` `.mise.local.toml` 覆盖 `.mise.toml`,团队不受影响。 ## 全局默认 ```bash mise use -g node@22 mise use -g [email protected] ``` 写到 `~/.config/mise/config.toml`。没有 `.mise.toml` 的目录用全局 版本。 ## 工具源 mise 用 asdf 插件 + 内置后端: ```bash # 列出可装 mise ls-remote node mise ls-remote python # 通过 npm / pip / cargo 后端装 mise use "npm:typescript@5" mise use "pipx:black@24" mise use "cargo:ripgrep@13" ``` 也支持 GitHub release / ubi / pre-compiled binary 等。 ## task runner ```toml [tasks.lint] description = "Run linters" run = """ ruff check . pnpm tsc --noEmit """ [tasks.test] depends = ["lint"] run = "pytest" ``` ```bash $ mise tasks # 列出 $ mise run test # 跑(先跑 lint) ``` 替代 justfile 的简单场景。复杂的还是 just 强。 ## 环境变量 + secrets ```toml [env] DATABASE_URL = "postgres://localhost/myapp" _.file = ".env.local" # 也加载这文件 _.path = ["./bin", "./node_modules/.bin"] # 加 PATH ``` 进目录自动 export → 写脚本不用 source。 `.env.local` 在 .gitignore,本地 secrets。 ## CI 用 mise ```yaml # GitHub Actions - uses: jdx/mise-action@v2 with: install: true - run: mise run test ``` CI 装的版本 = `.mise.toml` 里的版本 = 本地版本。一致性保证。 ## 与 Docker dev container 对比 | | mise | Docker dev container | |---|---|---| | 隔离 | 进程级 | 完全隔离 | | 启动 | 即时 | 启动 container 几秒 | | IDE | 原生(无远程) | VS Code Remote | | 文件 IO | 原生 | bind mount 可能慢(mac) | | 生产一致性 | 中(runtime 一致 OS 可能不同) | 高 | 小项目 / 单人 → mise 够用 + 更快。 大项目 + 多 OS 团队 → devcontainer 更靠谱。 我自己 95% 项目 mise,少数客户项目 devcontainer。 ## 从 nvm 迁移 ```bash # 卸 nvm rm -rf ~/.nvm # 从 .zshrc 删 nvm source # 装 mise + 集成 # 老的 .nvmrc 自动识别(兼容) echo "v20" > .nvmrc mise install ``` `.nvmrc` / `.python-version` / `.tool-versions` mise 全识别。 不强制迁到 `.mise.toml`,但建议(功能更强)。 ## 踩过的坑 1. **shell 没集成**:`node --version` 返回老版本。mise 必须 shell hook 才能切。`mise activate` 加 rc 文件 + 重开终端。 2. **CI 装很慢**:mise 第一次装 Python 3.12 编译几分钟。 `MISE_PYTHON_COMPILE=0` 用预编译 binary(uv 同样做法)。或者 cache `~/.local/share/mise/installs`。 3. **跟 asdf 冲突**:装了 asdf 又装 mise → PATH 混。卸一个。 4. **某些工具没插件**:罕见工具 mise 没现成插件。可以 `mise plugin install <git-url>` 加 asdf 插件用。 5. **`mise use` 不写版本会写 latest 到 .mise.toml**:commit 进 git 后别人装的可能是更新版本。建议明确版本号。
## 起因 经常遇到这场景: - 当前 feature 分支开发到一半 - 紧急 hotfix 需要在 main 改一行 - 又要 review 同事的 PR 在 third-branch 老办法:`git stash` + `git checkout main` + 改 + commit + `checkout feature` + `stash pop`。每次切换几分钟(编辑器要重 index、deps 不一样还要重装、 本地服务要重启)。 `git worktree` 解决:**一个仓库,多个工作目录,分别 checkout 不同分支**。 ## 基本用法 ```bash cd ~/projects/myapp # 主 worktree(main 分支) # 创建新 worktree 在 ../myapp-hotfix,checkout hotfix-x 分支 git worktree add ../myapp-hotfix -b hotfix-x # 创建在 ../myapp-review,checkout 现有 PR 分支 git worktree add ../myapp-review feature/pr-42 # 列出所有 git worktree list # /home/u/projects/myapp abcd1234 [main] # /home/u/projects/myapp-hotfix ef567890 [hotfix-x] # /home/u/projects/myapp-review 12345abc [feature/pr-42] # 完成后删 git worktree remove ../myapp-hotfix ``` 每个 worktree 是**独立目录**:自己的 working tree、index、stash、 hooks 状态。但共享 `.git/`(共享对象库、refs、config)。 磁盘只增加 working tree 文件大小,不重复存历史。 ## 我的目录约定 ``` ~/projects/myapp/ # 主 worktree(main) ~/projects/myapp-wt/ # 所有附加 worktree 都进这里 ├── feature-search/ ├── hotfix-401/ └── review-bob-pr/ ``` `-wt/` 在 `.gitignore` 里加(实际上 git 不会管它),也加到编辑器的 排除列表,避免 indexing 重复。 ## 效果 切换分支从"清理 + checkout + 重装 deps + 重启服务" → "cd 到另一个目录"。 - 编辑器在每个目录独立打开 - 每个 worktree 跑自己的 dev server(端口不同) - venv / node_modules 独立(cargo / go 缓存可以共享) 实测:从 hotfix 切回主 feature 从平均 5 分钟 → 5 秒。一周省 1 小时。 ## 自动化 每次 review 同事 PR 都建 worktree,写个 alias: ```bash # ~/.zshrc function wt-pr() { local pr=$1 gh pr checkout $pr # 拉到本地分支 local branch=$(git branch --show-current) cd .. git -C myapp worktree add "myapp-pr-$pr" "$branch" cd "myapp-pr-$pr" } ``` `wt-pr 42` → 自动 fetch + 建 worktree + cd 过去。 ## venv / node_modules 怎么办 每个 worktree 独立装: ```bash cd ../myapp-hotfix uv sync # 装到本地 .venv/ pnpm install # 装到本地 node_modules/ ``` uv / pnpm 用 hard link / 全局 cache → 实际硬盘开销小。 n × 同 deps 不会真的 n 倍磁盘。 ## 共享 hooks `.git/hooks/` 是共享的(git 内部目录共用)。 每个 worktree 都跑同样的 pre-commit。一般是好事。 个别 worktree 想跳过 hook → `--no-verify`。 ## 配 IDE VS Code:每个 worktree 当成独立 workspace 打开。 WebStorm / IntelliJ:同样。 Cursor:可以在多个窗口打开不同 worktree 同时操作。 `.vscode/settings.json` 通过 git ignore 的方式各自配置。 ## 与 stash / checkout 对比 | 场景 | stash + checkout | worktree | |---|---|---| | 切换速度 | 慢(要重 index / restart) | 快(cd 即可) | | 心智模型 | 一个目录多状态 | 多目录多状态(更直觉) | | 编辑器 indexing | 重做 | 不需要 | | 磁盘 | 少 | 多 working tree(但对象库共享) | | 临时草稿 | stash 容易丢 | 文件实存 | 90% 场景 worktree 胜。 ## 与 git clone 对比 git clone 多次克隆同 repo: - 优点:完全独立,无干扰 - 缺点:每个独立 `.git/`(重复存对象库,几百 MB → 几 GB)+ 不同 clone 之间分支不互通 worktree 共享 `.git/`,省空间 + 一处 fetch 全部能看到新 remote refs。 ## main worktree 不能删 ```bash git worktree remove . # error: cannot remove main working tree ``` 主 worktree 是创建仓库时所在的目录。要换主 worktree 要 `git worktree move`(少用)。 ## 踩过的坑 1. **同分支不能两个 worktree checkout**:保险机制防止冲突。 要看同一分支就建一个 detached HEAD worktree: `git worktree add ../tmp HEAD`。 2. **worktree 删除后 git 不会清理元数据**:`git worktree prune`。 或者用 `remove` 命令而不是手动 `rm -rf`。 3. **submodule + worktree**:早期 git submodule 在多 worktree 不太稳定。git 2.40+ 改善很多但仍偶有奇怪行为。 4. **CI 不支持 worktree**:CI clone 出来是单 worktree。worktree 是本地开发优化,不影响 CI。 5. **跨平台 path 差异**:worktree path 存在 `.git/worktrees/*/gitdir` 绝对路径。把仓库目录改名 / 移动 → worktree 失效。 用 `git worktree repair` 修。
## 起因 ssh 远程开发 + 本地多窗口工作流,需要: - 多终端面板(split / 切换) - detach 重连(断网不丢 session) - 持久化布局 `tmux` 是事实标准 20 年。`zellij`(Rust,2021+)是现代挑战者。 最近重度用 zellij 一个月。下面对比。 ## tmux 经典 + 普遍可用。 ```bash brew install tmux apt install tmux ``` `~/.tmux.conf` 配色 / 键位 / 插件。 ### 操作 ``` prefix = Ctrl-b(默认) prefix + c 新 window prefix + n 下个 window prefix + % 横分 pane prefix + " 竖分 pane prefix + d detach session prefix + [ 进 copy mode(scroll) ``` `tmux ls` 列 session,`tmux attach -t <name>` 接回。 ### 优势 - **最广泛可用**:所有 Linux distro / macOS / *BSD 包管理都有 - 极成熟(2007 至今) - 插件生态丰富(tpm + 几千插件) - 资源占用小(< 5 MB) - session 持久化 + tmux-resurrect 保存恢复 ### 劣势 - 默认配色 / 键位丑陋复古,要花时间配 - 学习曲线陡(前 1 小时挫败感强) - 配置文件语法奇特 - 无内置浮窗(3.2+ popup 比较基础) ## zellij ```bash brew install zellij cargo install zellij ``` 启动直接 `zellij`。 ### 操作 底部有状态栏显示当前 mode 和键位 → **不用记**! ``` Ctrl + p pane mode(split / resize) Ctrl + t tab mode(多 tab) Ctrl + s scroll mode Ctrl + o session mode(detach 等) Ctrl + n resize mode Ctrl + h move mode ``` 进 mode 后状态栏显示可用键,按数字 / 字母执行。 ### 优势 - **新手友好**:状态栏永远显示能做什么 - 默认配色现代 + 美观 - 内置 layout(KDL config) - 内置 floating pane / sticky pane - WASM 插件(Rust / Go 写插件) - 性能极好(Rust) ### 劣势 - 还年轻(兼容性 / 生态比 tmux 弱) - 老服务器没有(apt 包要等 24.04) - 远程 ssh 渲染偶有小问题 - 内存占用更大(30-80 MB vs tmux 5 MB) ## 配色 / layout 对比 **tmux 默认**: ``` [0] 0:bash- 1:vim* "myhost" 14:23 25-Apr-24 ``` 朴素绿色 bar,要配 powerline / status-utf8 才好看。 **zellij 默认**: ``` zellij_session TAB: 1 Bash 2 Vim ───────────────────────────────────── (panes) ───────────────────────────────────── Ctrl+p PANE Ctrl+t TAB Ctrl+s SCROLL Ctrl+o SESSION ``` 底部 hint bar 直接告诉你下一步。 ## 配置文件 **tmux** `.tmux.conf`: ```tmux set -g prefix C-a unbind C-b bind C-a send-prefix set -g mouse on set -g history-limit 50000 # split with intuitive keys bind | split-window -h bind - split-window -v # vim-style pane nav bind h select-pane -L bind j select-pane -D bind k select-pane -U bind l select-pane -R # plugin manager set -g @plugin 'tmux-plugins/tpm' set -g @plugin 'tmux-plugins/tmux-resurrect' run '~/.tmux/plugins/tpm/tpm' ``` **zellij** `~/.config/zellij/config.kdl`: ```kdl keybinds { normal { bind "Ctrl g" { SwitchToMode "Locked"; } } } theme "nord" simplified_ui false default_layout "compact" mouse_mode true ``` KDL 比 tmux 自定义语法人类友好得多。 ## layout file(强项) zellij: ```kdl // dev.kdl layout { tab name="editor" { pane command="nvim" } tab name="server" { pane split_direction="vertical" { pane command="pnpm" { args "dev"; } pane command="docker" { args "compose" "logs" "-f"; } } } tab name="test" { pane command="pnpm" { args "test" "--watch"; } } } ``` 启动: ```bash zellij --layout dev.kdl ``` 一键开 3 tab / 多 pane 各跑特定命令。 tmux 等价是 shell script + tmux send-keys → 又长又脆。 ## 远程 ssh 注意 tmux:远程跑 tmux + 本地 ssh 进去 attach。断网 → 进程不丢。 zellij:同样可以,但 zellij 用更多 ANSI escape → 慢网络上重绘 更明显。 我远程**仍用 tmux**。本地 zellij。 ## 我现在的工作流 - 远程服务器:tmux(兼容性 / 速度 + 几乎所有服务器都装好) - 本地 macOS:zellij(视觉好 + layout file 项目自动布局) - 跨 ssh 项目:zellij 本地嵌套(zellij outer + tmux inner on remote) ## 内存 / 启动 ```bash # tmux: ~3 MB resident # zellij: ~30 MB resident # zellij 启动 ~ 100ms vs tmux ~ 20ms ``` 500 倍 memory 差距听起来吓人,绝对数都很小。除非 1 GB 小机器 (树莓派 / 老 VPS)不是问题。 ## 共享 session(远程 pairing) tmux:`tmux a -t name`(多终端 attach 同 session,看到同样画面)。 zellij:`zellij attach <session>` 同样支持。 zellij 还有 `zellij --new-session-with-layout`,新人加入直接套布局。 ## 替代品快览 - **screen**:祖宗,纯 80 年代风格,今天没人新装 - **wezterm**:终端 emulator + 内置 multiplexer(可替代 zellij/tmux) - **kitty**:终端 emulator,有窗口管理但不是 multiplexer 如果你想"一个工具搞定终端 + multiplex" → wezterm。 传统派 → tmux/zellij 跟终端解耦。 ## 决策 - **新人 / 个人** → zellij(学习曲线低) - **远程 ssh / 老 server** → tmux(兼容) - **重 layout 配置** → zellij(KDL layout file) - **重 plugin** → tmux(生态成熟) - **极简资源** → tmux ## 踩过的坑 1. **zellij + 24-bit color 终端报错**:老终端不支持 truecolor。 `TERM=xterm-256color` 或者用 alacritty / kitty / wezterm。 2. **zellij 复制到系统剪贴板**:默认 OSC 52 escape。某些终端不支持 → 要配 `copy_command "pbcopy"` (mac) / `xclip`。 3. **tmux 2.x vs 3.x**:服务器装的 tmux 2.x 没 popup / 某些新 feature。 ssh 远程时小心。 4. **嵌套 multiplex 键冲突**:本地 zellij + 远程 tmux → prefix 键冲突。 把 inner 的 prefix 改成不同(`C-a` vs `C-b`)。 5. **`.tmux.conf` 编辑要 source**:改完不会自动重载。 `tmux source ~/.tmux.conf`,或者 `prefix + r` bind 一下。