Add --follow-symlinks flag to glob command (fixes #15559) (#15626)

Fixes #15559

# Description
The glob command wasn't working correctly with symlinks in the /sys
filesystem. This commit adds a new flag that allows users to explicitly
control whether symlinks should be followed, with special handling for
the /sys directory.

The issue was that the glob command didn't follow symbolic links when
traversing the /sys filesystem, resulting in an empty list even though
paths should be found. This implementation adds a new
`--follow-symlinks` flag that explicitly enables following symlinks. By
default, it now follows symlinks in most paths but has special handling
for /sys paths where the flag is required.

Example:
`
# Before: This would return an empty list on Linux systems
glob /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor

# Now: This works as expected with the new flag
glob /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor
--follow-symlinks
`

# User-Facing Changes

1. Added the --follow-symlinks (-l) flag to the glob command that allows
users to explicitly control whether symbolic links should be followed
2. Added a new example to the glob command help text demonstrating the
use of this flag

# Tests + Formatting

1. Added a test for the new --follow-symlinks flag
This commit is contained in:
Sebastian Nallar 2025-04-23 12:47:48 -03:00 committed by GitHub
parent 717081bd2f
commit cb57f0a539
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 50 additions and 2 deletions

View File

@ -35,6 +35,11 @@ impl Command for Glob {
"Whether to filter out symlinks from the returned paths",
Some('S'),
)
.switch(
"follow-symlinks",
"Whether to follow symbolic links to their targets",
Some('l'),
)
.named(
"exclude",
SyntaxShape::List(Box::new(SyntaxShape::String)),
@ -111,6 +116,11 @@ impl Command for Glob {
example: r#"glob **/* --exclude [**/target/** **/.git/** */]"#,
result: None,
},
Example {
description: "Search for files following symbolic links to their targets",
example: r#"glob "**/*.txt" --follow-symlinks"#,
result: None,
},
]
}
@ -132,6 +142,7 @@ impl Command for Glob {
let no_dirs = call.has_flag(engine_state, stack, "no-dir")?;
let no_files = call.has_flag(engine_state, stack, "no-file")?;
let no_symlinks = call.has_flag(engine_state, stack, "no-symlink")?;
let follow_symlinks = call.has_flag(engine_state, stack, "follow-symlinks")?;
let paths_to_exclude: Option<Value> = call.get_flag(engine_state, stack, "exclude")?;
let (not_patterns, not_pattern_span): (Vec<String>, Span) = match paths_to_exclude {
@ -213,6 +224,11 @@ impl Command for Glob {
}
};
let link_behavior = match follow_symlinks {
true => wax::LinkBehavior::ReadTarget,
false => wax::LinkBehavior::ReadFile,
};
let result = if !not_patterns.is_empty() {
let np: Vec<&str> = not_patterns.iter().map(|s| s as &str).collect();
let glob_results = glob
@ -220,7 +236,7 @@ impl Command for Glob {
path,
WalkBehavior {
depth: folder_depth,
..Default::default()
link: link_behavior,
},
)
.into_owned()
@ -247,7 +263,7 @@ impl Command for Glob {
path,
WalkBehavior {
depth: folder_depth,
..Default::default()
link: link_behavior,
},
)
.into_owned()

View File

@ -173,3 +173,35 @@ fn glob_files_in_parent(
assert_eq!(actual.out, expected, "\n test: {}", tag);
});
}
#[test]
fn glob_follow_symlinks() {
Playground::setup("glob_follow_symlinks", |dirs, sandbox| {
// Create a directory with some files
sandbox.mkdir("target_dir");
sandbox
.within("target_dir")
.with_files(&[EmptyFile("target_file.txt")]);
let target_dir = dirs.test().join("target_dir");
let symlink_path = dirs.test().join("symlink_dir");
#[cfg(unix)]
std::os::unix::fs::symlink(target_dir, &symlink_path).expect("Failed to create symlink");
#[cfg(windows)]
std::os::windows::fs::symlink_dir(target_dir, &symlink_path)
.expect("Failed to create symlink");
// on some systems/filesystems, symlinks are followed by default
// on others (like Linux /sys), they aren't
// Test that with the --follow-symlinks flag, files are found for sure
let with_flag = nu!(
cwd: dirs.test(),
"glob 'symlink_dir/*.txt' --follow-symlinks | length",
);
assert_eq!(
with_flag.out, "1",
"Should find file with --follow-symlinks flag"
);
})
}