Notes › Tech Notes › Three Edges of Filename Extraction: The Trailing Slash Backstops One Step, the Query String Stays Whole, Blank Lines Vanish Silently

Three Edges of Filename Extraction: The Trailing Slash Backstops One Step, the Query String Stays Whole, Blank Lines Vanish Silently

A pasted 20-line backup log yields 16 lines: 3 blank lines skip counting and the lone slash line is swallowed. The trailing-separator fallback steps back once, the query string stays in the name, and the extension cut follows the last dot; the stat bar 17, 16, 14 all reconcile.

Clear the line-count difference first

Paste a whole backup log into the input box, one path per line, hit extract, and the output has fewer lines than the input - before blaming the tool for dropping lines, do the bookkeeping. I ran a 20-line fixture and the stat bar shows total paths: 17, extracted filenames: 16, unique filenames: 14: those 3 blank lines are skipped before counting, so they never even reach 17; that lone slash line does enter 17, yet it is silently dropped from the output, stopping at 16. Output no longer maps back by line number, and the interface never says which line it was.

The trailing-separator fallback: it only backs up one step

Splitting D:\a\ yields 3 segments, the last one empty; the fallback steps back a single slot and takes the second-to-last segment, handing over a; /var/www/ and a/ behave the same way. But the fallback has no loop: for D:\a\\, which writes two separators at the end, both the last and the second-to-last segments are empty, and an empty string comes back; the same goes for a//, and for a line containing only / or only \. Runs of separators in the middle are actually harmless - taking the last segment never looks at empty ones, so a//b.txt still prints b.txt.

Fallback checks the second-to-last segment once, no loop (left: raw input)Input and splitReturn"D:\a\" → 3 segments ["D:","a",""]→ "a""D:\a\\" → 4 segments ["D:","a","",""]→ "" (line dropped)"a//" → 3 segments ["a","",""]→ "" (dropped)"/" and "\" → 2 segments ["",""]→ "" (dropped)"C:\" → 2 segments ["C:",""]→ "C:" (2 chars)"/report.pdf/" → ["","report.pdf",""]→ "report.pdf" (10 chars)

Figure 1: backing up one slot versus failing to back up, measured word by word inside the same function.

The introductory judgment on the two separators is in the previous article Batch Extract Filenames from Paths; this page only measures the positions that never raise an error.

The three stat-bar numbers and four toggle combinations

Line-count bookkeeping: of the 20 lines, 3 are empty after trim and never enter total, 1 yields an empty name and gets dropped, and 16 minus 14 equals 2 - report.pdf and photo.JPG each repeat twice. Byte bookkeeping:

Toggle combinationStat bar (total / extracted / unique)Output
Extension kept, no dedupe17 / 16 / 1416 lines, 165 bytes
Extension kept, dedupe kept17 / 14 / 1414 lines, 144 bytes
Extension off, no dedupe17 / 16 / 1416 lines, 102 bytes
Extension off, dedupe also on17 / 14 / 1414 lines, 89 bytes

The output is joined with LF only and carries no trailing newline. The CRLF version and the LF version of the fixture agree exactly on the three stat numbers and on the output character count (17, 16, 165); only the input bytes differ (374 versus 355); pasting one more newline at the end pushes the split from 20 segments to 21, and the stat bar does not budge.

Output byte bars and line counts (stat numbers: table above)Ext kept, no dedupe165 B / 16 linesExt kept, dedupe kept144 B / 14 linesExt off, no dedupe102 B / 16 linesExt off, dedupe also on89 B / 14 lines

Figure 2: the output bytes of one fixture under four toggle states, with the bar length drawn at 2 units per byte.

Query strings and fragments: the whole block stays inside the name

A 50-character CDN address, with the extension checkbox ticked, prints the 22-character a.jpg?w=800&h=600#frag: this page splits each line only on separators, so ?query#frag is part of the name; nothing parses the URL, and nothing decodes the percent encoding.

Ticking the extension off changes the picture entirely. Among those 22 characters only the single dot before "a.jpg" is left, sitting at index 1; one cut shaves away 21 characters, 94.5% evaporates, and what remains is the 1-character a. More dots get stranger: /files/report.csv?v=1.2 with the extension removed becomes the 14-character report.csv?v=1 - the last dot lands on the decimal point inside the query value 1.2, and the surviving string still drags a ?v=, which is neither a filename nor a clean extension split; /download/report.pdf?v=2 is instead perfectly clean and outputs report. URLs and Windows paths pass through the same blade.

The dot blade: two rules, and every twist follows from them

The implementation of 取消包含扩展名 is only two sentences: find the last dot in the final segment, and swing the blade only when its position is strictly greater than 0. Measured item by item:

  • multi-part extensions only lose their last part: a.tar.gz becomes a.tar (5 characters), archive.tar.bz2 becomes archive.tar (11 characters), a.b.c.d becomes a.b.c;
  • a dot at position 0 never gets cut: .gitignore is preserved as-is; for .hidden.txt the final dot still gets cut, leaving only .hidden;
  • a trailing dot counts as an extension: name. becomes name, and even the directory dot .. gets shaved down to a single .;
  • no dot means the whole string stays untouched: README is still README.

This blade is not idempotent either: paste the 16 output lines back in and run a second pass, a.tar keeps shrinking to a, archive.tar keeps shrinking to archive, 2 items shift; the extension-kept option shows 0 changed items on the second pass and still 16 lines. So do not treat 取消包含扩展名 as "the remove-extension switch" - one extra run shaves one extra layer.

Dedupe: exact character comparison, case counts as different too

Dedupe matches output names exactly, case included: /a/photo.JPG, /b/photo.jpg and /c/PHOTO.JPG still print 3 lines with dedupe ticked, and the stat bar reads unique filenames: 3. The fixture shrinks from 16 to 14 because report.pdf and photo.JPG each appear twice; one of the two photo.JPG lines carries leading and trailing whitespace, and only after trim do they finally merge.

Drive letters and URL roots: what comes back is another kind of string

Non-file path lines mixed into the batch all have fixed results too. The drive root C:\ returns the 2-character C:, the string holds no dot so the extension cut cannot touch it either; the "filename" of a URL root is really the host name, so https://example.com/ prints the 11-character example.com, and after the cut only 7 characters remain; a protocol string without any slash is taken whole as the name, mailto:support@example.com stays at its full 26 characters, and removing the trailing .com leaves 22 characters. The function has no idea what a drive letter, a host name or a mailbox address is; it only splits segments and hunts for dots.

200 lines is one bill of 0.154 ms

Copy the same fixture 10 times into a 200-line CRLF input of 3758 bytes, and on the Batch Extract Filenames action one measured run takes 0.154 ms; the output is 160 lines, and unique filenames is still 14. Ticking dedupe is actually faster: 0.1223 ms, 14 lines. The line-count bookkeeping scales linearly: total paths 170, extracted filenames 160. The page applies no length validation and no truncation.

A few spots that are easy to misread

Q: Will dedupe merge photo.JPG and photo.jpg into a single entry?

No. The match is exact and case-sensitive, all three lines stay, and the stat bar reads unique filenames: 3.

Q: Can the example button's content serve as a baseline?

Yes: the example feeds 6 lines of 155 characters / 163 bytes, LF-separated with no trailing newline, and line 4 is a deliberate repeat of line 1; the full run prints 6 lines and the stat bar reads 6 / 6 / 5, which makes it a clean baseline.

Carry these ledger cells back home

Inside this fixture 17, 16, 14 and 20 interlock: 20 lines go in, 3 blank lines are not counted, 1 empty-name line is swallowed, 16 lines come out, and two duplicate pairs fold down to 14. Three mechanisms: the trailing-separator fallback only backs up one slot, and the empty string it produces does not even enter the statistics; 取消包含扩展名 cuts at the last dot and is not idempotent; dedupe compares character by character, case-sensitive. A different list changes the numbers, but the reconciliation is still those same two subtractions.

📎 Related articles

How a 1 GB File Is Built From 1024 Chunks: Chunk Rhythm, Cancel Boundaries and Three Fill Bytes in a File Generator 2026-11-04 Turn encodeAll On and Chinese First Becomes 中: HTML Entity Table Coverage and Decode Tolerance, Measured 2026-11-04 HTML Beautify and Minify Are Not Inverses: A Round-Trip Byte Audit of 10 Samples 2026-11-04 You Paste 100 Fraction Rows and Row 45 Quietly Becomes 1/4: Who Owns the Minus Sign, Blank Lines and Big Numbers on This Page 2026-11-03 Paste 3. Start Into Markdown to HTML: Why Ordered List Numbering Only Honours the First Number 2026-11-03