Skip to content

csplit: report the size of the split the input failed in - #14295

Open
arbelonson-source wants to merge 2 commits into
uutils:mainfrom
arbelonson-source:fix/csplit-count-on-read-error
Open

csplit: report the size of the split the input failed in#14295
arbelonson-source wants to merge 2 commits into
uutils:mainfrom
arbelonson-source:fix/csplit-count-on-read-error

Conversation

@arbelonson-source

Copy link
Copy Markdown
Contributor

Fixes #13139.

csplit prints the size of each split as it closes it, but a read error returns straight out of the split loop, so the split that was open when the input went wrong is never accounted for:

$ csplit a_directory '/^a/'    # ours, before
csplit: read error: Is a directory

$ csplit a_directory '/^a/'    # GNU 9.11
csplit: read error: Is a directory
0

The two other ways a split can end early — MatchNotFound and LineOutOfRange — already call finish_split before returning their error, which is why csplit f '/^zzz/' prints its count today and this one does not. Read errors now take the same path, through a small helper on the writer that closes the split before handing the error on. That keeps the count subject to the same options as any other: --quiet silences it, -z drops the empty split instead of counting it, -k keeps the file it counted, and a %REGEXP% section that writes nowhere still prints nothing.

One difference remains, in ordering rather than content: GNU prints the count after the error, because it closes the split during its cleanup; we print it before, because the error is reported by the caller once csplit returns. The streams are separate, so this is only visible when both are pointed at the same terminal.

Testing

  • 24-case differential check against GNU 9.11 comparing stdout, stderr, exit code and the files left behind: a directory and /proc/self/mem as input, crossed with -k, -z, -s, -b, line patterns, regex patterns, {N} repeats and %REGEXP%, plus the ordinary successful splits and the no-match and out-of-range cases that had to keep their existing counts. All 24 match.
  • New test_read_error_reports_the_size_of_the_open_split covering the count under -k, -z and -s.
  • test_directory_input_file used stderr_only, which asserted the missing count; it now checks both streams and the GNU-matching output.
  • cargo test --features csplit --test tests -- test_csplit: 93 passed, 0 failed; cargo test -p uu_csplit: 41 passed.
  • cargo fmt --check and cargo clippy -p uu_csplit --all-targets: clean.

Disclosure

Prepared with AI assistance (Claude Opus 5, via Claude Code), per the AI policy in CONTRIBUTING.md. GNU's behaviour was established by running the installed GNU binary as a black box; I did not read GNU coreutils source. All testing was run locally.

`dd` puts the value it rejected into the message unquoted, so a byte the
terminal will not show is sent to the terminal as itself:

    $ dd status=$'\1'
    dd: invalid status level: <the byte>

GNU spells it out instead, and quotes the value even when it is ordinary:

    $ dd status=$'\1'
    dd: invalid status level: '\001'
    $ dd status=bogus
    dd: invalid status level: 'bogus'

GNU uses two quoting styles here, and which one goes where is visible from
the outside. The choices -- `status=`, `iflag=`, `oflag=`, `conv=` -- take
plain quoting, where an escape stays inside the quotes:

    dd: invalid status level: 'a\'b'

while numbers and operand names take shell syntax, where an unprintable
byte ends the quoted run and a quote in the value switches which quotes
are used:

    dd: invalid number: ''$'\001'
    dd: invalid number: "a'b"
    dd: unrecognized operand ''$'\001''=1'

Both are already in `uucore::quoting_style`, so this is a matter of asking
for them. The quotes move out of the message and into the quoting, which
is what supplies them for every value now.

Fixes uutils#13868.
`csplit` prints the size of each split as it closes it, but a read error
returns straight out of the split loop, so the split that was open when
the input went wrong is never accounted for:

    $ csplit a_directory '/^a/'
    csplit: read error: Is a directory

GNU closes it first, and prints its size the way it prints any other:

    $ csplit a_directory '/^a/'
    csplit: read error: Is a directory
    0

The two other ways a split can end early, `MatchNotFound` and
`LineOutOfRange`, already call `finish_split` before returning their
error. Read errors take the same path now, via a helper on the writer, so
`--quiet` still silences the count, `-z` still drops an empty split, and
`-k` still keeps the file it counted.

Fixes uutils#13139.
@codspeed-hq

codspeed-hq Bot commented Aug 31, 2026

Copy link
Copy Markdown

Merging this PR will degrade performance by 4.66%

❌ 2 regressed benchmarks
✅ 20 untouched benchmarks
⏩ 390 skipped benchmarks1

Warning

Please fix the performance issues or acknowledge them on CodSpeed.

Performance Changes

Mode Benchmark BASE HEAD Efficiency
Simulation csplit_line_number 56 ms 59.1 ms -5.19%
Simulation csplit_regex_pattern 73 ms 76.2 ms -4.13%

Tip

Investigate this regression by commenting @codspeedbot fix this regression on this PR, or directly use the CodSpeed MCP with your agent.


Comparing arbelonson-source:fix/csplit-count-on-read-error (d20a2d3) with main (2228600)2

Open in CodSpeed

Footnotes

  1. 390 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

  2. No successful run was found on main (3251833) during the generation of this report, so 2228600 was used instead as the comparison base. There might be some changes unrelated to this pull request in this report.

@github-actions

Copy link
Copy Markdown

GNU testsuite comparison:

Skipping an intermittent issue tests/date/date-locale-hour (passes in this run but fails in the 'main' branch)
Congrats! The gnu test tests/tail/tail-n0f is now passing!

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.

csplit: error doesn't exit program

1 participant