知识广场
按学科筛选:计算机科学 / 软件工程
«计算机科学 / 软件工程» 分类下共 42 篇帖子
## 起因 每个项目都有一堆"开发命令": ```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**:可以但易嵌套深 → 跑得乱。一般保持扁平结构。
## 起因 新机器 VSCode 装上后大家第一件事是装一堆插件:bracket pair colorizer / icon theme / 各种 snippet。其实 VSCode 内置功能已经很强,不知道反而错过。 下面是我每天用、新人常不知道的 12 个原生功能。 ## 1. 多光标编辑 ``` Alt+Click 加一个光标 Ctrl+D 选中下一个相同 word(继续按 Ctrl+D 选更多) Ctrl+Shift+L 选中所有相同 word Alt+Shift+I 多行末尾各加一个光标(先选多行) Ctrl+Alt+↓/↑ 垂直加光标(多行同列编辑) ``` 例:选中变量名 `old` → 几次 Ctrl+D → 同时改 5 处。 比 find/replace 快 10 倍(避免改到不该改的)。 ## 2. 命令面板(Command Palette) ``` Ctrl+Shift+P 命令面板 ``` VSCode 所有功能都在这里。打 "format" 找格式化命令;打 ">git" 找 git 操作;打 "Reload" 重启 window。 派生: ``` Ctrl+P 快速打开文件(fuzzy) Ctrl+Shift+O 当前文件 symbol 跳转 Ctrl+T workspace 全局 symbol 跳转 Ctrl+G 跳行号 Ctrl+@ 查看 outline ``` 记 `Ctrl+P / Ctrl+Shift+P / Ctrl+Shift+O` 三个就够日常。 ## 3. 集成 terminal ``` Ctrl+` 打开 / 切换 terminal Ctrl+Shift+` 新 terminal ``` terminal 支持 split / multiple shell / link 点击文件路径直接跳过去。 不用切窗口看输出。 ## 4. Source Control(git 集成) ``` Ctrl+Shift+G Source Control 视图 ``` - 看 diff(点 modified 文件) - 提交(输入 message + Ctrl+Enter) - stage hunk(左侧 ± 按钮) - branch 切换(左下角 status bar) - 推送 / 拉取(status bar 同步图标) 复杂的 rebase / 解 conflict 也有原生 UI。 GitLens 插件加强(line blame / file history 直观),但基础够用。 ## 5. 多文件 search & replace ``` Ctrl+Shift+F workspace 搜索 Ctrl+Shift+H workspace 替换 ``` - 支持 regex(小图标 .*) - preserve case(保留大小写:oldVar → newName,OldVar → NewName) - include / exclude 文件 glob - 全选预览后 replace all 替代很多自己写 sed 脚本的场景。 ## 6. snippets ``` Ctrl+Space 补全(如果 LSP 没自动弹) Tab 接受补全 ``` 自带 snippets:`for`、`if`、`function` 等输入触发模板。 自定义: ``` File → Preferences → Configure User Snippets → typescript.json ``` ```json { "Console Log": { "prefix": "clog", "body": ["console.log('${1:label}:', ${2:value})"], "description": "log with label" } } ``` 输入 `clog` + Tab → `console.log('label:', value)`,光标在 label。 ## 7. emmet(HTML / CSS / JSX) ``` div.container>ul>li*5>a[href="#"]{Item $}+Tab ``` → ```html <div class="container"> <ul> <li><a href="#">Item 1</a></li> <li><a href="#">Item 2</a></li> <li><a href="#">Item 3</a></li> <li><a href="#">Item 4</a></li> <li><a href="#">Item 5</a></li> </ul> </div> ``` CSS:`m10p` → `margin: 10px;`。HTML 模板秒生成。 ## 8. zen 模式 / 双 editor ``` Ctrl+K Z zen mode(隐藏所有 UI) Ctrl+\ split editor 左右 Ctrl+1 / Ctrl+2 切到第 1 / 2 个 editor Ctrl+W 关当前 editor ``` 复杂 review 时左 test 右 code 并排。 ## 9. 自定义 keymap `File → Preferences → Keyboard Shortcuts`: - 搜命令名(如"toggle terminal")→ 改 binding - 写 keybindings.json: ```json [ { "key": "ctrl+j", "command": "workbench.action.terminal.toggleTerminal" }, { "key": "ctrl+shift+j", "command": "editor.action.joinLines" } ] ``` Vim 用户装 vscode-neovim(不是 vim 插件)能用真 nvim 引擎,几乎 100% vim 体验。 ## 10. workspace 设置 `.vscode/settings.json` 进 git,团队成员共享: ```json { "editor.formatOnSave": true, "editor.codeActionsOnSave": { "source.organizeImports": "explicit", "source.fixAll.eslint": "explicit" }, "editor.rulers": [80, 100], "files.exclude": { "**/__pycache__": true, "**/.pytest_cache": true, "**/node_modules": true }, "search.exclude": { "**/dist": true, "**/.venv": true }, "python.defaultInterpreterPath": "${workspaceFolder}/.venv/bin/python", "[python]": { "editor.defaultFormatter": "charliermarsh.ruff" }, "[typescript]": { "editor.defaultFormatter": "esbenp.prettier-vscode" } } ``` 新人 clone 后 IDE 自动应用这些设置:format on save、自动 organize imports、文件树过滤 __pycache__ 等。 ## 11. tasks(不离开 IDE 跑命令) `.vscode/tasks.json`: ```json { "version": "2.0.0", "tasks": [ { "label": "test", "type": "shell", "command": "pytest -xvs", "group": { "kind": "test", "isDefault": true } }, { "label": "dev", "type": "shell", "command": "npm run dev", "isBackground": true, "presentation": { "reveal": "always", "panel": "dedicated" } } ] } ``` `Ctrl+Shift+P → Tasks: Run Task → test` 跑。 test failures 自动跳到对应行 + 显示 error。 ## 12. launch.json (debugger) `.vscode/launch.json`: ```json { "version": "0.2.0", "configurations": [ { "name": "Python: Current File", "type": "debugpy", "request": "launch", "program": "${file}" }, { "name": "Django", "type": "debugpy", "request": "launch", "program": "${workspaceFolder}/manage.py", "args": ["runserver", "--noreload"], "django": true }, { "name": "FastAPI", "type": "debugpy", "request": "launch", "module": "uvicorn", "args": ["app.main:app", "--reload"] }, { "name": "Node: Attach", "type": "node", "request": "attach", "port": 9229 } ] } ``` F5 启动 debugger,breakpoint / watch / call stack 全 GUI。 对 Python / Node 项目调试效率秒杀 print。 ## 几个值得装的插件 虽然主题是"原生功能",但有几个插件确实显著提升: 1. **GitLens**:行 blame / file history / interactive rebase 2. **Error Lens**:errors / warnings inline 显示 3. **REST Client**:写 `.http` 文件直接调 API 4. **Code Spell Checker**:拼写检查(注释 / 字符串) 5. **Better Comments**:`// TODO:` `// FIXME:` 颜色区分 不是 30+ 个的"插件汤",5 个就够了。 ## 性能 tip ```json { "files.watcherExclude": { "**/node_modules/**": true, "**/dist/**": true }, "search.followSymlinks": false, "telemetry.telemetryLevel": "off" } ``` 大项目(monorepo / 10万+ 文件)watcher 吃 CPU,excluded 加快。 ## 远程开发:Remote-SSH 服务器代码不想 mount / sync 到本地? ``` Remote-SSH: Connect to Host → server.example.com ``` VSCode 在远程跑 server 端,本地 UI 显示,编辑 / debugger / terminal 都像在本地。开发服务器 / 大型 GPU 机器场景神器。 ## 效果 教会团队新人这 12 个: - 多光标改名场景 -80% find/replace 使用 - "我代码格式不对了" 类 PR 评论消失(save 自动 format) - 不再"打开第二个窗口跑 terminal"(内置 terminal 够用) - workspace settings 进 git 让团队体验一致 ## 踩过的坑 1. **settings.json 全局 vs workspace 冲突**:workspace > user > default。 优先级搞清楚。 2. **format on save 改了你不想改的**:prettier 把字符串单引号改成双 引号之类。`.editorconfig` + `.prettierrc` 配项目偏好。 3. **多个 formatter for same language**:`editor.defaultFormatter` 强 指定,否则每次问你用哪个。 4. **Ctrl+Shift+P 命令找不到**:扩展没装就没那个命令。装对应 extension 后 reload window。 5. **远程 SSH 在 ARM mac 连 x86 server**:第一次连慢(下 server 端 binary)。挂代理设 `remote.SSH.useExecServer: false` 减少 hop。
## 起因 我想知道两种压缩方案哪个快——`gzip -9` vs `zstd -19` vs `xz -9`。 传统做法: ```bash time gzip -9 < big.txt > /dev/null # real 0m4.231s time zstd -19 < big.txt > /dev/null # real 0m3.876s ``` 跑一次的数字根本不可信(系统抖动 5-10%)。要科学就得跑 N 次取平均, 还要算标准差,手写麻烦。 `hyperfine` 是 Rust 写的 benchmark 工具,自动做 warmup + 多次运行 + 统计 + 多命令对比。 ## 安装 ```bash # Debian / Ubuntu sudo apt install hyperfine # 或者 brew install hyperfine cargo install hyperfine hyperfine --version ``` ## 单命令 benchmark ```bash hyperfine 'gzip -9 < big.txt > /dev/null' ``` 输出: ``` Benchmark 1: gzip -9 < big.txt > /dev/null Time (mean ± σ): 4.187 s ± 0.082 s [User: 4.135 s, System: 0.041 s] Range (min … max): 4.073 s … 4.298 s 10 runs ``` 默认跑 10 次(不够稳定会自动加),算均值 + 标准差 + 范围。 ## 多命令对比(最实用) ```bash hyperfine \ 'gzip -9 < big.txt > /dev/null' \ 'zstd -19 < big.txt > /dev/null' \ 'xz -9 < big.txt > /dev/null' ``` 输出: ``` Benchmark 1: gzip -9 < big.txt > /dev/null Time (mean ± σ): 4.187 s ± 0.082 s Benchmark 2: zstd -19 < big.txt > /dev/null Time (mean ± σ): 3.421 s ± 0.064 s Benchmark 3: xz -9 < big.txt > /dev/null Time (mean ± σ): 15.842 s ± 0.214 s Summary 'zstd -19 < big.txt > /dev/null' ran 1.22 ± 0.03 times faster than 'gzip -9 < big.txt > /dev/null' 4.63 ± 0.10 times faster than 'xz -9 < big.txt > /dev/null' ``` 最后的 `Summary` 直接告诉你倍数差距。 ## 参数扫描 ```bash hyperfine --parameter-list level 1,3,5,9,19 \ 'zstd -{level} < big.txt > /dev/null' ``` 会跑 5 个 benchmark(level=1, 3, 5, 9, 19),出对比表 + 倍数。 数值扫描: ```bash hyperfine --parameter-scan threads 1 16 \ 'cargo build --jobs {threads}' ``` `threads=1, 2, 3, ..., 16` 各跑一次。 ## warmup ```bash hyperfine --warmup 3 'some-command' ``` 先跑 3 次"不计入统计"——让 OS page cache 热起来 / JIT 预编译。 对涉及磁盘 IO 或冷启动的命令 essential。 ## 准备 / 清理钩子 ```bash hyperfine \ --prepare 'sync && echo 3 | sudo tee /proc/sys/vm/drop_caches' \ --cleanup 'rm /tmp/out' \ 'cp big.file /tmp/out' ``` `--prepare` 每次 run 之前跑(这里是清 page cache,模拟冷启动)。 `--cleanup` 每次 run 之后跑。 ## 导出原始数据 ```bash hyperfine --export-json bench.json --export-markdown bench.md cmd1 cmd2 # 或 CSV hyperfine --export-csv bench.csv cmd1 cmd2 ``` JSON 给脚本分析;Markdown 直接贴博客 / PR 描述。 ## 命令名 输出里的 `Benchmark 1` 不好看,自己命名: ```bash hyperfine \ -n 'old algorithm' 'old_cmd args' \ -n 'new algorithm' 'new_cmd args' Summary 'new algorithm' ran 1.34 ± 0.05 times faster than 'old algorithm' ``` PR 里直接贴这个 summary 是最好的"性能改进"证据。 ## 在 CI 跑 benchmark ```yaml # .github/workflows/bench.yml - name: Benchmark run: | hyperfine --warmup 3 --runs 20 \ --export-markdown bench.md \ 'cargo build --release' \ './before-binary' \ './after-binary' - name: Comment PR uses: peter-evans/create-or-update-comment@v4 with: issue-number: ${{ github.event.pull_request.number }} body-path: bench.md ``` 每次 PR 自动跑 benchmark + 评论到 PR。 ## 与 perf / time / Pythontimeit 对比 - **time**:粗糙,单次运行 - **/usr/bin/time -v**:详细但单次,还得自己算多次 - **Pythontimeit**:仅 Python 函数级 - **hyperfine**:黑盒命令多次运行 + 统计 它们解决不同问题: | 工具 | 适合 | |---|---| | hyperfine | "我的脚本快了吗" / "两个命令哪个快" | | perf | "函数 X 里哪行慢" | | flamegraph | "整个程序时间花在哪" | | timeit | "Python 这个表达式多快" | ## 高级:show-output 默认 hyperfine 把 stdout/stderr 重定向到 /dev/null(避免输出 IO 影响)。 debug 时打开: ```bash hyperfine --show-output 'cmd' ``` ## 限制时间 ```bash hyperfine --time-unit second --warmup 5 --min-runs 10 --max-runs 100 \ 'some-command' ``` `--min-runs` 保证统计意义,`--max-runs` 防止单次太慢导致总时间炸。 ## 效果 - 几秒钟解决 "A vs B 哪个快" 的争论,给出 ± 误差 - PR 里贴 hyperfine summary 比口头"快了一些"有说服力 100 倍 - CI 集成后性能回归被自动捕捉 - 团队选型(algorithm / lib / 构建工具)有了客观依据 ## 踩过的坑 1. **命令本身极快(< 1ms)**:hyperfine overhead 比命令还大。 测纳秒级用 `criterion`(Rust 库)或者 Python `timeit`。 2. **第一次跑 IO 命令明显慢**:page cache 没热。`--warmup 3` 或者 `--prepare 'sync && drop_caches'` 看你想测"warm" 还是"cold"。 3. **shell expansion 不一致**: ```bash # ❌ shell 把 *.txt 展开后传给 hyperfine,第一次有效,后续可能不一致 hyperfine 'cat *.txt > /dev/null' # ✅ 用 sh -c 让命令在子 shell 里展开 hyperfine 'sh -c "cat *.txt > /dev/null"' ``` 4. **CI runner 抖动大**:共享 runner 受其它 job 影响。专用 self-hosted runner 或者用 baseline 算相对差异,不看绝对数。 5. **--prepare 失败不退出**:prepare 命令出错 hyperfine 不知道。 prepare 命令里 `set -e` 自保。
## 起因 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 错。
经常要 `cd ~/projects/my-app/src/components` 这种长路径——zoxide 让你只打部分名字就能跳过去。它会自动学习你访问过的目录及频率, 下次靠模糊匹配 + 频率排序找最可能的目标。 ## 安装 ```bash # Debian 12+ / Ubuntu 22.04+ sudo apt install -y zoxide # 或 cargo install zoxide zoxide --version ``` ## 集成到 shell ```bash # bash: ~/.bashrc eval "$(zoxide init bash)" # zsh: ~/.zshrc eval "$(zoxide init zsh)" # fish: ~/.config/fish/config.fish zoxide init fish | source ``` `zoxide init` 注入一个 `z` 函数 + Tab completion + 命令钩子。 重新加载 shell 后开始用。 ## 用法 ```bash cd ~/projects/my-app/src/components # 第一次像往常一样 cd # zoxide 学到这个目录 cd ~/projects/other-app # 再去别处 # ... # 之后只打部分名字: z components # → ~/projects/my-app/src/components z my-app # → ~/projects/my-app z proj # → ~/projects/my-app(最近访问 + 频率高的) ``` 匹配规则: - 子串匹配(不是 fuzzy) - 按 "frecency" 排序:frequency × recency 的组合分 - 最近访问 + 经常去的最优先 ## 交互式选择(zi) 不确定哪个: ```bash zi compon # 弹出 fzf 让你选 ``` 需要装 `fzf`。`zi` 是 zoxide 的交互模式。 ## 多 token ```bash z my src # 必须同时含 "my" 和 "src" z app /home # 用 / 把不同部分隔开 ``` ## 列 / 清理 ```bash z -l # 列所有记录的目录(按 frecency) z -l projects # 列匹配 "projects" 的 zoxide remove ~/old-dir # 删某条 ``` ## 和原生 cd 共存 `z` 是新加的命令,`cd` 还是 `cd`。一些人喜欢 alias: ```bash alias cd='z' # 重激进;遇到 z 没记录的目录还是会回退到 cd ``` 我个人保留 `cd` 给一级目录(`cd ~`、`cd /etc`、`cd ..`), `z` 用于深层项目目录。 ## 与 fzf / starship 配合 ```bash # 用 fzf 浏览全部记录 zoxide query --interactive # 等价于 zi ``` starship prompt 不影响 z,但 z 命令很快所以 prompt 渲染不会卡。 ## 实际工作流改变 之前: ```bash cd ~/projects/myapp cd ../another-app/backend/src/main cd ../../../config ``` 之后: ```bash z myapp z main z config ``` 每次 1-2 个字符,速度感受非常明显。 ## 数据存哪 `~/.local/share/zoxide/db.zo`,纯二进制文件。换机器导出 + 导入: ```bash zoxide query --list --score > z.bak # 导出 # 新机器 cat z.bak | while read line; do score=$(echo "$line" | awk '{print $1}') path=$(echo "$line" | cut -d' ' -f2-) zoxide add "$path" done ``` 或者干脆删掉重新养——通常一周就练熟。 ## 与 docker / container 的限制 在容器内 zoxide 数据库是独立的,不会自动同步主机的。挂载主机的 `~/.local/share/zoxide` 也行,但容器路径和主机路径不一样时跳进 不存在的目录。容器开发一般不用 z。 ## 高级 hooks zoxide 在每次 cd / pwd 改变时记录目录。修改 `_z_dir_hook`: ```bash # 只记录 git 仓库根目录 _zoxide_should_add() { git -C "$PWD" rev-parse --show-toplevel >/dev/null 2>&1 } ``` 通常默认就够,不必魔改。 ## 踩过的坑 - 装了但 z 命令不工作:忘了 `eval "$(zoxide init bash)"` 或者放在了 rc 文件错的位置。`type z` 确认是个函数而不是 alias。 - `z foo` 跳到完全无关的目录:第一次访问陌生路径优先匹配最高频的记录; 解决办法 `cd /full/path/to/foo` 让 zoxide 学一下。 - 老 NFS 目录 unmount 后 zoxide 仍记着,每次 z 这个名字报错 "no directory matched"。`zoxide remove '/old/nfs/*'` 清掉。 - 同名目录在多个项目下:z 只给一个结果(frecency 最高)。要别的版本 用 `zi name` 交互选。
## 起因 新项目要装:PostgreSQL 16 + Redis 7 + Node 20 + Python 3.12 + ImageMagick + ffmpeg。同事的机器版本各异,"在我机器上能跑"频繁出现。 两种主流策略: A. `mise` + `direnv` 安装到 host B. `docker compose` 把依赖装容器 我俩都试过几次,下面记录适用场景 + 优缺点。 ## 方案 A:mise + direnv(本地原生) `.tool-versions`: ``` python 3.12.5 nodejs 20.10.0 go 1.22.1 postgres 16.4 redis 7.4.0 ``` `.envrc`: ```bash use mise dotenv .env PATH_add ./bin export DATABASE_URL=postgresql://localhost/myapp ``` ```bash cd myapp mise install # 装 Python 3.12.5 + Node 20.10 + Go ... # 第一次几分钟,之后秒级 ``` DB / Redis 还是装 host: ```bash brew install postgresql@16 redis brew services start postgresql@16 redis ``` 或者 host 上跑 docker 单独管 DB: ```bash docker run -d --name pg -p 5432:5432 -v pg-data:/var/lib/postgresql/data \ -e POSTGRES_PASSWORD=dev postgres:16 docker run -d --name redis -p 6379:6379 redis:7 ``` **优点**: - IDE 直接看到源码 + linter / 跳定义 / debugger 都丝滑 - 启动快(mise install 几秒;之后开机即可用) - 不依赖 docker daemon(笔记本 RAM 友好) - 测试 / lint / build 直接跑 host CPU 速度 **缺点**: - ImageMagick / ffmpeg 之类系统依赖每个 OS 装法不同 - C 扩展可能因 OS / glibc 版本兼容问题 - 多个项目用不同 Node 版本时偶尔切版本(mise 自动) ## 方案 B:docker compose(容器化) `docker-compose.yml`: ```yaml services: web: build: . volumes: - .:/app - node-modules:/app/node_modules # 不让 host 的覆盖 ports: ["3000:3000"] environment: DATABASE_URL: postgresql://app:dev@db/myapp REDIS_URL: redis://redis:6379 depends_on: - db - redis db: image: postgres:16 environment: POSTGRES_USER: app POSTGRES_PASSWORD: dev POSTGRES_DB: myapp volumes: - pg-data:/var/lib/postgresql/data ports: ["5432:5432"] redis: image: redis:7 volumes: - redis-data:/data volumes: pg-data: redis-data: node-modules: ``` `Dockerfile`: ```dockerfile FROM node:20-slim RUN apt update && apt install -y python3 build-essential libpq-dev \ ffmpeg imagemagick WORKDIR /app COPY package*.json ./ RUN npm ci COPY . . CMD ["npm", "run", "dev"] ``` ```bash docker compose up ``` **优点**: - 跨 OS 完全一致(Mac / Linux / Windows 同结果) - 系统依赖 (ffmpeg 等) 在 Dockerfile,新人 onboarding `docker compose up` 完事 - 接近生产环境 - 多项目隔离(每个项目自己一组 service) **缺点**: - 启动慢(image build 几分钟;首次 download image 几 GB) - IDE 集成稍复杂(要让 IDE 知道 venv 在容器里) - Mac / Windows 上 docker desktop 占内存(默认 4-8 GB) - 文件系统 bind mount 在 macOS 上有性能损失(npm install / build 慢 3-5x) ## 我的选择矩阵 | 场景 | 推荐 | |---|---| | 个人项目 + Linux 桌面 | mise + direnv | | 个人项目 + Mac/Win | mise + Docker for DB only | | 团队多人多 OS | docker compose | | 复杂多服务(5+ container) | docker compose(基本必须) | | 需要 IDE 强 debug | mise(host 跑 Python/Node 直 debug) | | onboarding 时间宝贵 | docker compose | | CI 跑测试 | docker compose 镜像 + GitHub Action | ## 混合方案(我个人用) 主要业务代码跑 host(mise 装好 Node/Python),仅 DB / Redis / LocalStack 等 service 跑 docker。 `docker-compose.dev.yml`: ```yaml services: db: image: postgres:16 environment: POSTGRES_USER: app POSTGRES_PASSWORD: dev POSTGRES_DB: myapp ports: ["127.0.0.1:5432:5432"] volumes: [pg-data:/var/lib/postgresql/data] redis: image: redis:7-alpine ports: ["127.0.0.1:6379:6379"] volumes: pg-data: ``` ```bash docker compose -f docker-compose.dev.yml up -d mise install direnv allow npm run dev # 跑 host ``` **两者优点结合**:IDE debug 顺滑 + 数据库环境一致。 ## 痛点解决 ### Mac docker 文件慢 ```yaml volumes: - .:/app:delegated # macOS 优化(让容器写少同步给 host) ``` 或用 `mutagen-compose`:rsync 风格双向同步代替 bind mount, 性能 10x。 ### node_modules 不同步 ```yaml volumes: - .:/app - /app/node_modules # named volume 让容器内的 node_modules 不被 host 空目录覆盖 ``` ### 多项目端口冲突 每个项目用不同端口: ```yaml ports: ["127.0.0.1:5433:5432"] # 项目 A ports: ["127.0.0.1:5434:5432"] # 项目 B ``` 或者用 Traefik 共享一个反代(前面有篇)。 ## 完全云端 dev:Codespaces / Gitpod ```json // .devcontainer/devcontainer.json { "image": "mcr.microsoft.com/devcontainers/python:3.12", "features": { "ghcr.io/devcontainers/features/node:1": { "version": "20" }, "ghcr.io/devcontainers/features/docker-in-docker:2": {} }, "postCreateCommand": "uv sync" } ``` GitHub Codespaces 一键起一个云端 VSCode(已配齐所有依赖)。新人 30 秒 能 contribute,不需要本地装任何东西。 ## 效果 我们团队(5 人,Mac/Linux 混用)现在用"混合方案": - 新人 onboarding 从 2 天 → 30 分钟(mise install + docker compose up) - IDE 调试体验保持原生 - 数据库版本统一(postgres 16)跨所有人无差异 - CI 用同样 docker image 测试 → 部署 - "我机器能跑" 类问题归零 ## 踩过的坑 1. **mise 装的 Python 链接 libssl 失败**:openssl 升级后老 Python 坏。`mise reshim` 重新链接,或 `mise uninstall python && mise install`。 2. **docker compose volume 跨用户权限**:root 跑了 docker,host 非 root 用户读不了 volume 文件。改 docker-compose `user: "1000:1000"` 或 chown。 3. **`.env` 进 git**:永远 `.env.example` 进 git,`.env` 进 gitignore。 4. **依赖装到全局**:用 mise 跑 `npm install -g` 会装到 mise 管的 Node 里。换 Node 版本就丢。改 `npx some-tool` 或本地 dev dep。 5. **VSCode 用错 venv**:`.vscode/settings.json` 指明 `"python.defaultInterpreterPath": "${workspaceFolder}/.venv/bin/python"`。
## 起因 每个项目都有一堆需要跑的命令:测试、lint、构建、迁移、部署。 用 npm script 写 → 只适合 Node 项目;写在 README → 复制粘贴慢; 用 Makefile → tab 缩进诡异、`.PHONY` 麻烦、shell quoting 各种坑、 Windows 上 make 不一定有。 `just` 是 Rust 写的命令运行器,专为"项目级 task runner"设计, 没有 make 的历史包袱。 ## 安装 ```bash # macOS brew install just # Debian / Ubuntu sudo apt install just # Ubuntu 24.04+ # 或下载二进制 curl --proto '=https' --tlsv1.2 -sSf https://just.systems/install.sh \ | bash -s -- --to /usr/local/bin # Windows scoop install just just --version ``` ## justfile 例 放在项目根目录 `justfile`(无后缀): ```just # 默认 recipe(just 不带参数时跑) default: list # 列所有 recipe list: @just --list # 测试 test: pytest -x # 跑某个测试 test-one TEST: pytest -x -v {{TEST}} # 启动开发服务器 dev: uvicorn app.main:app --reload # Lint + format lint: ruff check . mypy src/ fmt: ruff format . ruff check --fix . # 跑数据库迁移 migrate: alembic upgrade head migrate-create MESSAGE: alembic revision --autogenerate -m "{{MESSAGE}}" # 多步组合 ci: lint test @echo "✅ all green" # 部署到生产 deploy ENV='staging': @echo "Deploying to {{ENV}}" ./scripts/deploy.sh {{ENV}} # 用 docker 构建镜像 build VERSION: docker build -t myapp:{{VERSION}} . docker tag myapp:{{VERSION}} myapp:latest # 清理 clean: rm -rf dist build .pytest_cache .ruff_cache __pycache__ ``` ```bash just # 同 `just default` just test just test-one tests/test_user.py::test_create just migrate-create "add email field" just ci just deploy prod ``` ## 与 Makefile 对比 ```makefile # Makefile .PHONY: test lint deploy test: pytest -x lint: ruff check . mypy src/ deploy: @echo "Deploying" ./scripts/deploy.sh $(ENV) ``` ```bash make test make deploy ENV=prod # 注意要写 ENV=prod,just 是位置参数 deploy prod ``` just 的胜出: - 不需要 .PHONY(recipe 永远跑) - 不需要 tab(空格即可,4 / 2 都行) - 命令默认 echo(不需要 `@`) - 多行字符串 / heredoc 简单 - 参数语法直接(位置 + 默认值) - 跨平台(Windows / Linux / Mac 一致) ## 高级特性 ### 多 shell ```just # 默认 sh -cu hello: echo "from sh" # 用 bash [shell: bash] fancy: echo $BASH_VERSION arr=(1 2 3); echo "${arr[@]}" # 用 python [shell: python] math: print(2 ** 10) # 用 powershell [shell: pwsh -c] greet: Write-Host "from PowerShell" ``` ### 跨平台条件 ```just [unix] clean: rm -rf build/ [windows] clean: Remove-Item -Recurse -Force build/ ``` ### 依赖 ```just build: lint test cargo build --release release VERSION: build git tag v{{VERSION}} git push origin v{{VERSION}} ``` `just release 1.2.3` → 自动先跑 build → build 先跑 lint + test。 ### 变量 ```just version := `git describe --tags --always` docker_repo := "ghcr.io/myorg/myapp" build: docker build -t {{docker_repo}}:{{version}} . docker push {{docker_repo}}:{{version}} ``` ### env 读 .env ```just set dotenv-load dev: echo "Connecting to $DATABASE_URL" ``` `set dotenv-load` 让 just 自动读 `.env`。 ### 分组 ```just [group('test')] test-unit: pytest tests/unit [group('test')] test-e2e: playwright test [group('deploy')] deploy-staging: ./deploy.sh staging ``` ```bash just --list --groups # test: # test-unit # test-e2e # deploy: # deploy-staging ``` ### private recipes ```just [private] _setup: pip install -r requirements.txt dev: _setup flask run ``` `_` 开头 = private(不出现在 `just --list`),但能被依赖。 ## 嵌套 justfile monorepo 里每个子目录一个 justfile: ``` myapp/ ├── justfile # 顶层(全局 recipe) ├── backend/ │ └── justfile # backend 特定 └── frontend/ └── justfile # frontend 特定 ``` 顶层调用子项目: ```just test-all: just backend/test just frontend/test ``` 或者用 `just --justfile` 指定: ```bash just --justfile backend/justfile test just -f backend/justfile test # 简写 ``` ## tab 补全 ```bash # bash just --completions bash > /usr/local/etc/bash_completion.d/just # zsh just --completions zsh > ~/.zsh/completions/_just # fish just --completions fish > ~/.config/fish/completions/just.fish ``` 之后 tab 补全所有 recipe 名 + 参数。 ## 与 CI GitHub Actions: ```yaml - uses: extractions/setup-just@v2 - run: just ci ``` ## 效果 - 新人 clone 仓库 → `just --list` 一眼看到所有可用命令 - README 不再充满"how to run tests / lint / deploy",一句"see justfile" - Makefile 时代的 tab/space 翻车问题归零 - 跨平台一致,Windows 同事不再单独写 .ps1 脚本 ## 踩过的坑 1. **recipe 名带 `_`、`-`**:just 都支持,但 shell tab 补全有时不识别。 recipe 名优先短 + 不带 `_`。 2. **不在 git 根**:默认 just 在当前目录找 justfile。子目录里跑根的 recipe 需要 `cd` 上去或 `just -f ../justfile recipe`。`just` 也支持 `.justfile`(隐藏)让 ls 不见。 3. **shell 退出码处理**:单条 recipe 里某命令失败默认整个 recipe 停。 要继续:`-` 前缀(同 make): ```just deploy: -kill -9 $(pidof oldservice) systemctl start newservice ``` 4. **变量 `$var` vs `{{var}}`**:`$var` 是 shell 变量(运行时展开), `{{var}}` 是 just 变量(justfile 解析时展开)。混了会报错。 5. **CI 装 just 加二进制 cache**:每次 CI build 都装一次浪费时间, `extractions/setup-just` action 自带 cache。
## 起因 `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 自动装。
## 起因 打开项目 X 的工作流总是:开 tmux session → split 3 panel → 各跑 `vim .` / `npm run dev` / `tail -f log`。 切到项目 Y 又是另一套布局。每次手动布几次烦。 `tmuxinator`(YAML 定义 tmux layout)和 `zellij`(自带 layout 系统) 让"项目 → 预设布局" 一行命令搞定。 ## tmuxinator(tmux 上层) ```bash gem install tmuxinator # 或:brew install tmuxinator tmuxinator new myapp # 打开 YAML 编辑器 ``` ```yaml # ~/.config/tmuxinator/myapp.yml name: myapp root: ~/projects/myapp windows: - editor: layout: main-vertical panes: - nvim . - git status - tail -f log/dev.log - server: panes: - npm run dev - npm run watch:css - db: panes: - psql -d myapp - shell: panes: - clear ``` 启动: ```bash tmuxinator start myapp # 或 mux start myapp(短 alias) ``` 自动: 1. 开新 tmux session `myapp` 2. 第 1 个 window 'editor':左侧 nvim,右侧上 git status / 下 log tail 3. 第 2 个 window 'server':上下分 panel 跑 dev + watch 4. 第 3 个 window 'db':psql 5. 第 4 个 window 'shell':空 shell 切 window:tmux prefix + 数字键。整个项目立刻 ready。 ### 高级:pre/post hook ```yaml pre: docker compose -f docker-compose.dev.yml up -d post_window_create: sleep 1 windows: - editor: panes: - nvim . ``` `pre` 在启动 tmux 前跑(启 DB / 装依赖)。`post_window_create` 每个 window 创建后 sleep。 ### 多 session 同时 ```bash tmuxinator start myapp # tmux session "myapp" 起来 tmuxinator start client-x # 另一个项目 # tmux session "client-x" 同时存在 ``` `tmux ls` 看所有 session;`tmux a -t myapp` 进任意一个。 ## zellij(tmux 替代品,layout 内置) ```bash brew install zellij cargo install zellij ``` ```bash zellij --layout default # 默认 layout 启动 ``` zellij 跟 tmux 比: - modern UI(status bar + tips 自动显示) - 鼠标支持开箱即用 - layout 是 KDL 格式(更结构化) - 内置插件系统(Wasm) - 性能(Rust) ### KDL layout `~/.config/zellij/layouts/myapp.kdl`: ``` layout { cwd "/Users/me/projects/myapp" tab name="editor" focus=true { pane command="nvim" { args "." } pane split_direction="vertical" size="40%" { pane command="git" { args "status" } pane command="bash" { args "-c" "tail -f log/dev.log" } } } tab name="server" { pane command="npm" { args "run" "dev" } pane command="npm" { args "run" "watch:css" } } tab name="db" { pane command="psql" { args "-d" "myapp" } } } ``` 启动: ```bash zellij --layout myapp # 或: zellij -s myapp -l myapp ``` `-s myapp` session 名;`-l myapp` 用这个 layout。 ### resurrect (持久化) ```bash zellij list-sessions zellij attach myapp # attach 重连,状态保留 zellij kill-session myapp ``` 机器重启后 session 消失(跟 tmux 一样)。 需要持久化用 tmux + tmux-resurrect 插件。 ## 我用的 workflow 混用: - daily 默认 shell 用 tmux + tmuxinator(成熟、稳) - 新项目尝试 zellij(更好看 / 鼠标好) 每个项目根目录有 `.tmuxinator.yml`: ```bash cd myapp mux . # 在当前目录跑 tmuxinator 配置 ``` 或者 cd 时自动启 tmux: ```bash # ~/.zshrc function cd() { builtin cd "$@" if [[ -f .tmuxinator.yml && -z "$TMUX" ]]; then local name=$(basename "$PWD") tmuxinator start "$name" -f .tmuxinator.yml -n "$name" 2>/dev/null fi } ``` cd 进项目自动开预设布局。 ## 与 IDE 的关系 VSCode 已经能 multi-panel + integrated terminal。 为什么还要 tmux/zellij? - **远程开发**:SSH 上 tmux session 断线不丢 - **CLI 主导**:vim/neovim/helix 用户原生体验 - **多 session**:可以跑跑 backend / frontend / docs 各一个 tmux 互不干扰 - **持久 background**:长跑任务用 tmux 后台跑,不占 IDE IDE 用户也常用 tmux 跑 background server,VSCode 只 edit + git。 ## 团队共享 layout `.tmuxinator.yml` 进 git → 团队成员 `mux .` 起来同样 layout。 对 onboarding 价值极大:新人不需要"我应该开哪几个 terminal"。 注意:layout 文件里硬编码用户 path(`/home/me/...`)要避免, 用 `${PWD}` 或 `.` 相对路径。 ## 效果 - 项目切换从"开 5 个 terminal 各布置 1 分钟" → 1 秒 - 远程 SSH 断线后 reattach 看到所有状态保留 - 团队新人 onboarding 直接 `mux .` 起来全套 - 一台机器 daily 同时挂 4-5 个项目 session,按需 attach 切换 ## 踩过的坑 1. **tmuxinator 不更新某个 pane 命令**:YAML 改了但 session 已经 起来 → 改动不生效。`tmuxinator kill myapp` 再 `start`。 2. **windows 排序**:YAML 列出顺序就是 tab 顺序。习惯把 editor 放第 1(默认 attach 进的)。 3. **session 同名只能一个**:第二次 start 提示已存在。`mux start myapp` 会 attach 已有的(feature);要重启加 `--no-attach` + kill 旧的。 4. **layout 文件 path 跨机器不一致**:用 `~/projects/...` 不要硬 `/home/specific-user/`。 5. **tmux 自动 attach 把别人踢出去**:多人共享 server 上同一 tmux 不友好。建议 `tmux a -t myapp -d` (`-d` detach 别人) 或者 read-only attach(`-r`)。
Neovim 0.10+ 已经是一流的 IDE 平台:内置 LSP 客户端、Treesitter 语法、 Lua 配置。Lazy.nvim 是事实标准的插件管理器,懒加载 + 锁版本。 下面给一个 < 100 行 Lua、能开箱即用的最小配置。 ## 1. 装 Neovim 0.10+ ```bash # Ubuntu 24.04+ apt 自带;老版本用 ppa / appimage / brew sudo apt install -y neovim nvim --version # 确认 >= 0.10 ``` ## 2. 目录结构 ``` ~/.config/nvim/ ├── init.lua └── lua/ ├── options.lua ├── keymaps.lua ├── plugins.lua └── lsp.lua ``` ## 3. `init.lua` ```lua -- 一键 bootstrap lazy.nvim local lazypath = vim.fn.stdpath('data') .. '/lazy/lazy.nvim' if not vim.uv.fs_stat(lazypath) then vim.fn.system({ 'git', 'clone', '--filter=blob:none', 'https://github.com/folke/lazy.nvim.git', '--branch=stable', lazypath, }) end vim.opt.rtp:prepend(lazypath) require('options') require('keymaps') require('lazy').setup(require('plugins')) require('lsp') ``` ## 4. `lua/options.lua` ```lua local o = vim.opt o.number = true o.relativenumber = true o.signcolumn = 'yes' -- 给 LSP / git 留位置 o.expandtab = true o.shiftwidth = 2 o.tabstop = 2 o.smartindent = true o.wrap = false o.ignorecase = true o.smartcase = true o.termguicolors = true o.cursorline = true o.scrolloff = 8 o.updatetime = 250 -- 让 hover / diag 更快 o.clipboard = 'unnamedplus' -- 跟系统剪贴板互通 o.splitright = true o.splitbelow = true o.undofile = true -- 跨 session undo o.mouse = 'a' ``` ## 5. `lua/keymaps.lua` ```lua vim.g.mapleader = ' ' vim.g.maplocalleader = ' ' local map = vim.keymap.set map('n', '<leader>w', '<cmd>w<cr>', { desc = 'Save' }) map('n', '<leader>q', '<cmd>q<cr>', { desc = 'Quit' }) map('n', '<esc>', '<cmd>nohlsearch<cr>') map('n', '<C-h>', '<C-w>h') map('n', '<C-j>', '<C-w>j') map('n', '<C-k>', '<C-w>k') map('n', '<C-l>', '<C-w>l') map('v', '<', '<gv') -- 缩进后保持选中 map('v', '>', '>gv') ``` ## 6. `lua/plugins.lua` ```lua return { -- 配色 { 'catppuccin/nvim', name = 'catppuccin', config = function() vim.cmd.colorscheme('catppuccin-mocha') end }, -- 文件树 { 'nvim-tree/nvim-tree.lua', dependencies = { 'nvim-tree/nvim-web-devicons' }, keys = { { '<leader>e', '<cmd>NvimTreeToggle<cr>', desc = 'File tree' } }, config = function() require('nvim-tree').setup() end, }, -- 模糊搜索 { 'nvim-telescope/telescope.nvim', dependencies = { 'nvim-lua/plenary.nvim' }, keys = { { '<leader>f', '<cmd>Telescope find_files<cr>', desc = 'Find files' }, { '<leader>g', '<cmd>Telescope live_grep<cr>', desc = 'Live grep' }, { '<leader>b', '<cmd>Telescope buffers<cr>', desc = 'Buffers' }, }, }, -- 语法高亮(Treesitter) { 'nvim-treesitter/nvim-treesitter', build = ':TSUpdate', config = function() require('nvim-treesitter.configs').setup({ ensure_installed = { 'lua', 'python', 'javascript', 'typescript', 'tsx', 'go', 'rust', 'bash', 'markdown', 'json', 'yaml', 'html', 'css' }, highlight = { enable = true }, indent = { enable = true }, }) end, }, -- Git 集成 { 'lewis6991/gitsigns.nvim', config = function() require('gitsigns').setup() end }, -- LSP 安装管理 { 'williamboman/mason.nvim', config = true }, { 'williamboman/mason-lspconfig.nvim', dependencies = { 'mason.nvim', 'neovim/nvim-lspconfig' }, config = function() require('mason-lspconfig').setup({ ensure_installed = { 'pyright', 'ts_ls', 'lua_ls', 'gopls', 'rust_analyzer' }, }) end, }, -- 补全 { 'hrsh7th/nvim-cmp', dependencies = { 'hrsh7th/cmp-nvim-lsp', 'hrsh7th/cmp-buffer', 'hrsh7th/cmp-path', 'L3MON4D3/LuaSnip', 'saadparwaiz1/cmp_luasnip', }, config = function() local cmp = require('cmp') cmp.setup({ snippet = { expand = function(args) require('luasnip').lsp_expand(args.body) end }, mapping = cmp.mapping.preset.insert({ ['<C-Space>'] = cmp.mapping.complete(), ['<CR>'] = cmp.mapping.confirm({ select = true }), ['<Tab>'] = cmp.mapping.select_next_item(), ['<S-Tab>'] = cmp.mapping.select_prev_item(), }), sources = cmp.config.sources({ { name = 'nvim_lsp' }, { name = 'luasnip' }, { name = 'buffer' }, { name = 'path' }, }), }) end, }, -- 状态栏 { 'nvim-lualine/lualine.nvim', config = function() require('lualine').setup() end }, } ``` ## 7. `lua/lsp.lua` ```lua local lspconfig = require('lspconfig') local caps = require('cmp_nvim_lsp').default_capabilities() local on_attach = function(_, buf) local map = function(keys, fn, desc) vim.keymap.set('n', keys, fn, { buffer = buf, desc = desc }) end map('gd', vim.lsp.buf.definition, 'Go to definition') map('gr', vim.lsp.buf.references, 'References') map('K', vim.lsp.buf.hover, 'Hover') map('<leader>rn', vim.lsp.buf.rename, 'Rename') map('<leader>ca', vim.lsp.buf.code_action, 'Code action') map('<leader>d', vim.diagnostic.open_float, 'Show diagnostic') end for _, server in ipairs({ 'pyright', 'ts_ls', 'gopls', 'rust_analyzer', 'lua_ls' }) do lspconfig[server].setup({ on_attach = on_attach, capabilities = caps, }) end -- 保存时自动格式化(如果 LSP 支持) vim.api.nvim_create_autocmd('BufWritePre', { callback = function() vim.lsp.buf.format({ async = false }) end, }) ``` ## 8. 第一次启动 ```bash nvim # Lazy 自动 clone 插件,等几秒 # :Mason ← 进去能看到 LSP 安装状态 ``` ## 9. 升级 / 锁版本 ```vim :Lazy update " 升级所有 :Lazy sync " 装新加的、删旧的 :Lazy log " 看更新历史 ``` Lazy 会在 `~/.config/nvim/lazy-lock.json` 记录每个插件的 commit hash。 进 git 让别的机器同步到完全一样的版本。 ## 10. 排错 ```vim :checkhealth " 列出每个组件的健康状态 " 缺什么(python3 / npm / node / fd / ripgrep)一目了然 :LspInfo " 看当前 buffer 的 LSP 状态 ``` ## 踩过的坑 - 不装 `ripgrep` → Telescope live_grep 不能用。`sudo apt install ripgrep`。 - LSP server 装了但没启动:通常因为 root marker 找不到(pyright 找 `pyproject.toml` / `setup.py`)。在项目根目录打开 Neovim。 - 自动 format on save 把你刚写的中文注释格式化乱:把不可信的 formatter 关掉,或者 `:noa w` 跳过 autocmd。 - 升级 Treesitter parser 时 build 失败:缺 gcc / make。装编译工具链 `sudo apt install build-essential`。
## 起因 代码搜索 / 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`。
## 起因 我在 macOS 用 zsh + oh-my-zsh、Linux 服务器用 bash、Windows 偶尔用 PowerShell。每个 shell 一套提示符配置:颜色、git branch 显示、Python venv 提示、kubectl context… 三套配置一改全乱。 `starship` 是 Rust 写的跨 shell 提示符生成器:一份 TOML 配置文件, 所有 shell 都能用同一份。 ## 安装 ```bash # 一行装 curl -sS https://starship.rs/install.sh | sh # 或包管理器 brew install starship sudo apt install starship # Debian 12+ / Ubuntu 24.04+ scoop install starship # Windows starship --version ``` ## 接到 shell ```bash # bash: ~/.bashrc 末尾 eval "$(starship init bash)" # zsh: ~/.zshrc 末尾 eval "$(starship init zsh)" # fish: ~/.config/fish/config.fish 末尾 starship init fish | source # PowerShell: $PROFILE Invoke-Expression (&starship init powershell) ``` 重启 shell,提示符立刻变好看。 ## 默认行为 启动后默认显示: - 当前目录(智能截断 home / 项目根) - Git 分支 + 状态(修改 / 新文件 / 待 push) - 当前语言版本(在该项目目录自动显示 Python/Node/Go/Rust 版本) - exit code(非 0 时红色显示) - 命令耗时(> 2s 自动显示) 不用任何配置就比 default 强 10 倍。 ## 个性化 ~/.config/starship.toml 我用的(基于官方 nerd-font symbol preset 简化): ```toml format = """ $directory\ $git_branch\ $git_status\ $python\ $nodejs\ $golang\ $rust\ $kubernetes\ $cmd_duration\ $line_break\ $character\ """ [character] success_symbol = "[➜](bold green)" error_symbol = "[✗](bold red)" vimcmd_symbol = "[V](bold yellow)" [directory] truncation_length = 3 # 只显示最后 3 段 truncate_to_repo = true # 进 repo 后只显示从 repo 根的相对路径 style = "bold cyan" [git_branch] symbol = " " style = "purple" [git_status] ahead = "⇡${count}" behind = "⇣${count}" diverged = "⇕⇡${ahead_count}⇣${behind_count}" untracked = "?${count}" modified = "✱${count}" staged = "+${count}" deleted = "✘${count}" style = "yellow" [cmd_duration] min_time = 2_000 format = "took [$duration]($style) " style = "yellow" [python] symbol = " " format = '[${symbol}${pyenv_prefix}(${version} )(\($virtualenv\) )]($style)' style = "bold yellow" [nodejs] symbol = " " format = "[${symbol}${version}]($style) " style = "bold green" [kubernetes] disabled = false format = '[$symbol$context( \($namespace\))]($style) ' style = "bold blue" # 关一些少用的模块加速 [aws] disabled = true [gcloud] disabled = true [package] disabled = true ``` ## 看效果 ``` ~/projects/myapp on feature/auth ✱2 ?1 3.12.0 (.venv) prod default took 4.5s ➜ ``` ## 让符号显示完整 starship 用了大量 nerd-font 符号(、、、)。终端必须装 Nerd Font 字体才能正常显示: ```bash # macOS brew tap homebrew/cask-fonts brew install --cask font-jetbrains-mono-nerd-font # Linux mkdir -p ~/.local/share/fonts cd ~/.local/share/fonts curl -fLO https://github.com/ryanoasis/nerd-fonts/releases/latest/download/JetBrainsMono.zip unzip JetBrainsMono.zip fc-cache -fv # 然后终端 settings 字体改成 'JetBrainsMono Nerd Font' ``` 不装 Nerd Font 也能用——把 `symbol` 改成普通字符或留空。 ## 跨机器同步配置 把 `~/.config/starship.toml` 放进 dotfiles repo: ```bash cd ~/dotfiles ln -sf $(pwd)/starship.toml ~/.config/starship.toml git add starship.toml git commit -m 'starship config' ``` 新机器: ```bash git clone https://github.com/me/dotfiles ln -sf $(pwd)/dotfiles/starship.toml ~/.config/starship.toml ``` 一次配置,所有机器一致。 ## 性能 starship 单次 prompt 渲染 < 30ms(Rust + 并行模块)。 慢命令后 prompt 也立即响应。 ```bash # 看哪个模块拖时间 STARSHIP_LOG=debug starship prompt 2>&1 | grep -i 'time' ``` 发现慢模块 → 关掉或加 `disabled = true`。 ## 与 oh-my-zsh / Spaceship 等对比 | 工具 | 速度 | 跨 shell | 配置语法 | |---|---|---|---| | **starship** | 极快(Rust) | ✅ 全支持 | TOML | | oh-my-zsh themes | 慢(zsh 脚本) | 仅 zsh | shell | | Spaceship | 中(zsh script) | 仅 zsh | zsh vars | | pure | 极快 | 仅 zsh | minimal | | p10k | 极快(C) | 仅 zsh | wizard | 如果你只用 zsh,p10k 配置体验更好;多 shell 选 starship。 ## 效果 - 三台机器 + 两个 shell 同步配置,零差异 - 提示符里直接看到 git branch / venv / Node 版本,少跑 N 个 `git status` - 命令耗时显示让我知道哪些命令该 background 跑 - 同事看见后被安利的成功率约 50% ## 踩过的坑 1. **服务器 SSH 上没 Nerd Font** → 各种"豆腐块"。`starship preset plain-text-symbols` 切到纯文本符号 preset。 2. **WSL 启动慢**:默认每次 prompt 都查 git。`scan_timeout = 10` 限制超时;大仓库 disable git 模块只保留 branch。 3. **tmux 里颜色错**:tmux 设 `set -g default-terminal "tmux-256color"` + `set -ga terminal-overrides ',xterm-256color:Tc'` 启用真彩色。 4. **PowerShell 上 modulator 不显示**:Windows Terminal 字体必须设为 `CaskaydiaCove NF` 之类的 Nerd Font 变体。系统全局字体不算。 5. **zoxide / direnv 等其它工具的钩子顺序**:starship init 应在最后, 否则它的 hook 被覆盖。
## 起因 每个项目不同 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 但有时困惑。
## 起因 跨大洋下载 50 GB 数据集,到一半网断了。重新跑 = 重新下完整 50GB。 HTTP 标准里有 Range header(断点续传),客户端工具不全自动用。 ## curl:`-C -` 自动续传 ```bash curl -C - -O https://example.com/big.tar.gz # -C - 让 curl 检测本地文件大小 + Range header 续传 # -O 用 server 提供的文件名保存 ``` 如果服务端不支持 Range: ``` curl: (33) HTTP server doesn't seem to support byte ranges. Cannot resume. ``` 绝大多数现代 HTTP server / CDN 都支持。 ### 加速 / 重试 ```bash curl -C - -O \ --retry 5 \ --retry-delay 10 \ --retry-max-time 3600 \ --limit-rate 10M \ -L \ https://example.com/big.tar.gz ``` - `--retry 5`:网络错误自动重试 5 次 - `--retry-delay 10`:间 10 秒 - `--retry-max-time 3600`:1 小时内重试 - `--limit-rate 10M`:限速 10MB/s(晚上无人时占满带宽,白天礼貌点) - `-L`:跟 redirect ### 校验 ```bash curl -O https://example.com/big.tar.gz curl -O https://example.com/big.tar.gz.sha256 sha256sum -c big.tar.gz.sha256 ``` 下完一定要校验,网络可能让文件损坏。 ## wget:默认更友好 ```bash wget -c https://example.com/big.tar.gz # -c 续传 ``` 加重试 / 限速: ```bash wget -c \ --tries=10 \ --retry-connrefused \ --waitretry=30 \ --limit-rate=10m \ --user-agent='Mozilla/5.0' \ https://example.com/big.tar.gz ``` 镜像整个目录: ```bash wget -r -np -nH --cut-dirs=2 -R 'index.html*' \ https://example.com/files/2024/ ``` - `-r` 递归 - `-np` 不向上跳目录 - `-nH` 不创建 host name 目录 - `--cut-dirs=2` 砍掉前 2 级路径 - `-R 'index.html*'` 排除目录列表 HTML ## aria2c:多连接 + magnet + 元数据 vacuum ```bash sudo apt install aria2 brew install aria2 aria2c -x 16 -s 16 https://example.com/big.tar.gz # -x 16: 同一服务器最多 16 个连接 # -s 16: 同一文件用 16 个 segment 并行下 ``` 如果服务端支持 Range,多个 TCP 连接并行下不同字节范围, 吃满带宽(单 TCP 流由于 congestion control 经常吃不满)。 速度通常 3-5x 单连接。 更激进: ```bash aria2c -x 16 -s 16 -j 4 -k 1M --file-allocation=falloc \ --max-tries=10 --retry-wait=30 \ https://example.com/big.tar.gz ``` - `-j 4`: 同时下 4 个文件 - `-k 1M`: 每 segment 至少 1MB - `--file-allocation=falloc`: ext4/xfs 上预分配文件(防碎片) ### aria2c 支持的协议 ```bash # HTTPS aria2c https://... # FTP aria2c ftp://user:pass@host/path # BitTorrent aria2c some.torrent # Magnet link aria2c 'magnet:?xt=urn:btih:...' # metalink (含多个 mirror) aria2c file.metalink ``` 下大文件 magnet 时 aria2c 远比传统 BT 客户端轻量。 ### 多 mirror ```bash aria2c \ https://us.mirror.example.com/big.tar.gz \ https://eu.mirror.example.com/big.tar.gz \ https://asia.mirror.example.com/big.tar.gz ``` 3 个 mirror 同时下,每个用不同 segment。 带宽利用最大化。 ### 续传 aria2 默认自动续传: ```bash aria2c https://example.com/big.tar.gz # 中断 → 再跑同命令,从断点继续 ``` 断点信息在 `.aria2` 控制文件里。 ## rsync:对增量传输最优 ```bash rsync -avz --progress --partial \ user@server:/path/to/big.tar.gz \ ./big.tar.gz ``` - `--partial`:保留部分传输的文件供下次续传 - `--progress`:显示进度条 - rsync 还能传整个目录树 + 只传差异,比 scp 强 10x 更激进的限速 / 加密: ```bash rsync -avz --bwlimit=10000 \ -e 'ssh -c [email protected]' \ src/ dst:/path/ ``` `--bwlimit=10000` = 10 MB/s。aes128-gcm 加密快(用现代 CPU 的 AES-NI), 比默认 ChaCha20 快 2-3 倍。 ## 边下边校验:sha256 + tee ```bash curl -L https://example.com/big.tar.gz | tee big.tar.gz | sha256sum - # 边下载 + 边写文件 + 边算 hash # 输出 hash 后对比 expected ``` 适合一次性下 + 校验场景。 ## 限速 + 后台 + 续传完整组合 ```bash # 启动一个后台下载 nohup aria2c -x 8 -s 8 --max-overall-download-limit=20M \ --enable-rpc=true --rpc-listen-all=true --rpc-secret=mysecret \ -d ~/Downloads -o big.tar.gz \ https://example.com/big.tar.gz \ > ~/Downloads/aria2.log 2>&1 & # 通过 RPC 控制(暂停 / 看进度) curl -s -d '{"jsonrpc":"2.0","method":"aria2.tellActive","id":"x","params":["token:mysecret"]}' \ http://localhost:6800/jsonrpc | jq ``` aria2 RPC 让你用 web UI(aria2-webui)控制。Synology / OpenMediaVault 等 NAS 都有 aria2 GUI 包。 ## 服务端:让你的文件支持 Range nginx: ```nginx location /downloads/ { alias /var/www/downloads/; # 默认 nginx 静态文件就支持 Range,不需要配 } ``` 应用程序自己 stream 大文件时(如 Django/FastAPI/Flask),要手动处理 Range header。或者直接让 nginx 服务静态文件(最快)。 ## 反过来:让别人能续传我的下载 ```python # FastAPI 例 from fastapi import FastAPI, Request, Response from fastapi.responses import StreamingResponse, FileResponse import os @app.get('/download/{filename}') def download(filename: str, request: Request): path = f'/var/files/{filename}' # FileResponse 自动处理 Range return FileResponse(path, filename=filename) ``` `FileResponse` / nginx X-Sendfile 都支持 Range。 自己写 stream generator 要: - 看 `Range: bytes=100-200` header - 返回 206 Partial Content + `Content-Range` ## 效果 跨太平洋下 50GB(实测): | 工具 | 时间 | 带宽利用 | 注意 | |---|---|---|---| | curl | 6h | 25% | 单 TCP | | wget -c | 6h | 25% | 同上 | | aria2c -x 8 | 1.5h | 95% | 8 并行 | | aria2c + 3 mirror | 45min | 100% | 3 mirror × 8 段 | | rsync | 7h | 22% | SSH 加密开销 | 中断续传都 OK,但 aria2c 显著快。 ## 踩过的坑 1. **服务端不支持 Range** → curl `-C -` 直接报错。 测试:`curl -I -H "Range: bytes=0-100" https://...`, 返回 `206 Partial Content` = 支持。 2. **proxy / CDN 不传 Range** → 一些反代默认 buffer,吞 Range header。 nginx 加 `proxy_set_header Range $http_range;`。 3. **aria2c segment 上限**:每个 segment 一个 TCP 连接,过多反而 慢(拥塞 + 服务端 ratelimit)。8-16 是甜点。 4. **文件名含特殊字符 / 编码**:URL encode 不完整时下错文件。 `curl -o "myname.tar.gz" -L url` 显式指定本地名。 5. **磁盘满 silent fail**:下载到 99% 磁盘满 → 文件损坏。 `df -h` 提前确认空间。