☰
fish-shell 的 umask 内置命令:文件创建模式掩码的查看、设置与符号模式解析
2026/10/1 16:54:59 网站建设 项目流程
  • CLI
  • 开发工具

【免费下载链接】fish-shell

The user-friendly command line shell.

项目地址:https://gitcode.com/GitHub_Trending/fi/fish-shell
点击查看免费下载

导读

本文围绕 fish-shell 提供的umask内置命令展开,系统讲解文件创建模式掩码(umask)的八进制与符号两种表达方式、-S/--symbolic、-p/--as-command、-h/--help三个选项的用法,并深入其实现:umask在 fish 中是一个由进程实时计算的“电子变量”(electric variable),其设置最终会调用系统umask(2)系统调用;符号模式则由 share/functions/umask.fish 中定义的 fish 函数负责解析与换算。读完本文,你将掌握 umask 的工作原理、fish 特有的变量模型,以及如何通过 tests/checks/umask.fish 中的测试用例验证各种边界行为。

什么是 umask

umask(file creation mode mask,文件创建模式掩码)用于限制新建文件与目录的默认访问权限。当程序(包括 shell 的重定向、touch、mkdir等)创建文件时,系统会从请求的权限中减去掩码中标记的位,从而保证新文件不会意外获得过宽的权限。

fish 官方文档明确指出:本文档描述的是 fish 内置的umask;如果想查看非 fish 版本(如 bash 自带的同名校内建命令)的手册,请使用command man umask(见 doc_src/cmds/umask.rst)。

umask 有两种表达方式:

  • 八进制数字:表示“默认会被移除的权限位”。例如umask 027表示对组移除写权限(2)、对其他用户移除读写执行全部权限(7)。
  • 符号形式:表示“默认会被授予的权限位”,即要保留的权限。

访问权限(读r、写w、执行x,以及u/g/o三类主体)的完整说明参见chmod(1)手册页。

语法与选项

umask [OPTIONS] [MASK]

不带任何参数时,umask以八进制数字形式打印当前文件创建模式掩码。

选项说明
-S/--symbolic以符号形式(如u=rwx,g=rx,o=)而非八进制形式打印掩码
-p/--as-command以可重新作为输入使用的命令形式输出(如umask 0027)
-h/--help显示该命令的使用帮助

在 share/functions/umask.fish 的实现中,这三个选项通过argparse -n umask h/help p/as-command S/symbolic -- $argv解析,其中h/help对应-h/--help,p/as-command对应-p/--as-command,S/symbolic对应-S/--symbolic。

八进制掩码的语义

传入一个八进制数字作为参数时,当前 shell 的 umask 会被设置为该值,此后新建文件与目录默认会移除该掩码指定的权限。例如:

umask 177

177的每一位含义如下(1即二进制001,表示移除执行位x;7即111,表示移除读、写、执行全部权限):

  • 1:属主(u)移除执行权限;
  • 7:属组(g)移除全部权限;
  • 7:其他用户(o)移除全部权限。

文档给出的等价示例是umask 177或umask u=rw:二者都将文件创建掩码设置为“属主可读可写,其他任何用户没有任何权限”。

数值合法性与底层校验

八进制掩码不是任意数字都能生效。在 fish 的 Rust 实现中,umask是一个可写但值由进程实时计算的电子变量(见 src/env/impl/var.rs 中的writable_var(L!("umask"), Computed(GET_UMASK)))。当用户通过set -g umask ...或umask函数设置它时,会触发 src/env/impl/environment.rs 中的set_umask:

