有次把一批图片交给服务器,静态规则里只写了小写后缀,photo.JPG 和 document.PDF 整批漏在那条规则外面。想统一成小写,我把二十几个名字粘进去先跑了一轮,结果分三种:一半名字被改、几个原样吐回来、archive.tar.gz 只改了后半截。漏改这一类是会重复发生的,所以更值得先弄清楚。
先摆尺度:三种模式对同一行给出不同答案的行数是 20、19、20,占 74.1%、70.4%、74.1%——选哪一档并不是口味问题。
后缀从哪个点算起:最后一个,而且下标要大于 0
判定只有两行:var dot = filename.lastIndexOf('.'); 然后 if (dot <= 0) 就直接返回原串、后缀记为空。dot 是下标,不是个数,整行没有点时是 -1,点在最前面时是 0,这两种都被那道判断挡在转换之外。
| 输入 | 最后一个点 | 取到的后缀 | UPPER | lower | Title |
|---|---|---|---|---|---|
photo.JPG | 5 | JPG | 未变化 | photo.jpg | photo.Jpg |
archive.tar.gz | 11 | gz | archive.tar.GZ | 未变化 | archive.tar.Gz |
.profile.bak | 8 | bak | .profile.BAK | 未变化 | .profile.Bak |
.gitignore | 0 | 空 | 未变化 | 未变化 | 未变化 |
LICENSE | -1 | 空 | 未变化 | 未变化 | 未变化 |
file. | 4 | 空 | 未变化 | 未变化 | 未变化 |
data.JSONL | 4 | JSONL | 未变化 | data.jsonl | data.Jsonl |
C:\Program Files\my.app\run | 19 | app\run | 目录段被当后缀 | 未变化 | 同样被改 |
「无后缀」那 7 行能拆开对上号:整行没点的 3 行(LICENSE、index、C:\path\to\file)、点在下标 0 的 2 行(.gitignore、.)、切完后缀是空串的 2 行(file.、..),3 + 2 + 2 = 7,与统计栏报的数一致。尾巴那个点也不会被删:file. 输出仍是 file.。
图 1|最后一个点的下标,条形按下标等比;浅色三条都落在不转换那一侧。
三个模式改动的行数差这么多,无后缀还不是第四类
图 2|28 行清单里有 27 行有效,条形按行数等比;无后缀在三档都是 7。
计数口径要先说明白:已转换 + 未变化 = 总文件数,11 + 16 = 27 成立;无后缀同时被加进两个计数器,它是未变化的子集,四个数字加起来会比总文件数大。粘进去 28 行、表格只有 27 行,缺的是那个空行,它连表都进不去。我是拿后缀大小写转换把同一份清单按三档各跑一遍才把这些数字对上,页面上写着「已处理 27 个文件名」,重复的两行 photo.JPG 都算进去了,工具不去重。
Title 这一档最容易被当成「好看一点的小写」,其实它会改写后缀里除首字符外的每一个字母:document.PDF 得 document.Pdf,data.JSONL 得 data.Jsonl。PDF、JPEG、MP3 这类缩写想原样留着只有 UPPER 做得到;后缀只剩一个字符时 Title 和 UPPER 等价(a.b.c.D 输出仍是 a.b.c.D)。名字主体(最后一个点之前那一段)一律不动;整行文本的大小写是另一件事,要统一可以先过一遍大小写转换。
粘整行路径进来,被改的是目录名片段
判定只找点,不认识反斜杠。C:\path\to\file 没有点,原样透传;目录名里带一个点就露馅:C:\Program Files\my.app\run 的 dot 是 19,取到的「后缀」是 app\run,UPPER 之后变成 C:\Program Files\my.APP\RUN——改到的是目录,不是扩展名。所以我现在只粘纯文件名,整条路径先交给从路径里取文件名。中文与全角字符不受影响:年度报告.docx 变 年度报告.DOCX,报告 2024.final.docx 里那个 final 不动。
长度也不是问题:135 码元的名字不截断,换 400 行进去表格就出 400 行,源码里没有行数或字节上限。
粘出来的文本和三处对不上的地方
- 行首尾空白会被裁掉,两处各 trim 一遍;
note.txt后面那个空格不会跟着回来,中间空格留着(mid dle.TXT原样)。 - 空行、纯空格行、纯制表符行整行消失且不补位置;行序不改,
z.txt排第一就一直排第一。 - Windows 保留名和非法字符一概不校验,
CON.txt照变CON.TXT,PRN.log、nul.md同理;a<b>|".txt也能改,表格里那对尖括号是转义后显示的。 - CRLF 粘进去,行尾的 CR 在切行时就被吃掉,复制回来的永远是 LF 行尾,不回写 CRLF。
还有两处要自己盯。一是界面不会告诉你哪行没动:渲染那段有一个「前后是否相同」的判断,但两个分支拼出来的是同一个 fc-after 单元,实测三档表格里 fc-after 都是 27 次、fc-same 出现 0 次,已转换与未变化长得一模一样,只有统计数字能区分。二是这页只处理文本,不碰磁盘也不上传——脚本里网络与本地存储的调用都是 0 次,改名那一步要自己配 rename 或 mv。
Q:archive.tar.gz 为什么只改了一半?
因为点只认最后一个:dot = 11,切出来的是 gz,tar 在它前面,属于文件名那一段。三档都只作用在最后一段上,想把 tar 一起改的开关并不存在。
Q:LICENSE 和 .gitignore 为什么一行都不动?
同一条早退判断:dot <= 0。LICENSE 整行没有点,dot 是 -1;.gitignore 的点在下标 0。两条都记成「无后缀」,但仍计入未变化那一栏,所以统计里看得见它们。判定只看最后一个点的位置,不看开头:.profile.bak 的 dot 是 8,就正常转成 .profile.BAK。
Q:三种模式该按什么选?
按要保留什么选。要 URL 与服务器惯例统一成小写就走 lower,这份清单它只改 9 行;要保住 JPG、PDF 这种缩写原样只有 UPPER;Title 看着整齐,代价是把 PDF 拆成 Pdf。跑之前先放几个代表名(双层后缀、点开头、无点各一个)各档试一遍,比整批改完再回头核对便宜。