【ZSH 系列教程】参数扩展(上)
文章目录
【注】本文最后更新于 December 1, 2020,文中内容可能已经过时。
zsh 的参数扩展相比 bash 而言强大了不止一星半点,它让 zsh 无需借助外部命令就能完成大量操作,是写出一个复杂的流畅的 zsh 插件的必备技能之一。
这也是一个区分“zsh 用户”和“用 zsh 作为交互式 shell 的 bash 用户”的有效手段。
考虑到本节内容很长,而我很懒,我决定将这节拆开。这篇文章里只介绍传统的 ${var%string}
形式的扩展,这方面和 bash 的用法大部分相同,但也有一些区别。
首先是与变量定义相关的
形式 | 作用 |
---|---|
${+var} |
若 var 已定义则返回 1,反之返回 0 |
${var-str} |
若 var 未定义则返回 str,反之返回 $var |
${var:-str} |
若 var 为空则返回 str,反之返回 $var |
${var+str} |
若 var 已定义则返回 str,反之返回 $var |
${var:+str} |
若 var 不为空则返回 str,反之返回 $var |
${var=str} |
若 var 未定义则赋值并返回 str |
${var:=str} |
若 var 为空则赋值并返回 str |
${var::=str} |
总是为 var 赋值并返回 str |
${var?str} |
若 var 未定义则抛出错误,错误信息为 str,否则返回 $var |
${var:?str} |
若 var 为空则抛出错误,错误信息为 str,否则返回 $var |
这部分和 bash 应该是一致的,没啥好讲,只提一个 zsh 里的特殊用法:
包裹字符串字面量
比如我有一个字符串 “~/.local/share”,我想把 share 替换成 bin。
一般的做法是将这个字符串赋给一个变量,然后再替换。但在 zsh 里我们可以直接这样写 ${${:-"~/.local/share"}/share/bin}
。这里巧妙地利用了 ${var:-str}
的语法将一个字符串字面量包裹了起来,是一个节省代码行数的小技巧。降低代码可读性
接下来是最常用的字符串替换,仍然与 bash 差不多
形式 | 作用 |
---|---|
${var#pat} |
从 $var 的开头删掉匹配模式 pat 的字符串并返回 |
${var##pat} |
同上,不过此处是贪婪匹配 |
${var%pat} |
从 $var 的末尾删掉匹配模式 pat 的字符串并返回 |
${var%%pat} |
同上,不过此处是贪婪匹配 |
${var:#pat} |
如果 $var 完整匹配 pat 则返回空字符串,否则直接返回 |
${var/pat/repl} |
将第一个匹配 pat 的字符串替换为 repl,贪婪匹配 |
${var//pat/repl} |
同上,但是会替换所有出现的位置 |
${var:/pat/repl} |
同上,但是要求 pat 匹配整个字符串(类比一下 ${var:#pat} ) |
需要注意的是,如果 var 是数组的话,那么规则就会应用到每一个元素。比如 ${var#pat}
变成从数组中每个元素的开头删除匹配模式 pat 的字符串,${var:#pat}
变成将数组中完整匹配 pat 的元素删除。
举例如下,注意只有加了引号并且没有使用 @
的时候规则才会作用于数组整体:
|
|
关于 ${var/pat/repl}
的形式还有几个需要注意的点:
-
在
${var/pat/repl}
的形式中,pat 也可以加#
、%
或#%
来指示从前、从后、或是要求完整匹配字符串。这点同样与 bash 一致。 -
如果 pat 是一个变量,那么它的展开结果会被当成一个纯字符串而不是模式。这点与 bash 不同需要注意!你需要显式使用
${~var}
来告诉 zsh 这个地方的展开结果是模式。 -
这个替换默认是贪婪的,如果想要不贪婪的话,需要使用
S
flag:${(S)var/pat/repl}
。关于参数扩展 flag 的内容会在下节介绍。
接下来是数组操作,这部分掺杂的乱七八糟的玩意儿就逐渐多起来了
形式 | 作用 |
---|---|
${var:|arr} |
两数组做差 |
${var:*arr} |
两数组取交集 |
${var:^arr} |
zip 两个数组,最终数组长度以较短的为准 |
${var:^^arr} |
zip 两个数组,最终数组长度以较长的为准,较短的数组会循环补全到同样长度 |
${var:ofs} |
切片,取下标 ofs 一直到末尾的内容 |
${var:ofs:len} |
切片,从下标 ofs 开始取 len 个元素 |
注意点:
-
ofs 如果包含负号的话需要加一个空格,否则会与
${var:-str}
的用法冲突 -
切片下标会自动进行算术扩展,因此可以直接写 a + b 而不用
$(( a + b ))
(当然还是要注意空格)
最后是某些前缀,下面的 spec 可以为上面任一扩展形式,其中 #
如过要与 ^=~
组合的话,则必须放在它们的右边。特别的,如果 spec 只是一个简单的变量名的话,可以省略大括号:
-
${#spec}
– 取展开结果的长度 -
${^spec}
– 对展开结果启用 RC_EXPAND_PARAM 开关,比如var=(1 2 3)
,则A${^var}B
会展开为A1B A2B A3B
-
${=spec}
– 对展开结果启用 SH_WORD_SPLIT,也就是根据 IFS 把你的展开结果分割为数组。没错!这就是 bash 的默认行为…… -
${~spec}
– 对展开结果启用 GLOB_SUBST 并视为模式尝试展开。没错!这也是 bash 的默认行为……
后两个都是坑爹 bash 的默认行为,也是导致 bash 里到处都是引号的罪魁祸首。因为 zsh 默认没启用这种行为,所以在写 zsh 的时候大家可以放心地省略引号,不会有任何问题。
最后值得一提的是 ${~spec}
的一个常用用法:echo ${~${:-"~/.zshenv"}}
=> /home/aloxaf/.zshenv
,不要再傻傻地用 ${var/~/$HOME}
了!