fn set_umask(list_val: &[WString]) -> EnvStackSetResult { if list_val.len() != 1 || list_val[0].is_empty() { return EnvStackSetResult::Invalid; } let Ok(mask) = fish_wcstol_radix(&list_val[0], 8) else { return EnvStackSetResult::Invalid; }; if mask > 0o777 || mask < 0 { return EnvStackSetResult::Invalid; } // Do not actually create a umask variable. On env_stack_t::get() it will be calculated. umask(Mode::from_bits(mask as libc::mode_t).unwrap()); EnvStackSetResult::Ok }

这里有两处关键校验:

  1. 值必须以 8 为基数解析(fish_wcstol_radix(&list_val[0], 8)),因此umask 228、umask 0282、umask 1234这类包含非法八进制数字的值都会被拒绝;
  2. 解析后的数值必须在0..=0o777(即 0 到 511)范围内,超出范围同样返回Invalid。

校验通过后,fish 会直接调用 nix 封装下的系统调用umask(2)(umask(Mode::from_bits(mask as libc::mode_t).unwrap()))完成真实掩码的设置,并且不会真正创建名为umask的变量——该变量的值始终在读取时动态计算。这一点被 tests/checks/set.fish 的检查项印证:set umask abc会得到错误set: Tried to modify the special variable 'umask' to an invalid value。

读取掩码:电子变量与 GET_UMASK

当不带参数执行umask时,fish 函数读取$umask变量。这个值并非存储的普通变量,而是由GET_UMASK这个 Getter 实时生成的(见 src/env/impl/var.rs):

const GET_UMASK: Getter = |_env| { // note umask() is an absurd API: you call it to set the value and it returns the old // value. Thus we have to call it twice, to reset the value. The env_lock protects // against races. Guess what the umask is; if we guess right we don't need to reset it. let guess = Mode::S_IWGRP | Mode::S_IWOTH; let res = umask(guess); if res != guess { umask(res); } EnvVar::new_from_name(L!("umask"), sprintf!("0%0.3o", res.bits())) };

这段代码值得细读,它揭示了 POSIXumask(2)的一个著名“怪癖”:调用它设置新值的同时会返回旧值,因此查询当前掩码必须先把猜测值设置进去、再恢复原值。fish 的策略是:

  1. 猜测当前掩码为S_IWGRP | S_IWOTH(即八进制022,组与其他用户均屏蔽写权限);
  2. 调用umask(guess)获得真实旧值res;
  3. 若res != guess,则立即调用umask(res)恢复原值,避免竞态窗口内掩码被篡改;
  4. 将res格式化为带前导零的四位八进制字符串(sprintf!("0%0.3o", ...),如0027),作为变量值返回。

因此echo $umask输出的总是形如0027的四位八进制字符串,同时读操作本身不会改变掩码。umask因此被 fish 归为“电子变量”(electric variable)体系:它与其他变量一样可读可写,但值由进程状态计算而来,且写入时会被set_umask拦截并转发给内核。

符号掩码的语义

如果传入符号掩码,则直接指定期望的权限位,而不是其反掩码。符号掩码是一个逗号分隔的权利列表,每个权利由三部分组成:

  1. 适用对象(who):u(文件属主)、g(文件属组)、o(其他用户)或a(三者全部)。
  2. 操作模式(mode):=(将权限设置为新值)、+(在原有基础上追加指定权限)、-(从原有基础上移除指定权限)。
  3. 权限位(perms):r、w、x的任意组合,分别表示读、写、执行。

如果省略第一、二部分,则分别默认视为a和=。文档给出的示例是r,u+w,等价于a=r,u+w,含义是:所有用户获得读权限,文件属主额外获得写权限。

注意:fish 官方文档明确提示“符号掩码目前并不能按预期工作”(Note that symbolic masks currently do not work as intended)。原因可以从实现中看出:符号模式的解析与换算全部由 fish 函数 share/functions/umask.fish 完成(而非内核),它与 bash 语义存在已知偏差;同时 tests/checks/umask.fish 的注释也承认,其期望输出“全部基于 bash 执行相同命令时的行为”逐条手工核对而来。因此生产脚本中若对权限有强要求,建议优先使用八进制形式,或先用umask -S/ 测试用例验证符号模式在你所用版本上的实际表现。

符号模式的解析流程

share/functions/umask.fish 中的__fish_umask_parse是符号掩码的核心解析器,流程如下:

  1. 八进制捷径:先用string match -qr '^0?[0-7]{1,3}$'判断输入是否为合法八进制掩码,是则补零为四位后直接返回;
  2. 符号校验:否则用正则([ugoa]*)([=+-]?)([rwx]*)校验整个逗号分隔列表;并额外拒绝“有 who 无 mode”的非法组合(如urw);
  3. 拆分处理:把掩码拆成u、g、o三个数字位,对每个权利项解析出 scope、mode、perms;
  4. 换算权限值:r=4、w=2、x=1(与八进制权限位一致),通过预置的换算表__fish_umask_add_table、__fish_umask_remove_table、__fish_umask_set_table完成“符号权限 → umask 数字”的映射;
  5. 输出:最终以0开头拼接三位数字,返回四位八进制字符串。

而__fish_umask_print_symbolic则负责反向转换,把$umask的每一位数字还原成u=...,g=...,o=...的符号形式,这正是umask -S的输出来源。

顶层 umask 函数

share/functions/umask.fish 定义的顶层umask函数逻辑如下:

  • 零参数:若$umask未设置,则默认取113(即八进制0113,屏蔽属主的执行位以及组、其他用户的全部权限);随后根据选项输出——-p/--as-command打印umask $umask,-S/--symbolic调用__fish_umask_print_symbolic,否则直接打印$umask;
  • 一个参数:调用__fish_umask_parse解析并校验,成功后set -g umask $parsed(设置全局变量umask,进而触发set_umask写入内核),失败则打印umask: Invalid mask '...'并返回 1;
  • 多个参数:打印umask: Too many arguments并返回 1。

默认值113的引入是为了在$umask从未被设置时也能给出一个合理的回退值。

完整示例

以下示例均基于 tests/checks/umask.fish 中的真实用例整理:

# 设定基线掩码 027,然后查看八进制与符号两种输出 umask 027 umask # 输出 0027 echo umask var = $umask # 输出 umask var = 0027 umask -S # 输出 u=rwx,g=rx,o= # 非法掩码会被拒绝并报错 umask 1234 # 报错:umask: Invalid mask '1234' umask 228 # 报错:umask: Invalid mask '228' umask 0282 # 报错:umask: Invalid mask '0282' # 符号模式:先清空掩码再追加权限 umask 0 umask u-w,o-x umask # 输出 0021 umask -S # 输出 u=rw,g=rwx,o=rw # 符号模式:= 直接设定权限 umask 777 umask u+r,g+w,o=rw umask -S # 输出 u=rwx,g=rwx,o=rw # 省略 who 与 mode:默认 a 与 = umask 0 umask rx # 等价于 umask a=rx umask -S # 输出 u=rx,g=rx,o=rx # 组合操作:=r,g+w,o+x,o-r 逐个调整 umask 777 umask =r,g+w,o+x,o-r umask -S # 显式逐位设置 umask u=rwx,g=rwx,o= umask # 输出 0003(组、其他用户均屏蔽写与执行之外的权限) umask u=rwx,g=,o=rwx umask # 输出 0070

关于最后几组输出,可对照umask语义推算:u=rwx,g=rwx,o=表示属主与属组完整权限、其他用户无任何权限,对应的反掩码为0003(仅属主与属组移除执行位1之外的权限)——测试文件中的#CHECK注释给出了逐条期望值,是理解符号换算的最佳参考。

测试验证与已知限制

tests/checks/umask.fish 是 fish 官方对 umask 的回归测试集,覆盖了:

  • 基线八进制掩码的输出(0027)与符号输出(u=rwx,g=rx,o=);
  • 非法掩码的拒绝路径(1234、228、0282);
  • 大量符号模式的+、-、=组合及其与 bash 行为的对照;
  • Cygwin 下noacl挂载点的特殊处理(此时权限由 umask 模拟,测试会跳过依赖cut的符号输出分支)。

测试注释特别说明:所有符号模式的期望值都是编写者以 bash 相同命令的行为为基准逐一验证的,如果 bash 在这些场景下存在 bug,fish 可能同样存在——这也是文档中“符号掩码目前并不能按预期工作”警告的直接来源。此外 tests/checks/set.fish 验证了通过set命令把umask设为非法值时会被拒绝,印证了 src/env/impl/environment.rs 中set_umask的校验逻辑。

与普通 shell 变量的差异小结

方面普通变量umask(电子变量)
存储存在变量栈中不真正存储,读取时由GET_UMASK调用umask(2)实时计算
写入直接改栈由set_umask拦截,校验后调用umask(2)写入内核
输出格式原样固定四位八进制(如0027)
默认值无未设置时umask函数回退为113

理解这层差异对排查权限问题很有帮助:任何子进程(包括其他 shell、编辑器、构建工具)创建文件时都会继承该掩码,而 fish 之所以用电子变量包装,是为了在保持$umask可读可写接口的同时,确保它与内核状态始终一致。

  • CLI
  • 开发工具

【免费下载链接】fish-shell

The user-friendly command line shell.

项目地址:https://gitcode.com/GitHub_Trending/fi/fish-shell
点击查看免费下载
上一篇:6个步骤掌握Unity-Webview:从集成到实战
下一篇:从零到一:用GPT-2快速生成专属AI文本,本地跑通仅需5分钟

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询