Repository navigation
grep: spell walked paths with / on Windows when UUTILS_PATH_SEPARATOR=/ - #143
mutewinter wants to merge 1 commit into
Conversation
A recursive search on Windows joins the paths it walks with a backslash, so grep -r prints deep\sub\c.txt:... where MSYS2, Cygwin and Git Bash users, and scripts written for them, expect deep/sub/c.txt. With UUTILS_PATH_SEPARATOR=/ the walked paths use / instead. Operands named on the command line keep their spelling, as GNU grep prints them as given. Other values, and other platforms, are unchanged. It also accepts UUTILS_PATH_SEPARATOR=slash, because MSYS2 and Git Bash rewrite an environment value of a bare / into their install folder before a native program sees it.
There was a problem hiding this comment.
🟡 Changes recommended
Lossy path conversion can prevent valid Windows files from being opened or cause a different file to be searched.
1 open finding
What changed in this PR
Adds opt-in forward-slash paths for recursive Windows searches without changing command-line file operands.
Changes:
- Recognizes
UUTILS_PATH_SEPARATOR=/andslashon Windows. - Converts walked paths while preserving verbatim and device paths.
- Adds four separator behavior tests.
| File | Description |
|---|---|
| tests/test_grep.rs | Tests recursive output and separator settings. |
| src/searcher.rs | Converts walked paths to forward slashes. |
| src/lib.rs | Reads the Windows separator preference. |
🧠 Review effort: Balanced
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| let s = path.to_string_lossy(); | ||
| if !s.contains('\\') || s.starts_with("\\\\?\\") || s.starts_with("\\\\.\\") { | ||
| return None; | ||
| } | ||
| Some(std::path::PathBuf::from(s.replace('\\', "/"))) |
There was a problem hiding this comment.
The AI is right and wrong.
The encoding of Path is an implementation detail, but we can relatively safely assume that it'll always be some superset of ASCII (currently WTF-8), as long as we have some unit tests to cover it.
This allows you to inspect the path as an OsString and directly mutate the underlying bytes. Don't use encode_wide for something like this - that's really stupid for it to suggest.
Merging this PR will not alter performance
|
| /// Join the paths a recursive search walks with `/` rather than `\`, as | ||
| /// `UUTILS_PATH_SEPARATOR=/` asks, or `=slash`, which MSYS2 and Git Bash | ||
| /// pass through unchanged where they rewrite a bare `/`. |
There was a problem hiding this comment.
I don't understand this comment.
| let s = path.to_string_lossy(); | ||
| if !s.contains('\\') || s.starts_with("\\\\?\\") || s.starts_with("\\\\.\\") { | ||
| return None; | ||
| } | ||
| Some(std::path::PathBuf::from(s.replace('\\', "/"))) |
There was a problem hiding this comment.
The AI is right and wrong.
The encoding of Path is an implementation detail, but we can relatively safely assume that it'll always be some superset of ASCII (currently WTF-8), as long as we have some unit tests to cover it.
This allows you to inspect the path as an OsString and directly mutate the underlying bytes. Don't use encode_wide for something like this - that's really stupid for it to suggest.
|
FWIW, I'm not happy with this PR aside from my review comments. I fail to see the point of |

Related: uutils/findutils#906 (same variable for
find), uutils/coreutils#12742, microsoft/coreutils#107.Problem
A recursive search on Windows prints the paths it walks with
\:MSYS2, Cygwin and Git Bash users, and programs that drive grep from a POSIX-style shell, expect
reports/b.mdand have no way to ask for it. A tool parsing the output cannot just replace\afterwards, because the matched line may contain backslashes of its own.Cause
Searcher::process_dirhandsprocess_filetheDirEntry::path()that walkdir built withPath::join, which uses\on Windows; the output code prints that path as is.Fix
A
cfg(windows)forward_slashesfield onConfig, set whenUUTILS_PATH_SEPARATOR=/, and inprocess_dirthe walked path is respelled with/before it reachesprocess_file, so the prefix,-l/-L/-cnames and error messages agree. It also acceptsslash, because MSYS2 and Git Bash rewrite an environment variable whose value is a bare/into their install folder before a native program sees it (UUTILS_PATH_SEPARATOR=/ python -c ...in Git Bash readsC:/Program Files/Git/, checked with 2.53.0.windows.2); the users the variable is for could not otherwise set it. This mirrors ripgrep's--path-separator; the variable keeps GNU's option space untouched and is the same one proposed for uutilsfind.Scope
Unchanged: the default on every platform; values other than
/; non-Windows builds; file operands named on the command line, which GNU prints as given; verbatim\\?\and device\\.\paths.Tests
Four tests in
tests/test_grep.rs:-routput,-rloutput with/and withslash, and that another value keepsstd::path::MAIN_SEPARATOR. On Linux and macOS the first three assert existing behavior; the Windows CI leg exercises the change. Full CI (including windows-latest) and GnuTests pass in my fork;cargo clippy --all-targets -- -D warningsis clean for the host andx86_64-pc-windows-gnu.Authored with Claude Opus 5.5