杂记 › 技术笔记 › 后缀只切最后一个点:archive.tar.gz 分走 gz,photo. 和 .gitignore 挤进同一格,表格首列还会掉一个点

后缀只切最后一个点:archive.tar.gz 分走 gz,photo. 和 .gitignore 挤进同一格,表格首列还会掉一个点

后缀拆分把切点写死在最后一个点,archive.tar.gz 只切出 gz;没点和点打头的两类输入共用同一个空扩展名返回,photo. 这类尾点走另一条分支也落进无后缀。第一列是重建串会吞掉尾点,11 格里有 4 格对不回输入。

切点写死在最后一个点:archive.tar.gz 只分到 gz

手边攒了一堆文件名想按后缀归堆:archive.tar.gz 到底算 gz 还是 tar.gz?photo. 算不算有后缀?开头带点的 .gitignore 又该进哪一列?把这几行贴进去、点一次「拆分后缀」,结果和脑子里那个「扩展名」并不完全对得上。差的不是按钮,是切点被钉死在最后一个点,外加紧跟其后的一条分支把两种输入并到了一起。模板一共 6546 字节 / 158 行,拆后缀这件事全住在 splitSuffix 那十来行里。

切点走的是 filename.lastIndexOf('.'),从右往左数到的第一个点就是唯一切点。文件名后缀拆分于是把 archive.tar.gz 切成 name 为 archive.tar、ext 为 gz——多重后缀永远只切最后那一段。11 个样本里点数 ≥ 2 的有 7 格,切完之后 ext 里含点的 0 格、name 里仍带点的反而有 8 格:中间那些点全被留在了文件名那一侧,而不是跟着进扩展名列。

archive.tar.gz 这类多重后缀,只切最后一段 样本 切出的 name ext archive.tar.gz archive.tar gz my.file.txt my.file txt 多.重.后.缀.zip 多.重.后.缀 zip 点数 ≥ 2 的 7 格 · ext 含点 0 格 · name 仍含点 8 格

图 1:切点钉在最后一个点,多重点全被留在文件名侧。

dot <= 0 这一条,把「没有点」和「点打头」并成一个返回

紧跟着切点的 L66 那道 if (dot <= 0) 一次接住两类完全不同的输入:dot 为 -1(README 根本没有点)和 dot 为 0(.gitignore 的点就在第 0 位)。两者共用同一个 return { name: filename, ext: '' }——整串原样进 name,ext 给空串。于是 .gitignore 不被拆出扩展名、README 也一样,两格落进同一个「无后缀」计数。

真正容易被读漏的是尾点那一族:photo.、.hidden.、..、name.。它们走的是 dot > 0 那条分支,点确实在串里某处,只是 substring(dot + 1) 从点后一位切到串尾,切出来是个空串。于是 ext 照样为空,在 L96 的统计里它和 README、.gitignore 一起落进 withoutExt——11 格里这样的有 4 格。差别只落在 name:前两类整串进 name,尾点族的 name 被削掉了最后那个点。

逐格摊开:11 行真值表,看第一列对不回输入的有哪几格

下面把 11 个样本一格一格铺开。最后一列是「重建出来的第一列」,它等于 name 拼回 ext;拿它和修剪后的输入逐格比,7 格对得上、4 格对不上,对不上的正是 photo. / .hidden. / .. / name. 这四个尾点样本。

输入文件名最后一个点nameext第一列重建串对得上?
archive.tar.gz11archive.targzarchive.tar.gz是
my.file.txt7my.filetxtmy.file.txt是
.gitignore0.gitignore—.gitignore是
.bashrc.conf7.bashrcconf.bashrc.conf是
README-1README—README是
photo.5photo—photo否
.hidden.7.hidden—.hidden否
a..b2a.ba..b是
..1.—.否
name.4name—name否
多.重.后.缀.zip7多.重.后.缀zip多.重.后.缀.zip是

