Skip to content

printf: treat a -- past the format as an argument - #14394

Open
Socialpranker wants to merge 1 commit into
uutils:mainfrom
Socialpranker:printf-double-dash-argument
Open

printf: treat a -- past the format as an argument#14394
Socialpranker wants to merge 1 commit into
uutils:mainfrom
Socialpranker:printf-double-dash-argument

Conversation

@Socialpranker

Copy link
Copy Markdown
Contributor

printf lost a -- operand that came after the format string:

$ printf '%s\n' --            # GNU
--
$ printf '%s\n' --            # uutils, before
                              # (empty line)

$ printf '%s %s\n' -- --      # GNU
-- --
$ printf '%s %s\n' -- --      # uutils, before
%s
----

$ printf '%s' --help          # GNU
--help
$ printf '%s' --help          # uutils, before
<the help text>

--help spells out the two mutually exclusive forms:

Usage: printf FORMAT [ARGUMENT]...
or: printf OPTION

Options are an alternative to FORMAT, not something that can follow it. So
GNU looks for --, --help and --version only in the very first
operand; from FORMAT onwards everything is data, including further --.

The fix

print_formatted handed the arguments to clap unchanged, and clap ends the
options at the first -- it sees anywhere, so a -- in argument position
was consumed. A new mark_end_of_options inserts an explicit -- in front
of the format operand when the caller did not already write one there,
which pins the terminator to the only place GNU accepts it. A leading
--help/--version is stepped over so both still work as the sole
operand; past that point they reach the formatter as ordinary arguments.

The change is confined to argument marshalling — the formatter, the
diagnostics capture (which still sees the arguments as typed) and the
excess-argument warning are untouched.

How the GNU behavior was established

By running the installed GNU coreutils 9.11 binary (Homebrew, gprintf) as
a black box: printf -- '%s\n' a, printf '%s\n' --,
printf '%s %s\n' -- --, printf -- -- x, printf -- --help,
printf '%s' --help, printf -- --version, printf '%s' -- --version,
printf --help, printf --version, printf, printf -v, and diffing
against uutils. I did not read GNU coreutils source.

Testing

  • New tests in tests/by-util/test_printf.rs:
    leading_double_dash_ends_the_options,
    double_dash_after_the_format_is_an_argument,
    double_dash_as_the_format_is_printed_literally,
    help_and_version_past_the_format_are_arguments. Mutation-checked:
    reverting printf.rs alone makes two of them fail.
  • cargo test --features printf --test tests test_printf: 145 passed, 0
    failed (141 before, plus the four new ones).
  • cargo clippy -p uu_printf --all-targets -- -D warnings: clean.
  • cargo fmt --check: clean.
  • Differential A/B against GNU coreutils 9.11 over 41 printf
    invocations: mismatches 4 -> 3, no case regressed. (The two remaining
    real mismatches are unrelated: %a formatting, and a harness quoting
    artifact.)

Disclosure

Prepared with AI assistance (Claude Opus 5, via Claude Code), per the AI
policy in CONTRIBUTING.md. Every GNU behavior quoted above came from
running the installed binary, not from reading GPL source. All testing was
run locally.

Only a `--` written before FORMAT ends the options; afterwards every
operand is data, so `printf '%s\n' --` prints `--`. clap swallowed the
first `--` wherever it appeared, dropping the operand. The same holds for
`--help` and `--version`, which GNU honors only as the first operand.
@github-actions

github-actions Bot commented Sep 5, 2026

Copy link
Copy Markdown

GNU testsuite comparison:

Skipping an intermittent issue tests/pr/bounded-memory (passes in this run but fails in the 'main' branch)
Skipping an intermittent issue tests/rm/isatty (passes in this run but fails in the 'main' branch)
Note: The gnu test tests/rm/many-dir-entries-vs-OOM is now being skipped but was previously passing.
Note: The gnu test tests/tail/tail-n0f 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.

1 participant