diff --git a/Cargo.toml b/Cargo.toml
index 5d7bbad9b..85aad89a5 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -128,6 +128,7 @@ uzers = "0.12.1"
windows-sys = { version = "0.61.2", features = [
"Win32_System_Console",
"Win32_Foundation",
+ "Win32_Storage_FileSystem",
] }
[build-dependencies]
diff --git a/README.md b/README.md
index fea6e62bf..24a6b68db 100644
--- a/README.md
+++ b/README.md
@@ -107,7 +107,7 @@ eza’s options are almost, but not quite, entirely unlike `ls`’s. Quick overv
- **-x**, **--across**: sort the grid across, rather than downwards
- **-F**, **--classify=(when)**: display type indicator by file names (always, auto, never)
- **--colo[u]r=(when)**: when to use terminal colours (always, auto, never)
-- **--colo[u]r-scale=(field)**: highlight levels of `field` distinctly(all, age, size)
+- **--colo[u]r-scale=(field)**: highlight levels of `field` distinctly (all, age, size)
- **--color-scale-mode=(mode)**: use gradient or fixed colors in --color-scale. valid options are `fixed` or `gradient`
- **--icons=(when)**: when to display icons (always, auto, never)
- **--hyperlink=(when)**: when to display entries as hyperlinks (always, auto, never)
@@ -122,6 +122,7 @@ eza’s options are almost, but not quite, entirely unlike `ls`’s. Quick overv
Click to expand
- **-a**, **--all**: show hidden and 'dot' files
+- **--show-dotfiles**: show dot-prefixed files without showing other hidden files
- **-d**, **--treat-dirs-as-files**: list directories like regular files
- **-L**, **--level=(depth)**: limit the depth of recursion
- **-r**, **--reverse**: reverse the sort order
diff --git a/completions/bash/eza b/completions/bash/eza
index f1033d10e..b2adc6bd4 100644
--- a/completions/bash/eza
+++ b/completions/bash/eza
@@ -4,7 +4,7 @@ _eza() {
prev=${COMP_WORDS[COMP_CWORD-1]}
case "$prev" in
- --help|-v|--version|--smart-group)
+ --help|-v|--version|--smart-group|--show-dotfiles)
return
;;
diff --git a/completions/fish/eza.fish b/completions/fish/eza.fish
index 98e538b13..0b26d4759 100644
--- a/completions/fish/eza.fish
+++ b/completions/fish/eza.fish
@@ -57,6 +57,7 @@ complete -c eza -l group-directories-last -d "Sort directories after other files
complete -c eza -l git-ignore -d "Ignore files mentioned in '.gitignore'"
complete -c eza -s a -l all -d "Show hidden and 'dot' files. Use this twice to also show the '.' and '..' directories"
complete -c eza -s A -l almost-all -d "Equivalent to --all; included for compatibility with `ls -A`"
+complete -c eza -l show-dotfiles -d "Show dot-prefixed files without showing other hidden files"
complete -c eza -s d -l treat-dirs-as-files -d "List directories like regular files"
complete -c eza -s L -l level -d "Limit the depth of recursion" -x -a "1 2 3 4 5 6 7 8 9"
complete -c eza -s w -l width -d "Limits column output of grid, 0 implies auto-width"
diff --git a/completions/nush/eza.nu b/completions/nush/eza.nu
index 6558c9189..8e7e1f6d7 100644
--- a/completions/nush/eza.nu
+++ b/completions/nush/eza.nu
@@ -25,6 +25,7 @@ export extern "eza" [
--git-ignore # Ignore files mentioned in '.gitignore'
--all(-a) # Show hidden and 'dot' files. Use this twice to also show the '.' and '..' directories
--almost-all(-A) # Equivalent to --all; included for compatibility with `ls -A`
+ --show-dotfiles # Show dot-prefixed files without showing other hidden files
--treat-dirs-as-files(-d) # List directories like regular files
--level(-L): string # Limit the depth of recursion
--width(-w) # Limits column output of grid, 0 implies auto-width
diff --git a/completions/pwsh/_eza.ps1 b/completions/pwsh/_eza.ps1
index b7d3fd703..798c3ea7e 100644
--- a/completions/pwsh/_eza.ps1
+++ b/completions/pwsh/_eza.ps1
@@ -172,6 +172,7 @@ Register-ArgumentCompleter -Native -CommandName 'eza' -ScriptBlock {
[CompletionResult]::new('--all' ,'filter' , [CompletionResultType]::ParameterName, 'show hidden and ''dot'' files. Use this twice to also show the ''.'' and ''..'' directories')
# [CompletionResult]::new('-A' ,'filter' , [CompletionResultType]::ParameterName, 'equivalent to --all; included for compatibility with `ls -A`')
# [CompletionResult]::new('--almost-all' ,'filter' , [CompletionResultType]::ParameterName, 'equivalent to --all; included for compatibility with `ls -A`')
+ [CompletionResult]::new('--show-dotfiles' ,'filter' , [CompletionResultType]::ParameterName, 'show dot-prefixed files without showing other hidden files')
# [CompletionResult]::new('-d' ,'filter' , [CompletionResultType]::ParameterName, 'list directories as files; don''t list their contents')
[CompletionResult]::new('--treat-dirs-as-files' ,'filter' , [CompletionResultType]::ParameterName, 'list directories as files; don''t list their contents')
# [CompletionResult]::new('-D' ,'filter' , [CompletionResultType]::ParameterName, 'list only directories')
diff --git a/man/eza.1.md b/man/eza.1.md
index dc14382fb..ce220f8a3 100644
--- a/man/eza.1.md
+++ b/man/eza.1.md
@@ -144,6 +144,9 @@ Use this twice to also show the ‘`.`’ and ‘`..`’ directories.
`-A`, `--almost-all`
: Equivalent to --all; included for compatibility with `ls -A`.
+`--show-dotfiles`
+: Show dot-prefixed files without showing other hidden files.
+
`-d`, `--treat-dirs-as-files`
: This flag, inherited from `ls`, changes how `eza` handles directory arguments.
diff --git a/src/fs/dir.rs b/src/fs/dir.rs
index 70ff56e80..56801dec5 100644
--- a/src/fs/dir.rs
+++ b/src/fs/dir.rs
@@ -89,6 +89,8 @@ impl Dir {
inner: self.contents.iter(),
dir: self,
dotfiles: dots.shows_dotfiles(),
+ #[cfg(windows)]
+ windows_hidden: dots.shows_windows_hidden(),
dots: dots.dots(),
git,
git_ignoring,
@@ -122,6 +124,10 @@ pub struct Files<'dir, 'ig> {
/// Whether to include dotfiles in the list.
dotfiles: bool,
+ #[cfg(windows)]
+ /// Whether Windows hidden-attribute entries should be visible.
+ windows_hidden: bool,
+
/// Whether the `.` or `..` directories should be produced first, before
/// any files have been listed.
dots: DotsNext,
@@ -184,7 +190,7 @@ impl<'dir> Files<'dir, '_> {
// Windows has its own concept of hidden files, when dotfiles are
// hidden Windows hidden files should also be filtered out
#[cfg(windows)]
- if !self.dotfiles && file.attributes().map_or(false, |a| a.hidden) {
+ if !self.windows_hidden && file.attributes().is_some_and(|a| a.hidden) {
continue;
}
@@ -244,6 +250,9 @@ pub enum DotFilter {
/// Show files and dotfiles, but hide `.` and `..`.
Dotfiles,
+ /// Show dotfiles by name only, but keep platform hidden-attribute files hidden.
+ DotfilesByName,
+
/// Just show files, hiding anything beginning with a dot.
#[default]
JustFiles,
@@ -255,16 +264,84 @@ impl DotFilter {
match self {
Self::JustFiles => false,
Self::Dotfiles => true,
+ Self::DotfilesByName => true,
Self::DotfilesAndDots => true,
}
}
+ #[cfg(windows)]
+ /// Whether this filter should reveal Windows hidden-attribute entries.
+ fn shows_windows_hidden(self) -> bool {
+ cfg!(windows) && matches!(self, Self::Dotfiles | Self::DotfilesAndDots)
+ }
+
/// Whether this filter should add dot directories to a listing.
fn dots(self) -> DotsNext {
match self {
Self::JustFiles => DotsNext::Files,
Self::Dotfiles => DotsNext::Files,
+ Self::DotfilesByName => DotsNext::Files,
Self::DotfilesAndDots => DotsNext::Dot,
}
}
}
+
+#[cfg(all(test, windows))]
+mod tests {
+ use super::*;
+ use std::fs;
+ use std::os::windows::ffi::OsStrExt;
+ use std::path::Path;
+ use std::time::{SystemTime, UNIX_EPOCH};
+ use windows_sys::Win32::Storage::FileSystem::{
+ FILE_ATTRIBUTE_HIDDEN, GetFileAttributesW, SetFileAttributesW,
+ };
+
+ fn unique_temp_dir() -> std::path::PathBuf {
+ let nanos = SystemTime::now()
+ .duration_since(UNIX_EPOCH)
+ .expect("time went backwards")
+ .as_nanos();
+ let path = std::env::temp_dir().join(format!("eza-show-dotfiles-{nanos}"));
+ fs::create_dir_all(&path).expect("failed to create temp dir");
+ path
+ }
+
+ fn set_hidden(path: &Path) {
+ let wide = path
+ .as_os_str()
+ .encode_wide()
+ .chain(std::iter::once(0))
+ .collect::>();
+ unsafe {
+ let attrs = GetFileAttributesW(wide.as_ptr());
+ assert_ne!(attrs, u32::MAX);
+ assert_ne!(
+ SetFileAttributesW(wide.as_ptr(), attrs | FILE_ATTRIBUTE_HIDDEN),
+ 0
+ );
+ }
+ }
+
+ #[test]
+ fn show_dotfiles_does_not_show_windows_hidden_attributes() {
+ let path = unique_temp_dir();
+ fs::write(path.join(".dotfile"), "").unwrap();
+ fs::write(path.join("_underscore"), "").unwrap();
+ fs::write(path.join("hidden.txt"), "").unwrap();
+ set_hidden(&path.join("hidden.txt"));
+
+ let dir = Dir::read_dir(path.clone()).unwrap();
+
+ let names: Vec<_> = dir
+ .files(DotFilter::DotfilesByName, None, false, false, false)
+ .map(|file| file.name)
+ .collect();
+
+ assert!(names.contains(&".dotfile".to_string()));
+ assert!(names.contains(&"_underscore".to_string()));
+ assert!(!names.contains(&"hidden.txt".to_string()));
+
+ let _ = fs::remove_dir_all(path);
+ }
+}
diff --git a/src/options/filter.rs b/src/options/filter.rs
index 1ecea7d8a..2c2a2ccfe 100644
--- a/src/options/filter.rs
+++ b/src/options/filter.rs
@@ -99,14 +99,17 @@ impl DotFilter {
pub fn deduce(matches: &ArgMatches, strict: bool) -> Result {
let all_count = matches.get_count("all");
let has_almost_all = matches.get_flag("almost-all");
+ let show_dotfiles = matches.get_flag("show-dotfiles");
- match (all_count, has_almost_all) {
- (0, false) => Ok(Self::JustFiles),
+ if has_almost_all {
+ return Ok(Self::Dotfiles);
+ }
- // either a single --all or at least one --almost-all is given
- (1, _) | (0, true) => Ok(Self::Dotfiles),
- // more than one --all
- (c, _) => {
+ match all_count {
+ 0 if show_dotfiles => Ok(Self::DotfilesByName),
+ 0 => Ok(Self::JustFiles),
+ 1 => Ok(Self::Dotfiles),
+ c => {
if matches.get_flag("tree") {
Err(OptionsError::TreeAllAll)
} else if strict && c > 2 {
@@ -250,6 +253,22 @@ mod tests {
);
}
+ #[test]
+ fn deduce_dot_filter_show_dotfiles() {
+ assert_eq!(
+ DotFilter::deduce(&mock_cli(vec!["--show-dotfiles"]), false),
+ Ok(DotFilter::DotfilesByName)
+ );
+ }
+
+ #[test]
+ fn deduce_dot_filter_show_dotfiles_and_all() {
+ assert_eq!(
+ DotFilter::deduce(&mock_cli(vec!["--show-dotfiles", "--all"]), false),
+ Ok(DotFilter::Dotfiles)
+ );
+ }
+
#[test]
fn deduce_sort_field_default() {
assert_eq!(
diff --git a/src/options/parser.rs b/src/options/parser.rs
index 231c27968..64d743acc 100644
--- a/src/options/parser.rs
+++ b/src/options/parser.rs
@@ -94,6 +94,7 @@ pub fn get_command() -> clap::Command {
.next_help_heading("FILTERING OPTIONS")
.arg(arg!(-a --all... "show hidden files. Use this twice to also show the '.' and '..' directories"))
.arg(arg!(-A --"almost-all" "equivalent to --all; included for compatibility with `ls -A`"))
+ .arg(arg!(--"show-dotfiles" "show dot-prefixed files without showing other hidden files"))
.arg(arg!(-d --"treat-dirs-as-files" "treat directories as files; don't list their contents")
.alias("list-dirs") // TODO: compat alias to remove (above flag published in v0.23.4 / 2025-10-03)
.conflicts_with_all(["recurse", "tree"]))
diff --git a/tests/ptests/ptest_2439b7d68089135b.stdout b/tests/ptests/ptest_2439b7d68089135b.stdout
index 5bd9aa127..2ecdd11ee 100644
--- a/tests/ptests/ptest_2439b7d68089135b.stdout
+++ b/tests/ptests/ptest_2439b7d68089135b.stdout
@@ -32,6 +32,7 @@ DISPLAY OPTIONS:
FILTERING OPTIONS:
-a, --all... show hidden files. Use this twice to also show the '.' and '..' directories
-A, --almost-all equivalent to --all; included for compatibility with `ls -A`
+ --show-dotfiles show dot-prefixed files without showing other hidden files
-d, --treat-dirs-as-files treat directories as files; don't list their contents
-D, --only-dirs list only directories
-f, --only-files list only files