Skip to content

pr: stop dropping lines the columns do not divide evenly - #14088

Open
luantaraschi wants to merge 1 commit into
uutils:mainfrom
luantaraschi:fix/pr-column-partial-page
Open

pr: stop dropping lines the columns do not divide evenly#14088
luantaraschi wants to merge 1 commit into
uutils:mainfrom
luantaraschi:fix/pr-column-partial-page

Conversation

@luantaraschi

Copy link
Copy Markdown
Contributor

pr in column mode drops whatever the columns cannot divide evenly.

$ seq 9 | pr -t -2 -w 20      # GNU coreutils 9.7
1         6
2         7
3         8
4         9
5

$ seq 9 | pr -t -2 -w 20      # uutils, main at 9875296
1               5
2               6
3               7
4               8

The ninth line is gone. A page holding fewer lines than it has columns comes out empty instead:

$ printf 'a\n' | pr -t -3 | wc -c
2      # GNU
0      # uutils

seq 9 | pr -2 -l 12 ends the same way, on an empty page 3 where GNU prints the ninth line.

It comes down to one line, src/uu/pr/src/pr.rs:1408:

let num_rows = lines.len() / columns;

Integer division truncates, so nine lines over two columns give four rows and the ninth never gets a cell.

Rounding up gets closer but still misses, because GNU balances the columns: each one takes the ceiling of what is still unplaced over the number of columns left to fill, so nine lines over four columns come out 3, 2, 2, 2 rather than 3, 3, 3, 0. I read that rule off the output of /usr/bin/pr rather than its source, then checked it for every column count from 1 to 7 against every input length from 0 to 39, down and across. 560 cases, no mismatch.

Two smaller things came with the fix. The short-file branch was tested before the across-mode one, so pr -a laid a partly filled page out downwards; and the last row of such a page stops part way through, so it needs its line separator without a trailing column separator.

What I ran

All of it in a Debian container where /usr/bin/pr is GNU coreutils 9.7.

I ran 425 combinations of options and input length against GNU, this branch, and a build of main. main agreed with GNU in 138 of them and this branch agrees in 370, so 232 newly agree and none regressed. The comparison ignores column padding: uutils does not match GNU there, and test_columns already carries a TODO about it.

Another 574 combinations went through a line count check, comparing how many input lines survive the round trip. None lost.

cargo test --features pr --no-default-features test_pr passes 83. The four new tests fail on main and pass here, the 79 that were already there are untouched, and no expected fixture changed.

cargo fmt -p uu_pr -- --check and cargo clippy -p uu_pr --all-targets come back clean. clippy -D warnings still trips on the generated uucore/embedded_locales.rs, which fails the same way for a crate I did not touch.

I left the padding alone. GNU pads with tabs and leaves the last column unpadded while uutils pads with spaces, which is a different problem.

to_table_short_file built the table with lines.len() / columns rows, so a
page whose line count is not a multiple of the column count lost the
remainder. `seq 9 | pr -2` dropped the ninth line, and a page holding fewer
lines than it has columns came out empty.

Spread the lines the way GNU pr does instead: every column takes the ceiling
of what is still unplaced over the number of columns left to fill, which
leaves nothing over and keeps the longer columns on the left.

Two things followed from that. The short-file branch was tested before the
across-mode branch, so `pr -a` read a partly filled page downwards, and the
last row of such a page needs its line terminator without a trailing column
separator.
Copilot AI lite review requested due to automatic review settings August 23, 2026 04:34

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@codspeed-hq

codspeed-hq Bot commented Aug 23, 2026

Copy link
Copy Markdown

Merging this PR will degrade performance by 0.4%

⚠️ Different runtime environments detected

Some benchmarks with significant performance changes were compared across different runtime environments,
which may affect the accuracy of the results.

Open the report in CodSpeed to investigate

⚡ 2 improved benchmarks
❌ 2 regressed benchmarks
✅ 353 untouched benchmarks
⏩ 50 skipped benchmarks1

Warning

Please fix the performance issues or acknowledge them on CodSpeed.

Performance Changes

Mode Benchmark BASE HEAD Efficiency
Simulation du_summarize_balanced_tree[(5, 4, 10)] 16 ms 16.8 ms -4.69%
Simulation du_wide_tree[(5000, 500)] 19.4 ms 20.3 ms -4.1%
Simulation cksum_crc32b 39.7 ms 38.1 ms +4.19%
Simulation cksum_sysv 68.4 ms 66.2 ms +3.32%

Tip

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


Comparing luantaraschi:fix/pr-column-partial-page (2be25be) with main (9875296)

Open in CodSpeed

Footnotes

  1. 50 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.

@github-actions

Copy link
Copy Markdown

GNU testsuite comparison:

Skip an intermittent issue tests/date/date-locale-hour (fails in this run but passes in the 'main' branch)
Skipping an intermittent issue tests/cut/bounded-memory (passes in this run but fails in the 'main' branch)
Skipping an intermittent issue tests/pr/bounded-memory (passes in this run but fails in the 'main' branch)
Skipping an intermittent issue tests/tail/tail-n0f (passes in this run but fails in the 'main' branch)
Note: The gnu test tests/cut/cut-huge-range is now being skipped but was previously passing.
Note: The gnu test tests/seq/seq-epipe is now being skipped but was previously 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.

2 participants