1
Fork 0
mirror of https://github.com/RGBCube/uutils-coreutils synced 2025-07-28 03:27:44 +00:00

env: ignore flags after the command or -- argument

This commit is contained in:
Dan Hipschman 2025-04-18 12:29:50 -07:00
parent d68e288602
commit 814e82ea4e
2 changed files with 120 additions and 22 deletions

87
src/uu/env/src/env.rs vendored
View file

@ -78,6 +78,18 @@ const ABOUT: &str = help_about!("env.md");
const USAGE: &str = help_usage!("env.md"); const USAGE: &str = help_usage!("env.md");
const AFTER_HELP: &str = help_section!("after help", "env.md"); const AFTER_HELP: &str = help_section!("after help", "env.md");
mod options {
pub const IGNORE_ENVIRONMENT: &str = "ignore-environment";
pub const CHDIR: &str = "chdir";
pub const NULL: &str = "null";
pub const FILE: &str = "file";
pub const UNSET: &str = "unset";
pub const DEBUG: &str = "debug";
pub const SPLIT_STRING: &str = "split-string";
pub const ARGV0: &str = "argv0";
pub const IGNORE_SIGNAL: &str = "ignore-signal";
}
const ERROR_MSG_S_SHEBANG: &str = "use -[v]S to pass options in shebang lines"; const ERROR_MSG_S_SHEBANG: &str = "use -[v]S to pass options in shebang lines";
struct Options<'a> { struct Options<'a> {
@ -216,16 +228,16 @@ pub fn uu_app() -> Command {
.infer_long_args(true) .infer_long_args(true)
.trailing_var_arg(true) .trailing_var_arg(true)
.arg( .arg(
Arg::new("ignore-environment") Arg::new(options::IGNORE_ENVIRONMENT)
.short('i') .short('i')
.long("ignore-environment") .long(options::IGNORE_ENVIRONMENT)
.help("start with an empty environment") .help("start with an empty environment")
.action(ArgAction::SetTrue), .action(ArgAction::SetTrue),
) )
.arg( .arg(
Arg::new("chdir") Arg::new(options::CHDIR)
.short('C') // GNU env compatibility .short('C') // GNU env compatibility
.long("chdir") .long(options::CHDIR)
.number_of_values(1) .number_of_values(1)
.value_name("DIR") .value_name("DIR")
.value_parser(ValueParser::os_string()) .value_parser(ValueParser::os_string())
@ -233,9 +245,9 @@ pub fn uu_app() -> Command {
.help("change working directory to DIR"), .help("change working directory to DIR"),
) )
.arg( .arg(
Arg::new("null") Arg::new(options::NULL)
.short('0') .short('0')
.long("null") .long(options::NULL)
.help( .help(
"end each output line with a 0 byte rather than a newline (only \ "end each output line with a 0 byte rather than a newline (only \
valid when printing the environment)", valid when printing the environment)",
@ -243,9 +255,9 @@ pub fn uu_app() -> Command {
.action(ArgAction::SetTrue), .action(ArgAction::SetTrue),
) )
.arg( .arg(
Arg::new("file") Arg::new(options::FILE)
.short('f') .short('f')
.long("file") .long(options::FILE)
.value_name("PATH") .value_name("PATH")
.value_hint(clap::ValueHint::FilePath) .value_hint(clap::ValueHint::FilePath)
.value_parser(ValueParser::os_string()) .value_parser(ValueParser::os_string())
@ -256,34 +268,34 @@ pub fn uu_app() -> Command {
), ),
) )
.arg( .arg(
Arg::new("unset") Arg::new(options::UNSET)
.short('u') .short('u')
.long("unset") .long(options::UNSET)
.value_name("NAME") .value_name("NAME")
.action(ArgAction::Append) .action(ArgAction::Append)
.value_parser(ValueParser::os_string()) .value_parser(ValueParser::os_string())
.help("remove variable from the environment"), .help("remove variable from the environment"),
) )
.arg( .arg(
Arg::new("debug") Arg::new(options::DEBUG)
.short('v') .short('v')
.long("debug") .long(options::DEBUG)
.action(ArgAction::Count) .action(ArgAction::Count)
.help("print verbose information for each processing step"), .help("print verbose information for each processing step"),
) )
.arg( .arg(
Arg::new("split-string") // split string handling is implemented directly, not using CLAP. But this entry here is needed for the help information output. Arg::new(options::SPLIT_STRING) // split string handling is implemented directly, not using CLAP. But this entry here is needed for the help information output.
.short('S') .short('S')
.long("split-string") .long(options::SPLIT_STRING)
.value_name("S") .value_name("S")
.action(ArgAction::Set) .action(ArgAction::Set)
.value_parser(ValueParser::os_string()) .value_parser(ValueParser::os_string())
.help("process and split S into separate arguments; used to pass multiple arguments on shebang lines") .help("process and split S into separate arguments; used to pass multiple arguments on shebang lines")
).arg( ).arg(
Arg::new("argv0") Arg::new(options::ARGV0)
.overrides_with("argv0") .overrides_with(options::ARGV0)
.short('a') .short('a')
.long("argv0") .long(options::ARGV0)
.value_name("a") .value_name("a")
.action(ArgAction::Set) .action(ArgAction::Set)
.value_parser(ValueParser::os_string()) .value_parser(ValueParser::os_string())
@ -296,8 +308,8 @@ pub fn uu_app() -> Command {
.value_parser(ValueParser::os_string()) .value_parser(ValueParser::os_string())
) )
.arg( .arg(
Arg::new("ignore-signal") Arg::new(options::IGNORE_SIGNAL)
.long("ignore-signal") .long(options::IGNORE_SIGNAL)
.value_name("SIG") .value_name("SIG")
.action(ArgAction::Append) .action(ArgAction::Append)
.value_parser(ValueParser::os_string()) .value_parser(ValueParser::os_string())
@ -387,7 +399,31 @@ impl EnvAppData {
original_args: &Vec<OsString>, original_args: &Vec<OsString>,
) -> UResult<Vec<OsString>> { ) -> UResult<Vec<OsString>> {
let mut all_args: Vec<OsString> = Vec::new(); let mut all_args: Vec<OsString> = Vec::new();
for arg in original_args { let mut process_flags = true;
let mut expecting_arg = false;
// Leave out split-string since it's a special case below
let flags_with_args = [
options::ARGV0,
options::CHDIR,
options::FILE,
options::IGNORE_SIGNAL,
options::UNSET,
];
let short_flags_with_args = ['a', 'C', 'f', 'u'];
for (n, arg) in original_args.iter().enumerate() {
let arg_str = arg.to_string_lossy();
// Stop processing env flags once we reach the command or -- argument
if 0 < n
&& !expecting_arg
&& (arg == "--" || !(arg_str.starts_with('-') || arg_str.contains('=')))
{
process_flags = false;
}
if !process_flags {
all_args.push(arg.clone());
continue;
}
expecting_arg = false;
match arg { match arg {
b if check_and_handle_string_args(b, "--split-string", &mut all_args, None)? => { b if check_and_handle_string_args(b, "--split-string", &mut all_args, None)? => {
self.had_string_argument = true; self.had_string_argument = true;
@ -411,8 +447,15 @@ impl EnvAppData {
self.had_string_argument = true; self.had_string_argument = true;
} }
_ => { _ => {
let arg_str = arg.to_string_lossy(); if let Some(flag) = arg_str.strip_prefix("--") {
if flags_with_args.contains(&flag) {
expecting_arg = true;
}
} else if let Some(flag) = arg_str.strip_prefix("-") {
for c in flag.chars() {
expecting_arg = short_flags_with_args.contains(&c);
}
}
// Short unset option (-u) is not allowed to contain '=' // Short unset option (-u) is not allowed to contain '='
if arg_str.contains('=') if arg_str.contains('=')
&& arg_str.starts_with("-u") && arg_str.starts_with("-u")

View file

@ -66,6 +66,61 @@ fn test_invalid_arg() {
new_ucmd!().arg("--definitely-invalid").fails_with_code(125); new_ucmd!().arg("--definitely-invalid").fails_with_code(125);
} }
#[test]
#[cfg(not(target_os = "windows"))]
fn test_flags_after_command() {
new_ucmd!()
// This would cause an error if -u=v were processed because it's malformed
.args(&["echo", "-u=v"])
.succeeds()
.no_stderr()
.stdout_is("-u=v\n");
new_ucmd!()
// Ensure the string isn't split
// cSpell:disable
.args(&["printf", "%s-%s", "-Sfoo bar"])
.succeeds()
.no_stderr()
.stdout_is("-Sfoo bar-");
// cSpell:enable
new_ucmd!()
// Ensure -- is recognized
.args(&["-i", "--", "-u=v"])
.succeeds()
.no_stderr()
.stdout_is("-u=v\n");
new_ucmd!()
// Recognize echo as the command after a flag that takes a value
.args(&["-C", "..", "echo", "-u=v"])
.succeeds()
.no_stderr()
.stdout_is("-u=v\n");
new_ucmd!()
// Recognize echo as the command after a flag that takes an inline value
.args(&["-C..", "echo", "-u=v"])
.succeeds()
.no_stderr()
.stdout_is("-u=v\n");
new_ucmd!()
// Recognize echo as the command after a flag that takes a value after another flag
.args(&["-iC", "..", "echo", "-u=v"])
.succeeds()
.no_stderr()
.stdout_is("-u=v\n");
new_ucmd!()
// Similar to the last two combined
.args(&["-iC..", "echo", "-u=v"])
.succeeds()
.no_stderr()
.stdout_is("-u=v\n");
}
#[test] #[test]
fn test_env_help() { fn test_env_help() {
new_ucmd!() new_ucmd!()