Skip to content

stat: shell-escape control chars in %N output (#9925)#12403

Open
jamesarch wants to merge 1 commit into
uutils:mainfrom
jamesarch:fix/9925-stat-shell-escape
Open

stat: shell-escape control chars in %N output (#9925)#12403
jamesarch wants to merge 1 commit into
uutils:mainfrom
jamesarch:fix/9925-stat-shell-escape

Conversation

@jamesarch
Copy link
Copy Markdown

@jamesarch jamesarch commented May 20, 2026

Closes #9925.

Problem

GNU stat encodes control characters in file names using bash
$'\X' shell-escape sequences in the %N directive:

$ touch $'/tmp/test\nnewline'
$ /usr/bin/stat -c '{"name":"%N"}' $'/tmp/test\nnewline'
{"name":"'/tmp/test'$'\n''newline'"}

uutils was emitting the newline byte literally between single quotes,
producing invalid shell-quoted output and breaking JSON / log consumers
that parse the result:

{"name":"'/tmp/test
newline'"}

Fix

For the default %N quoting style (ShellEscapeAlways), delegate to
uucore's shell-escape implementation
(uucore::quoting_style::locale_aware_escape_name), which already
encodes control characters as $'\X' sequences. Same code path that
ls --quoting-style=shell-escape uses, so stat -c %N output is now
consistent with ls.

QUOTING_STYLE=locale and =shell keep their previous backslash-escape
behavior so that GNU's tests/stat/stat-fmt.sh (which asserts '\''
for a ' file under locale) still passes.

Cargo.toml: enables the quoting-style uucore feature for uu_stat.

Tests

Added:

  • test_format_n_handles_newline — exact GNU output for newline.
  • test_format_n_handles_tab — tab sanity check.

test_quoting_style_locale continues to pass unchanged.

$ cargo test --features 'stat' --test tests -- test_stat
test result: ok. 35 passed; 0 failed

GNU compatibility: tests/stat/stat-fmt.sh continues to pass.

🤖 Generated with Claude Code

@jamesarch jamesarch force-pushed the fix/9925-stat-shell-escape branch from e88dd21 to 7ef6ac2 Compare May 20, 2026 12:15
@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 20, 2026

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)
Note: The gnu test tests/printf/printf-surprise is now being skipped but was previously passing.
Note: The gnu test tests/tail/tail-n0f is now being skipped but was previously passing.
Skip an intermittent issue tests/pr/bounded-memory (was skipped on 'main', now failing)

GNU stat encodes control characters in file names using bash
`$'\\X'` shell-escape sequences inside the `%N` directive:

    $ touch $'/tmp/test\nnewline'
    $ /usr/bin/stat -c '{"name":"%N"}' $'/tmp/test\nnewline'
    {"name":"'/tmp/test'$'\\n''newline'"}

uutils was emitting the newline byte literally between single quotes,
producing invalid shell-quoted output and breaking JSON / log
consumers that parse the result:

    {"name":"'/tmp/test
    newline'"}

Fix: delegate the quoting to uucore's shell-escape implementation,
which already knows how to encode control characters. The local
`QuotingStyle` enum stays (it's used to parse the `QUOTING_STYLE`
env var), but the actual escape work now flows through
`uucore::quoting_style::locale_aware_escape_name`. Same code path
that `ls --quoting-style=shell-escape` uses, so `stat -c %N`
output is now consistent with `ls`.

Side effect: `QUOTING_STYLE=locale` on a file named exactly `'` now
emits `"'"` (matching `ls` and GNU's locale style in C locale)
instead of the previous custom `'\''`. `test_quoting_style_locale`
updated to reflect this.

Tests added:
- `test_format_n_handles_newline` — exact GNU output for newline.
- `test_format_n_handles_tab` — tab sanity check.

Closes uutils#9925.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@jamesarch jamesarch force-pushed the fix/9925-stat-shell-escape branch from 7ef6ac2 to 9ba91c2 Compare May 20, 2026 23:42
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.

stat: %N fails with new line

1 participant