Skip to content

split: implement it - #220

Merged
JalonSolov merged 3 commits into
vlang:mainfrom
metif12:fix/split
Oct 4, 2026
Merged

JalonSolov merged 3 commits into
vlang:mainfrom
metif12:fix/split

Conversation

@metif12

@metif12 metif12 commented Oct 4, 2026

Copy link
Copy Markdown
Member

split: implement it

src/split held a delete.me and nothing else, so split was a binary that did
nothing. Of the 96 directories here, 33 are like that.

What it does now: writes a file into fixed sized pieces, named PREFIXaa, PREFIXab
and so on, or PREFIX00 with -d, and widens the suffix when the combinations at the
current width run out rather than failing.

Measured against GNU 9.4

Twenty one cases, each running this binary and GNU's over the same input and
comparing the names produced, every file's contents and the exit status:

default                      same
-l 3                         same
-d -l 3                      same
-a 3 -l 3                    same
-l 1                         same
-l 4 -d                      same
empty input                  same
no trailing newline          same
-b 4                         same
-b 5                         same
-b 10                        same
-b 1                         same
-C 4                         same
-C 5                         same
-C 1                         same
-b 4 -a 1                    same
explicit prefix              same
two ways asked for at once   same
-l 0, -b 0                   same
no such file                 same

The two modes that are easy to get wrong are pinned down rather than guessed:

  • -b N cuts where the byte count says, mid line if that is where it lands. With
    -b 1 over a 21 byte file GNU writes 21 files of one byte, and so does this.
  • -C N is that cut with one difference: a line is never split, so a line that does
    not fit starts the next file. With -C 4 over ten two byte lines GNU puts four
    lines in a file and gives the last two a file each, because "9\n10\n" is 5 bytes;
    with -C 5 they share one file. With -C 1, where no line fits at all, GNU stops
    trying and cuts by bytes instead, giving the same 21 one byte files as -b 1. That
    last one was wrong here at first: this wrote whole lines, and comparing showed it.

A missing input file is reported with the same wording as GNU:

split: cannot open 'nothere.txt' for reading: No such file or directory

What is left, and why it is left

-n is not implemented, and says so rather than producing something plausible. It
takes two forms and neither is the even split the word "chunks" suggests: over ten
lines, -n l/7 gives 2,1,2,1,2,1,1 and -n 7 gives 2,1,2,1,2,1,1 lines. Three data
points are not enough to say what the rule is for every N, and a split that puts the
wrong bytes in the wrong file is worse than one that refuses.

--filter is not implemented for a different reason. It runs a command per piece, and
on Windows the only way this V has of starting a process from a command string is
os.system, which is deprecated and an error under -W, which is what make test
builds with. Shipping it would mean shipping a deprecated call.

One known difference in argument handling: -n 0 says "chunks are not implemented"
where GNU says "invalid number of chunks: ‘0’", and GNU quotes the number with the
curly quotes that this port uses straight ones for. The number is not parsed before
the refusal, so an invalid -n is reported the same way as a valid one.

What did not change

Nothing else. delete.me stays where it is, as it does in the other 32 directories
that hold one.

src/tee held a delete.me and nothing else, so `tee` was a binary that did nothing.
Of the 96 directories here, 33 are like that.

What it does now: copies standard input to standard output and to every file named,
truncating them, or appending with -a.

## Measured against GNU 9.4

Standard input is read a line at a time and written out unchanged, newline included,
so a file that does not end in a newline stays that way, which is the whole reason
tee is expected to be byte for byte what came in. Both shapes of input, compared on
stdout, on stderr and on exit status:

	no file, no trailing newline      same, ours=0 gnu=0
	no file, with trailing newline    same, ours=0 gnu=0

One file, written directly and read back:

	$ tee2.exe out.txt < in.txt && cmp out.txt in.txt
	13 bytes, one\ntwo\nthree, identical to the input

A file that cannot be opened is reported the way GNU reports it, with the system's
reason rather than this port's wording, and the exit status is 1 in three of the four
output-error modes:

	-p warn         ours=1 gnu=1
	-p exit         ours=1 gnu=1
	-p die          ours=1 gnu=1
	--output-error=bogus    ours=1 gnu=1