重建出来的第一列,会悄悄吃掉一个尾点

第一列不是原样回填输入,它是按 name + (ext ? '.' + ext : '') 现拼的重建串,而输入在进函数之前已经被剪过首尾空白。photo. 走完一圈,name 是 photo、ext 空,重建串就停在 photo,那个尾点没了。尾点这一族通吃:三个点 ... 重建剩 ..,archive.tar.gz 后面再补一个点,回来变回 archive.tar.gz——每次恰好吞掉 1 个尾点。第二列的 name 却仍保留中间那些点,所以两列对不齐的根子在重建那一步,不在切分本身。

本页所有样本都按输入原样大小写记着,后缀的大小写归后缀大小写转换那页管。

第一列 = name +(有 ext 才补一个点)+ ext 输入 重建出来的第一列 尾点 photo. photo 点没了 ... .. 少 1 个点 archive.tar.gz. archive.tar.gz 少 1 个点 11 格里:对得上 7 · 掉点 4

图 2:重建列每次吞掉一个尾点,11 格里 4 格对不回输入。

统计三行和提示语,是同一本账

右上角的统计走 L96 的 if (r.ext),判据是 ext 空不空,不是「有没有点」。11 个样本整批跑一遍,total 11、有后缀 5、无后缀 6,和表格的 11 行、6 个破折号格、5 个绿格、提示语那句「已拆分 11 个文件名」完全同分母,5 + 6 = 11 也咬得上。点「示例」灌进去的那 6 行则得 6 / 4 / 2,每行点数分别 1、2、0、1、1、1,archive.tar.gz 那一格一个人贡献 2 个点、ext 却只拿走 gz。

空行只进分母、不进任何计数:掺进 20000 行里 5000 个空行,实测统计停在 15000,被跳过的 5000 不出现在任何一列。同分母这三处对齐,可以逐条核:

  • 表格行 11、破折号格 6、绿格 5,提示语的 N 也写 11,四处同分母。
  • 示例灌的 6 行得 6 / 4 / 2,且 6 = 4 + 2。
  • 20000 行掺 5000 空行,统计 15000,空行不进任何一列。

至于同工具更早那篇拆两列的文章,量的是另一套账,和这里切点的分支不是一回事,它的结论本篇不重复;要看那条链路可点开文件名拆两列的旧文。

Q:photo. 明明有个点,怎么被算成无后缀?

点在倒数第一位,substring(dot + 1) 从点后一位切到串尾是空串,ext 为空,统计就把它归进无后缀那一格;只是它的 name 被削掉了尾点,和 README 那种整串进 name 并不一样。

Q:.tar.gz 不能整体当扩展名吗?

在这一页不能。切点恒在最后一个点,11 格里 ext 含点 0 格,.tar.gz 的 ext 只剩 gz,前面那段留在 name 里——工具文案里「支持多扩展名」这句,和代码实际切出来的并不一致。

Q:第一列和输入对不上,是坏了吗?

它是按 name 拼回 ext 现造的重建串,不回填原串。尾点这一族因此在第一列少一个点,11 格里有 4 格对不回修剪后的输入,而第二列的 name 仍保留中间那些点。

📎 相关文章

3.7 读成 3、1e3 读成 1:分数计算器这一页先在输入端截成整数,再在 14 个字符处换显示口径 2026-11-14 正则测试页永远带 g:填 gg 直接报错、填大写静默失效,21 处带组匹配只列 20 行 2026-11-14 星号段长写死:脱敏页五套掩码固定切出 4、3、11、12 枚星号,18 位串被手机档抢走、空框回落 * 2026-11-13 锁是单向的、百分比是两把尺:pct 50 得 619×430,PNG 四档 quality 全 6971 B 2026-11-13 相对时间的「一个月」写死 30 天:时间戳页六档除数、13 位毫秒输入与 UTC+8/UTC-5 两档截断实测 2026-11-13