在 Shell 运行命令前就把变量改写成新值的一批运算符。

Shell 参数展开改写变量值

参数展开根据 Shell 参数产生一个值,随后命令才收到展开后的参数。它可以提供默认值、拒绝缺失配置,或者删除字符串的一部分,不必为这些操作启动额外进程。但“执行命令前展开”并不等于“初次解析脚本时就求值”:函数在被调用时读取变量当前的内容,函数定义之后修改变量,会影响后续调用。区分解析、展开和执行,才能解释为什么相同的表达式在不同时间产生不同结果。 默认值要区分未设置与空字符串 ${setting:-default} 在变量未设置或者为空时使用默认值;${setting-default} 只处理未设置的情况,保留调用者明确传入的空字符串。冒号改变的是配置语义,不能当成随意省略的标点。例如空字符串可能表示关闭某个前缀,擅自用默认值替换就会改变用户的选择。${setting:+enabled} 只在变量已设置且非空时产生替代文本,去掉冒号后,已设置但为空也满足条件。 普通默认值展开不会修改变量本身。${setting:=default} 才会在选中默认分支时把结果存进变量,之后再读取就能看到新值。${setting:?message} 则在变量未设置或为空时把诊断写到标准错误,并让非交互 Shell 以非零状态退出。去掉冒号后,这两个运算符也只检查是否设置。应当先确定调用约定:允许为空、允许省略,还是必须提供,然后选择相应形式,而不是把所有配置问题都掩盖成一个默认值。 花括号还负责把变量名与相邻文本分开。"${prefix}_suffix" 是给变量内容追加后缀,"$prefix_suffix" 却读取另一个变量。用几个配置拼文件名时,看似缺值的问题可能只是变量名边界写错。应分别检查各个组成部分,再排查最终路径;一个语法完全合法的字符串,也可能混入来自错误变量的内容。 模式处理字符串,不访问文件 ${name#pattern} 与 ${name##pattern} 分别删除最短和最长匹配前缀;对应的后缀运算符是 % 与 %%。这里使用 Shell 模式,不是正则表达式。匹配不到时保留原字符串,匹配成功也不会自动回写原变量。斜杠在这里就是普通的文本内容,这些运算不会查询目录、确认文件存在或者解析符号链接,因此不能用一次字符串截取证明路径安全。 如果结果必须保持一个参数,就给整个展开加双引号。"${name##*/}" 仍然会应用花括号里的模式,删除直到最后一个斜杠的前缀;外层引号保护的是展开结果,不会让内部星号失去模式含义。这些形式属于可移植的 Shell 操作,而下标截取、全局替换等还可能依赖 Bash 扩展。脚本既然用 sh 启动,就不能仅凭作者的交互环境支持某种写法而假定所有机器都支持。 坏掉的样子 未加引号的展开结果可能继续经历分词与文件名展开。花括号只划定变量名,不保护参数边界;变量包含空格或星号时,命令可能收到额外参数,甚至收到当前目录里碰巧匹配的文件名。另外,默认表达式只有在对应分支被选中时才会展开。如果把命令替换放进默认值,那么缺值分支就可能启动进程,不能把这种表达式当成完全没有副作用的常量。 参数展开也不是命令模板系统。从变量得到的分号、引号等文本,不会自动重新解析成 Shell 语法。需要动态参数列表时,在 Bash 中使用数组,或在可移植 Shell 中明确构造位置参数;不要把整条命令存成字符串,再用 eval 解释。set -u 能发现部分未设置变量,却不能判断业务取值是否合理;pipefail 管理管道状态,也不会改变这里的字符串运算规则。 ...

十一月 16, 2027 · 1 分钟 · XB Field Notes