-i is accepted and does nothing: there is no signal handling here to be interrupted
from, and taking the option is better than refusing one GNU takes.

## What is left, and why it is left

-p warn-no-fail exits 0 here and 1 at GNU. The manual says that mode does not modify
the exit status, so the measurement is not what the documentation describes, and the
rule behind it has not been pinned down: the likely one is that GNU still fails when
none of the named files could be opened at all, and the case where one of two opens is
exactly the one that would tell the two explanations apart. Encoding a rule from one
data point would be a guess, so the mode is left as written and the difference
recorded here instead.

There is no test file for this tool either, which is true of 64 of the tools in this
repository. One belongs here, and it is the next thing to write for tee: the byte for
byte check above is a probe, and a probe does not run in CI.

## What did not change

Nothing else. `delete.me` stays where it is, as it does in the other 32 directories
that hold one.
Returning `if append { os.open_append(path) } else { os.create(path) }` puts a
Result in each arm of a conditional, and V reports that as a missing or {} block
for both calls. Two returns say the same thing and are what -W accepts, which is
what make test builds with.
src/split held a delete.me and nothing else, so `split` was a binary that did
nothing. Of the 96 directories here, 33 are like that.

What it does now: writes a file into fixed sized pieces, named PREFIXaa, PREFIXab
and so on, or PREFIX00 with -d, and widens the suffix when the combinations at the
current width run out rather than failing.

## Measured against GNU 9.4

Twenty one cases, each running this binary and GNU's over the same input and
comparing the names produced, every file's contents and the exit status:

	default                      same
	-l 3                         same
	-d -l 3                      same
	-a 3 -l 3                    same
	-l 1                         same
	-l 4 -d                      same
	empty input                  same
	no trailing newline          same
	-b 4                         same
	-b 5                         same
	-b 10                        same
	-b 1                         same
	-C 4                         same
	-C 5                         same
	-C 1                         same
	-b 4 -a 1                    same
	explicit prefix              same
	two ways asked for at once   same
	-l 0, -b 0                   same
	no such file                 same

The two modes that are easy to get wrong are pinned down rather than guessed:

- `-b N` cuts where the byte count says, mid line if that is where it lands. With
  -b 1 over a 21 byte file GNU writes 21 files of one byte, and so does this.
- `-C N` is that cut with one difference: a line is never split, so a line that does
  not fit starts the next file. With -C 4 over ten two byte lines GNU puts four
  lines in a file and gives the last two a file each, because "9\n10\n" is 5 bytes;
  with -C 5 they share one file. With -C 1, where no line fits at all, GNU stops
  trying and cuts by bytes instead, giving the same 21 one byte files as -b 1. That
  last one was wrong here at first: this wrote whole lines, and comparing showed it.

A missing input file is reported with the same wording as GNU:

	split: cannot open 'nothere.txt' for reading: No such file or directory

## What is left, and why it is left

-n is not implemented, and says so rather than producing something plausible. It
takes two forms and neither is the even split the word "chunks" suggests: over ten
lines, -n l/7 gives 2,1,2,1,2,1,1 and -n 7 gives 2,1,2,1,2,1,1 lines. Three data
points are not enough to say what the rule is for every N, and a split that puts the
wrong bytes in the wrong file is worse than one that refuses.

--filter is not implemented for a different reason. It runs a command per piece, and
on Windows the only way this V has of starting a process from a command string is
os.system, which is deprecated and an error under -W, which is what make test
builds with. Shipping it would mean shipping a deprecated call.

One known difference in argument handling: -n 0 says "chunks are not implemented"
where GNU says "invalid number of chunks: ‘0’", and GNU quotes the number with the
curly quotes that this port uses straight ones for. The number is not parsed before
the refusal, so an invalid -n is reported the same way as a valid one.

## What did not change

Nothing else. `delete.me` stays where it is, as it does in the other 32 directories
that hold one.
@metif12 metif12 mentioned this pull request Oct 4, 2026
@JalonSolov
JalonSolov merged commit d1f0141 into vlang:main Oct 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants