Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 65 additions & 22 deletions src/uu/env/src/env.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,18 @@ const ABOUT: &str = help_about!("env.md");
const USAGE: &str = help_usage!("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";

struct Options<'a> {
Expand Down Expand Up @@ -216,36 +228,36 @@ pub fn uu_app() -> Command {
.infer_long_args(true)
.trailing_var_arg(true)
.arg(
Arg::new("ignore-environment")
Arg::new(options::IGNORE_ENVIRONMENT)
.short('i')
.long("ignore-environment")
.long(options::IGNORE_ENVIRONMENT)
.help("start with an empty environment")
.action(ArgAction::SetTrue),
)
.arg(
Arg::new("chdir")
Arg::new(options::CHDIR)
.short('C') // GNU env compatibility
.long("chdir")
.long(options::CHDIR)
.number_of_values(1)
.value_name("DIR")
.value_parser(ValueParser::os_string())
.value_hint(clap::ValueHint::DirPath)
.help("change working directory to DIR"),
)
.arg(
Arg::new("null")
Arg::new(options::NULL)
.short('0')
.long("null")
.long(options::NULL)
.help(
"end each output line with a 0 byte rather than a newline (only \
valid when printing the environment)",
)
.action(ArgAction::SetTrue),
)
.arg(
Arg::new("file")
Arg::new(options::FILE)
.short('f')
.long("file")
.long(options::FILE)
.value_name("PATH")
.value_hint(clap::ValueHint::FilePath)
.value_parser(ValueParser::os_string())
Expand All @@ -256,34 +268,34 @@ pub fn uu_app() -> Command {
),
)
.arg(
Arg::new("unset")
Arg::new(options::UNSET)
.short('u')
.long("unset")
.long(options::UNSET)
.value_name("NAME")
.action(ArgAction::Append)
.value_parser(ValueParser::os_string())
.help("remove variable from the environment"),
)
.arg(
Arg::new("debug")
Arg::new(options::DEBUG)
.short('v')
.long("debug")
.long(options::DEBUG)
.action(ArgAction::Count)
.help("print verbose information for each processing step"),
)
.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')
.long("split-string")
.long(options::SPLIT_STRING)
.value_name("S")
.action(ArgAction::Set)
.value_parser(ValueParser::os_string())
.help("process and split S into separate arguments; used to pass multiple arguments on shebang lines")
).arg(
Arg::new("argv0")
.overrides_with("argv0")
Arg::new(options::ARGV0)
.overrides_with(options::ARGV0)
.short('a')
.long("argv0")
.long(options::ARGV0)
.value_name("a")
.action(ArgAction::Set)
.value_parser(ValueParser::os_string())
Expand All @@ -296,8 +308,8 @@ pub fn uu_app() -> Command {
.value_parser(ValueParser::os_string())
)
.arg(
Arg::new("ignore-signal")
.long("ignore-signal")
Arg::new(options::IGNORE_SIGNAL)
.long(options::IGNORE_SIGNAL)
.value_name("SIG")
.action(ArgAction::Append)
.value_parser(ValueParser::os_string())
Expand Down Expand Up @@ -387,7 +399,31 @@ impl EnvAppData {
original_args: &Vec<OsString>,
) -> UResult<Vec<OsString>> {
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 {
b if check_and_handle_string_args(b, "--split-string", &mut all_args, None)? => {
self.had_string_argument = true;
Expand All @@ -411,8 +447,15 @@ impl EnvAppData {
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 '='
if arg_str.contains('=')
&& arg_str.starts_with("-u")
Expand Down
55 changes: 55 additions & 0 deletions tests/by-util/test_env.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,61 @@ fn test_invalid_arg() {
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]
fn test_env_help() {
new_ucmd!()
Expand Down
Loading