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

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

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

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

Side effect

QUOTING_STYLE=locale on a file named exactly `'` now emits `"'"`
(matching ls and GNU's locale style in the C locale) instead of the
previous custom `'\''`. test_quoting_style_locale updated to
reflect this; the change makes stat's behavior consistent with the
other utilities.

Tests

Added:

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

Updated:

  • `test_quoting_style_locale` — see side-effect note above.
$ cargo test --features 'stat' --test tests -- test_stat
test result: ok. 35 passed; 0 failed

🤖 Generated with Claude Code

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 e88dd21 to 7ef6ac2 Compare May 20, 2026 12:15
@github-actions
Copy link
Copy Markdown

GNU testsuite comparison:

GNU test failed: tests/stat/stat-fmt. tests/stat/stat-fmt is passing on 'main'. Maybe you have to rebase?
Skip an intermittent issue tests/date/date-locale-hour (fails in this run but passes in the 'main' branch)
Congrats! The gnu test tests/cut/cut-huge-range is now passing!
Congrats! The gnu test tests/rm/many-dir-entries-vs-OOM 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.

stat: %N fails with new line

1 participant