2026-06-16
今日主题
- macOS 文件默认打开方式管理
- 终端文件路径点击行为
- Homebrew 分发的软件类型
- 开发库的文件形态与安装约定
- pkg-config 与 .pc 文件机制
- MDM 静默推送软件的追溯方法
- Go vs Java 文件名与包命名差异
- C语言编译四阶段与中间产物
- Go编译过程与C的差异
新增认知
macOS 文件默认打开方式管理
-
duti -x 查扩展名,duti -d 查 UTI:duti 的查询参数极易混淆——-x 接受扩展名(如 json),返回当前默认应用;
-d 接受 UTI(如 public.json),返回该 UTI 的 handler 信息。之前一直用 duti -d json 查扩展名,
UTI 名为 json 的注册项不存在,所以始终返回 no default handler,误以为设置未生效。正确验证方式是用 duti -x 而非 -d。 -
duti -s 无报错不代表写入成功:duti -s 返回 exit 0 只表示命令执行无异常,
不保证 macOS LaunchServices 实际接受了绑定。必须用 duti -x 验证。
应用还需在 Info.plist 的 CFBundleDocumentTypes 中声明对应 UTI 或通配符 *,否则系统拒绝绑定。 -
Shell 双引号内 ~ 不展开:~ 在双引号中保留字面值,不会展开为 HOME 目录。如 ~/path 在双引号内变成字面字符串 ~/path,
正确写法是使用 $HOME 变量或把 ~ 放在引号外。
之前 zshrc 中 idea_bin="~/Applications/..." 因此一直指向不存在的路径。
终端文件路径点击行为
-
iTerm2 点击文件路径打开编辑器是终端行为,非 Claude Code 控制:普通终端模式下,
iTerm2 的智能选择(Smart Selection)功能识别 file.ts:123 模式,Cmd+Click 时调用 macOS 系统默认应用打开,
与 Claude Code 无关。仅全屏 TUI 模式(/tui fullscreen)下 Claude Code 才接管鼠标事件自行处理。
当前环境为 default 模式,/tui 命令可查看当前模式。 -
macOS 文件默认应用由 LaunchServices 决定,按扩展名关联:
系统通过 UTI(Uniform Type Identifier)映射扩展名到应用。应用在 Info.plist 中声明支持的 UTI 和扩展名,
用户可通过 Finder 简介面板或 duti 命令行工具覆盖默认绑定。批量修改需逐扩展名设置,
.html 受 public.html UTI 系统保护限制无法通过 duti 覆盖。
Homebrew 分发的软件类型
-
Homebrew 分发六类软件:Homebrew 不只装 CLI 工具,
还可以装 GUI 应用(Cask)、开发库(.a/.dylib + .h)、语言运行时、后台服务(brew services 管理)、字体。区分方式:
装完有可执行文件是 CLI/运行时;装到 /Applications 是 GUI;只有头文件和链接库是开发库;
可以 brew services start 的是服务。 -
Cask 与 Formula 的本质区别:Formula 会走编译或预编译安装流程,结果是通用 Unix 文件布局;
Cask 专用于 macOS 二进制分发,直接把 .app 拖进 /Applications 或解压预编译包,不做编译。
开发库的文件形态与安装约定
-
开发库 = .a/.so/.dylib + .h:
开发库安装产物是静态库(.a)、动态库(macOS .dylib / Linux .so)和头文件(.h)。它不提供可执行文件,
用途是在编译其他程序时 #include 头文件并 -l 链接,本身没有用来运行的场景。 -
Homebrew 隔离在自己的 prefix:Homebrew 遵循 Unix {bin,lib,include,share,etc} 约定,
但整体放在 /opt/homebrew(Apple Silicon)或 /usr/local(Intel),不污染系统目录 /usr。
编译时需要用 -I/-L 显式指向 Homebrew prefix,或交给 pkg-config 自动解析。
pkg-config 与 .pc 文件机制
-
pkg-config 解决库路径可移植性问题:不同平台库路径不同(/opt/homebrew vs /usr),手写 -I/-L 路径不可移植。
库提供 .pc 文件(记录 prefix、Cflags、Libs),pkg-config 读取后输出正确参数,
编译命令统一为 gcc $(pkg-config --cflags --libs foo) myapp.c,跨平台无需改动。 -
并非所有库都有 .pc 文件:pkg-config 支持是可选的,
库维护者需主动提供 .pc 文件并安装到 PKG_CONFIG_PATH 下(如 /opt/homebrew/lib/pkgconfig/)。
没有 .pc 文件的库只能手动指定路径,libyaml 提供了 yaml-0.1.pc 所以可以直接用 pkg-config 查询。
MDM 静默推送软件的追溯方法
-
MDM 可静默推送安全软件:公司 MDM 策略(如得物通过飞连)可以自动安装 DLP 等安全软件到员工设备,无需用户手动操作。
通过 profiles status 查看 MDM 服务器域名可确认归属,
pkgutil --pkgs 和 pkgutil --pkg-info 可查安装包及时间。 -
安全软件常用混淆文件名:sys5c2gpr0 这种无意义随机名是 DLP 软件的反检测手段,即使文件名无意义,
codesign 仍能暴露真实签名方(这里是 CirrusGate)。 -
追溯未知二进制文件的标准流程:
file 看类型 → codesign -dvvv 看签名方 → otool -L 看链接库 → pkgutil 看安装包 → profiles status 看 MDM 来源 → 查找 LaunchDaemons/Agents 看启动方式,
这五步能完整还原一个未知二进制文件的来源和用途。
Go vs Java 文件名与包命名差异
-
Go文件名与语���语义无关:Go 的编译单元是 package(目录),文件名只是文件系统组织方式,不参与语义,
所以文件名可以是关键字(如 select.go)。Java 则要求文件名必须与 public 类名一致,类名是标识符不能用关键字,因此文件名也被间接限制。 -
Go同目录文件共享符号表:同一 package 下所有 .go 文件编译时符号表合并,跨文件可直接引用顶层声明,无需 import,
也没有跨文件的声明顺序要求。Java 则文件是独立编译单元,互相引用必须 import。 -
Go internal目录有编译器强制语义:Go 的 internal/ 目录下的包只能被其父级模块引用,这是编译器强制的,不是约定。
Java 无此机制,内部包可见性只靠访问修饰符。
C语言编译四阶段与中间产物
-
C编译四阶段产物:预处理(.c→.i,文本展开 #include/#define)→ 编译(.i→.s,生成平台相关汇编,
经过 IR 优化)→ 汇编(.s→.o,ELF 格式目标文件)→ 链接(多个.o/.a→可执行文件,解析符号+重定位)。.s 已是平台相关的,
但作为可观测中间层便于调试编译器优化。 -
ELF目标文件的重定位机制:.o 文件里跨文件的地址引用全是占位符(0x00000000),包含重定位表记录哪些地方需要修正。链接器做符号解析后,
把占位符替换成真实虚拟地址。ld 只决定虚拟地址布局写入 ELF,真正映射到进程内存是运行时 loader 的工作——两者职责不同。 -
静态库.a按需提取.o:.a 是多个 .o 的打包(ar 格式),附带符号索引。链接器用到 .a 时不是整个打进去,
而是查索引找到需要的符号在哪个 .o,只提取那几个 .o。所以静态链接是按需提取,不是全量复制。 -
main符号报错的本质:ld 找不到 main 报 undefined reference 不是因为 ld 特别关心 main,
而是 C 运行时 crt0.o 提供的 _start 引用了 main 符号,符号解析失败就报错,和其他 undefined reference 性质完全一样。
Go编译过程与C的差异
-
Go无预处理阶段:Go 没有宏和 #include,//go:build 是编译器内置的文件级过滤(不是文本替换),
stringer 是 go generate 触发的代码生成工具与编译完全分离。所以 Go 编译只有:
编译(go tool compile)→ 链接(go tool link)两个显式阶段。 -
Go按依赖拓扑顺序编译package:编译不是最终一次链接,而是拓扑排序后逐个编译 package,每个 package 产出 .a。
下游 package 编译时需要读上游的 .a 获取类型信息做类型检查,所以上游必须先编译完。这也是 Go 不需要头文件的原因——类型信息打进了 .a。 -
Go的.a和C的.a用途不同:C 的 .a 只在链接阶段用(提供机器码)。Go 的 .a 在编译阶段就要用(提供导出类型信息做类型检查),
链接阶段再用(提供机器码)。一个文件承担了 C 里头文件+静态库两个角色。 -
Go编译一个package:多文件一次调用:go tool compile 把同一 package 下所有 .go 文件作为整体一次传入,
内存里合并符号表后统一类型检查,各函数独立生成机器码,最终打包成一个 .a。内部可能存在类似 .o 的中间结构但不落盘,
C 里 .o 落盘是因为工具链分成独立程序必须用文件传递。