Since v9.10 (Feb 2026) --help always outputs bold and hyperlinks
even when output was not a tty, as this was thought more useful
with pagers and grep etc. This change makes the more conservative
choice of only outputting markup to tty so that users who do not
have less setup to display color (-R), or users who enable color
by default in grep, get improved output (though without URLs etc.)
* src/system.h (oputs_): Call isatty() when not
forcing markup with $IN_HELP2MAN. Also reverse the
double negatives to a more natural form.
* man/local.mk: Remove redundant assignment of $TERM.
* doc/coreutils.texi (common options): Mention that --help markup
requires output to be a tty, and that $IN_HELP2MAN overrides that.
* NEWS: Mention the change in behavior.
* NEWS: Mention the crash when invoking various programs --help option
when configured with --disable-bold-man-page-references and
--disable-manual-url.
This crash only occurs when configured with both
--disable-bold-man-page-references and --disable-manual-url.
* src/system.h (oprintf_): Add a va_start and va_end call surrounding
the call to vprintf.
Copyright-paperwork-exempt: Yes
$ ./configure --without-libgmp --quiet
$ dd bs=64k count=1 if=/dev/urandom of=58.in
$ time src/basenc-before --base58 58.in >/dev/null
real 0m6.588s
$ time src/basenc-after --base58 58.in >/dev/null
real 0m3.463s
* src/basenc.c (base58_encode): Remove an affirm that is very expensive
in the minigmp implementation. Essentially the same affirm was done
at the start of the function anyway.
* NEWS: Mention the improvement (including the impending similar
mpz_sizeinbase() removal from mini-gmp).
This is now possible due to the previous changes
as default mode subtracts one column, and
-c subtracts one character, and every character counts as one,
so there is no need to track the last_character_width
(which is problematic anyway in the presence of
multiple backspaces).
* src/old.c (adjust_column): Just use the locally determined width.
With POSIX (and the LI18NUX2000 patch) backspaces back up by 1 column.
This matches terminal output. Note less(1) and thus grotty(1)
use per character backup accounting for their legacy bold/underline
markup format, so we'll keep --characters to enable that mode if needed.
This also avoids the issue of multiple backspaces
using the width of a single previous character.
* src/fold.c (adjust_column): Without -c,
have backspace back up by 1 column.
* doc/coreutils.texi (fold invocation): Mention that --characters
does per character width backspace accounting.
* tests/fold/fold-characters.sh: Add/Adjust test cases.
* NEWS: Mention the change in behavior.
* src/chroot.c (main): When the primary group was inferred from the
uid outside the chroot, let a successful getpwuid() inside the chroot
replace it, as is already done for the uid and supplemental groups.
Previously the gid found outside persisted, so for example
'chroot --userspec=alice NEWROOT' ran with alice's uid from NEWROOT
but alice's gid from the host.
* NEWS: Mention the bug fix.
Link: https://github.com/coreutils/coreutils/pull/363
Without this change, this test would fail during 'make distcheck', for
example.
* tests/misc/exec-bad-unicode.sh: Skip the test if $abs_top_builddir has
spaces or special characters.
* cfg.mk (_tb_misc): Also match tests/misc/exec-bad-unicode.sh.
Reported by Bruno Haible.
* src/basenc.c (struct base58_context): Declare capacity before the buf
member and annotate buf with COUNTED_BY.
(base58_encode_ctx_finalize, base58_decode_ctx_finalize): Reset size and
capacity to zero when freeing buf and setting it to NULL, so the
counted-by invariant holds.
Copyright-paperwork-exempt: Yes
Link: https://github.com/coreutils/coreutils/pull/362
* tests/misc/no-fork.sh: Use an "unused" placeholder
so runcon is invoked with the correct number of arguments
where `id -Z` doesn't return anything.
This is needed since commit v9.12-53-g8555a51f2
cut, expand, fold, join, numfmt, unexpand, uniq, on non-GLIBC platforms
treated the \u0085 (next line) character as a blank character.
src/system.h (c32isvertspace): Include 0x0085 so c32issep() excludes it.
Also split out to ...
(c32isnewline): ... a new function, in case we need to
consider unicode newlines in isolation in future.
* tests/unexpand/mb.sh: Add a test case to both ensure
processing of \u0085 characters does not overflow a buffer,
and that those characters are not treated as separators.
* NEWS: Mention the fix.
Link: https://github.com/coreutils/coreutils/pull/361
* src/unexpand.c (unexpand): Clamp a negative c32width() to 1 when
accumulating a blank, matching the sibling non-blank path and expand
and fold. A separator whose display width is negative (e.g. U+0085
where c32issep() accepts it) left 'column' un-advanced, so the pending
blank buffer, sized for one byte of advance per blank, grew without
bound.
* NEWS: Mention the bug fix.
Link: https://github.com/coreutils/coreutils/pull/361
Generate ASCII and UTF-8 data:
$ yes $(yes eeeaae | head -n10K | paste -s -d,) | head -n10K > ll.in
$ yes $(yes éééááé | head -n9 | paste -s -d,) | head -n1M > mb.in
ASCII is 14x faster:
$ time src/wc-before -m < ll.in
real 0m1.801s
$ time src/wc-after -m < ll.in
real 0m0.123s
UTF-8 is 7x faster:
$ time src/wc-before -m < mb.in
real 0m1.399s
$ time src/wc-after -m < mb.in
real 0m0.189s
* src/wc.c (wc): Use the u8_count() routine like cut(1),
to optimize counting valid UTF-8 characters.
* tests/wc/wc.pl: Add a test case.
* NEWS: Mention the improvement.
* bootstrap.conf: Reference the new "utf8" module.
* cfg.mk: Adjust checks as per mbbuf.
* gl/lib/utf8.c: Include the implementation.
* gl/lib/utf8.h: Add is_utf8_charset() and u8_count().
Note we don't give is_utf8_charset external-linkage
due to its static cache.
* gl/local.mk: Reference the new module files.
* gl/modules/utf8: Add the module definition and dependencies.
* src/cut.c: Use is_utf8_charset() and u8_count().
* src/numfmt.c: Use is_utf8_charset().
* src/system.h: Remove is_utf8_charset() definition.
Commit v9.4-43-ga6064bb86 introduced this edge case
in handling multi-byte characters spanning the input buffer.
* src/wc.c (wc): Ensure we include the bytes previously read
but as yet unprocessed.
* tests/wc/mb-non-utf8.sh: Add a new test for miscounted GB118030 chars.
* tests/local.mk: Reference the new test.
* tests/wc/wc.pl: Add a new check for word count with a
UTF-8 space spanning a buffer boundary.
* NEWS: Mention the bug fix.
* tests/readlink/rl-1.sh: Add tests for the 'readlink' invocations that
would cause an infinite loop before commit b66f3e4e2 (realpath: fix an
infinite loop when resolving symbolic links, 2026-09-25). Also test the
behavior without an option, which would not cause the infinite loop.
* tests/misc/realpath.sh: Test the flags supported by 'realpath' that
would previously infinite loop before commit b66f3e4e2 (realpath: fix an
infinite loop when resolving symbolic links, 2026-09-25). Test an
invocation which would avoid the infinite loop by not touching the file.
* src/join.c (struct line): Use the COUNTED_BY attribute on the fields
member.
(struct seq): Use the COUNTED_BY attribute on the lines member.
(freeline): When setting a pointer using the COUNTED_BY attribute to
NULL, set its counter to zero.
Increases throughput when no delimiters found by up to 15x.
$ yes | head -n10M > sl.in
$ time LC_ALL=C src/cut-before -f10 -w sl.in >/dev/null
real 0m0.171s
$ time LC_ALL=C src/cut-after -f10 -w sl.in >/dev/null
real 0m0.011s
* src/cut.c (cut_fields_bytesearch): Also add a byte search
shortcut for the -w case in uni-byte locales.
Avoiding calling memchr2() for fields less than 16 bytes,
results in a 40% performance boost.
$ yes $(yes eeeaae | head -n9 | paste -s -d' ') | head -n1M > asw.in
$ time LC_ALL=C src/cut-before -f10 -w asw.in >/dev/null
real 0m0.254s
$ time LC_ALL=C src/cut-after -f10 -w asw.in >/dev/null
real 0m0.151s
* src/cut.c (find_c_blank): A wrapper around memchr2()
to avoid its significant setup cost.
These are the most common input encodings so worth optimizing for.
Performance was tested with GCC 16 on an i7-5600U with
CFLAGS='-march=native -O3 -flto' with the following setup:
$ yes | head -n10M > sl.in
$ yes $(yes eeeaae | head -n10K | paste -s -d' ') | head -n10K > llw.in
$ yes $(yes eeeaae | head -n9 | paste -s -d' ') | head -n1M > asw.in
$ yes $(yes éééááé | head -n9 | paste -s -d$'\xe2\x80\x83') |
head -n1M > mbw.in
for type in sl llw asw mbw; do
cat $type.in >/dev/null;
for imp in '-before' '-after'; do
echo ============ "${imp:-base}" $type ==============;
for d in -c; do
fields='-f1 -f10 -f100'
test "$d" = "-c" && { fields='-c1 -c10 -c100000'; d=''; }
for f in $fields; do
for loc in C.UTF-8; do
# Skip -b for UTF-8 as no different
test "$loc" = C.UTF-8 && echo "$f" | grep -q -- -b \
&& continue
# Skip multi-byte delimiter for C and not allowed
test "$loc" = C && test $(echo -n "$d" | wc -c) -ge 4 \
&& continue
bin=src/cut${imp}
LC_ALL=$loc $bin $f $d /dev/null 2>/dev/null &&
hyperfine --warmup=2 -m2 -M6 \
"LC_ALL=$loc $bin $f $d $type.in >/dev/null" ||
printf 'Benchmark 1: %s\n unsupported\n\n' \
"LC_ALL=$loc $bin $f $d $type.in >/dev/null"
done;
done;
done;
done;
done
After a little post-processing of the results, we get:
-- src/cut-before
| command | sl | llw | asw | mbw |
| --------------- | -------- | -------- | -------- | -------- |
| UTF8 -c1 | 70.7 ms | 103.5 ms | 26.9 ms | 48.6 ms |
| UTF8 -c10 | 55.1 ms | 103.9 ms | 48.6 ms | 127.3 ms |
| UTF8 -c100000 | 54.7 ms | 1.585 s | 156.9 ms | 555.9 ms |
-- src/cut-after
| command | sl | llw | asw | mbw |
| --------------- | -------- | -------- | -------- | -------- |
| UTF8 -c1 | 90.1 ms | 103.6 ms | 28.0 ms | 50.9 ms |
| UTF8 -c10 | 39.7 ms | 103.2 ms | 40.0 ms | 127.5 ms |
| UTF8 -c100000 | 39.0 ms | 104.4 ms | 18.5 ms | 30.3 ms |
* bootstrap.conf: Explicitly depend on unistr/u8-check.
* src/cut.c (cut_characters_mode): Quickly skip over unselected
ASCII/UTF-8 data.
* tests/cut/cut.pl: Add test cases.
Performance was tested with GCC 16 on an i7-5600U with
CFLAGS='-march=native -O3 -flto' with the following setup:
$ yes | head -n10M > sl.in
$ yes $(yes eeeaae | head -n10K | paste -s -d' ') | head -n10K > llw.in
$ yes $(yes eeeaae | head -n9 | paste -s -d' ') | head -n1M > asw.in
$ yes $(yes éééááé | head -n9 | paste -s -d$'\xe2\x80\x83') |
head -n1M > mbw.in
for type in sl llw asw mbw; do
cat $type.in >/dev/null;
for imp in '-before' '-after'; do
echo ============ "${imp:-base}" $type ==============;
for d in -c; do
fields='-f1 -f10 -f100'
test "$d" = "-c" && { fields='-c1 -c10 -c100000'; d=''; }
for f in $fields; do
for loc in C.UTF-8; do
# Skip -b for UTF-8 as no different
test "$loc" = C.UTF-8 && echo "$f" | grep -q -- -b \
&& continue
# Skip multi-byte delimiter for C and not allowed
test "$loc" = C && test $(echo -n "$d" | wc -c) -ge 4 \
&& continue
bin=src/cut${imp}
LC_ALL=$loc $bin $f $d /dev/null 2>/dev/null &&
hyperfine --warmup=2 -m2 -M6 \
"LC_ALL=$loc $bin $f $d $type.in >/dev/null" ||
printf 'Benchmark 1: %s\n unsupported\n\n' \
"LC_ALL=$loc $bin $f $d $type.in >/dev/null"
done;
done;
done;
done;
done
After a little post-processing of the results, we get:
-- src/cut-before
| command | sl | llw | asw | mbw |
| ------------- | -------- | -------- | -------- | -------- |
| UTF8 -c1 | 60.7 ms | 1.244 s | 123.5 ms | 515.2 ms |
| UTF8 -c10 | 39.3 ms | 1.248 s | 127.4 ms | 526.0 ms |
| UTF8 -c100000 | 39.4 ms | 1.242 s | 121.4 ms | 514.3 ms |
-- src/cut-after
| command | sl | llw | asw | mbw |
| ------------- | -------- | -------- | -------- | -------- |
| UTF8 -c1 | 70.9 ms | 105.5 ms | 27.0 ms | 47.6 ms |
| UTF8 -c10 | 55.7 ms | 105.7 ms | 48.2 ms | 126.8 ms |
| UTF8 -c100000 | 55.2 ms | 1.596 s | 158.8 ms | 555.1 ms |
* src/cut.c (cut_characters_mode): Shortcut the line delimiter
search if finished processing characters.
* tests/cut/cut.pl: Add a test case.
* tests/printf/printf.sh: Exercise %a without depending on a
particular hex form by round-tripping through %.10f.
Also check the 0x/0X prefix.
Suggested by Pádraig Brady.
Reported by Brian Foster in:
<https://bugs.launchpad.net/ubuntu/+source/coreutils/+bug/2161155>
* gnulib: Update to get commit b18879b9ed (canonicalize: Fix a missing
check for symlink loops., 2026-09-25)
* tests/misc/realpath.sh: Add a call to getlimits_. Add a test case.
* NEWS: Mention the bug fix.
The assert function doesn't always cause the program to exit, so it
can't be marked _Noreturn. Therefore, even affirm (false), when it
expands to assert (false), will warn about an implicit fallthrough.
* src/cut.c (main): Move the affirm (false) case to the end of the
switch statement.
* src/numfmt.c (simple_strtod_fatal): Likewise.
* src/tr.c (string2_extend): Remove the call to unreachable () default
and combine it with the affirm (false) case.
The following errors were seen on NetBSD 11.99:
Setting LC_CTYPE to 'de_DE' failed setlocale(3)
Setting LC_MESSAGES to 'de_DE' failed setlocale(3)
Setting LC_MONETARY to 'de_DE' failed setlocale(3)
Setting LC_NUMERIC to 'de_DE' failed setlocale(3)
Setting LC_TIME to 'de_DE' failed setlocale(3)
* tests/date/date.pl: Ignore setlocale() errors seen on NetBSD 11.99.
Reported at https://bugs.gnu.org/81923
This was seen with a NetBSD failure in the pipe-f2.sh test
due to it clearing the EOF indication on a fifo, and thus
blocking in the subsequent read. That was discussed at:
https://lists.gnu.org/r/coreutils/2026-04/msg00129.htmlhttps://bugs.gnu.org/81923
Note the NetBSD issue may be addressed in NetBSD 12 as
discussed in the bug above.
This is more defensive/consistent in the case
where a subsequent read on a file blocks.
We might be operating on non fifos after all.
* src/tail.c (tail_forever): Flush output before we might block.
* tests/tail/pipe-f2.sh: Test non inotify path on all platforms.
* NEWS: Mention the improvement.
zstd compressed sources are less than half the size
of gzip coompressed sources, so are a better secondary
distribution format in addition to xz.
* README-prereq: Remove gzip, Add zstd.
* bootstrap.conf (buildreq): Likewise.
Bump automake version check to 1.16.2 (released 2020).
* configure.ac (AM_INIT_AUTOMAKE): Disable gz, add dist-zstd.
Bump automake version requirement to 1.16.2.
* cfg.mk: Use compression level 18, and multi-threading.
* NEWS: Mention the change.