
bash 的参数展开里,#、%、/ 三家都做「删一段、换一段」的事,各自又分贪婪和不贪婪两个版本。问的人多的是下面这几个,按原话排。
${v#-} 和 ${v##-} 到底差在哪?
差在匹配到多长。# 是最短匹配,## 是最长匹配。变量是 report-2026-09-18-final.tar.gz 的时候:
===== 准备数据 =====
v = report-2026-09-18-final.tar.gz
===== # 最短匹配:从左往右找到第一个匹配就删 =====
v#*- -> 2026-09-18-final.tar.gz
===== ## 最长匹配:从左往右一路贪到最后一个匹配 =====
v##*- -> final.tar.gz
#*- 找到第一个 - 就停,删掉 report-;##*- 一路贪到最后一个 -,把 report-2026-09-18- 全删了。% 和 %% 同理,只是从右往左:
===== % 最短匹配:从右往左找到第一个匹配就删 =====
v%.* -> report-2026-09-18-final.tar
===== %% 最长匹配:从右往左一路贪到头 =====
v%%.* -> report-2026-09-18-final
记法一句话:一个符号最短,两个符号最长。
为什么 ${p//\/\//\/} 会把 a//b 折叠成 a/b?
这个问法背后通常是这样的事:拿 p="a//b///c.txt" 做 ${p//'//'/'/'},结果不是少一组斜杠,是一组都没少:
===== 相邻斜杠会折叠:路径长什么样,模式就按长什么样匹配 =====
p = a//b///c.txt
p//'//'/'/' = a/b//c.txt
想删掉所有重复斜杠、只留一个,得跑两遍,因为第二遍跑完 a/b//c.txt 里还剩一组。更直接的办法是换成 sed 或者参数展开的通配写法,一次到位。
替换和删除的字数对不上,多出来的是什么?
模式是按 glob 整体命中的,命中多长就换掉多长:
===== 替换与删除都要求模式整体命中,命中多长换多长 =====
w = path/name//name/file
w//name/X = path/X//X/file
w//'//'/'/' = path/name/name/file
w//name/X 把两处 name 都换成了 X,中间那组斜杠没动,因为模式里没写它。想要的是「把 name 连同前后多余斜杠一起收掉」,模式就得写成 w//'/*name/*'/'X' 这类把斜杠算进去的写法,光盯着 name 本身改不出来。
路径处理什么时候别用参数展开?
取目录名、取文件名这种事,dirname 和 basename 一直都在,各种边界人家都处理过了。参数展开适合的是简单的掐头去尾:去掉扩展名、去掉前缀。模式一复杂,#、%、/ 混着写出来的东西过两天连作者都读不懂,这种场景宁可多起一个进程换可读性。
🤞 分享