Skip to content

Commit 478daa1

Browse files
authored
Merge pull request #10090 from AnuthaDev/echo-alloc
echo: Reduce memory allocation
2 parents 0004574 + db22117 commit 478daa1

File tree

2 files changed

+64
-34
lines changed

2 files changed

+64
-34
lines changed

src/uu/echo/src/echo.rs

Lines changed: 41 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -98,33 +98,31 @@ fn is_flag(arg: &OsStr, options: &mut Options) -> bool {
9898
/// # Returns
9999
///
100100
/// - Vector of non-flag arguments.
101-
/// - [`Options`], describing how teh arguments should be interpreted.
102-
fn filter_flags(mut args: impl Iterator<Item = OsString>) -> (Vec<OsString>, Options) {
103-
let mut arguments = Vec::with_capacity(args.size_hint().0);
101+
/// - [`Options`], describing how the arguments should be interpreted.
102+
fn filter_flags(args: impl Iterator<Item = OsString>) -> (impl Iterator<Item = OsString>, Options) {
104103
let mut options = Options::default();
104+
let mut args = args.peekable();
105105

106106
// Process arguments until first non-flag is found.
107-
for arg in &mut args {
107+
while let Some(arg) = args.peek() {
108108
// We parse flags and aggregate the options in `options`.
109-
// First call to `is_echo_flag` to return false will break the loop.
110-
if !is_flag(&arg, &mut options) {
109+
// First call to `is_flag` to return false will break the loop.
110+
if is_flag(arg, &mut options) {
111+
args.next();
112+
} else {
111113
// Not a flag. Can break out of flag-processing loop.
112-
// Don't forget to push it to the arguments too.
113-
arguments.push(arg);
114114
break;
115115
}
116116
}
117117

118-
// Collect remaining non-flag arguments.
119-
arguments.extend(args);
120-
121-
(arguments, options)
118+
// Return remaining non-flag arguments.
119+
(args, options)
122120
}
123121

124122
#[uucore::main]
125123
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
126124
// args[0] is the name of the binary.
127-
let args: Vec<OsString> = args.skip(1).collect();
125+
let mut args = args.skip(1).peekable();
128126

129127
// Check POSIX compatibility mode
130128
//
@@ -139,13 +137,13 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
139137
// > representation. For example, echo -e '\x2dn'.
140138
let is_posixly_correct = env::var_os("POSIXLY_CORRECT").is_some();
141139

142-
let (args, options) = if is_posixly_correct {
143-
if args.first().is_some_and(|arg| arg == "-n") {
140+
let (args, options): (Box<dyn Iterator<Item = OsString>>, Options) = if is_posixly_correct {
141+
if args.peek().is_some_and(|arg| arg == "-n") {
144142
// if POSIXLY_CORRECT is set and the first argument is the "-n" flag
145143
// we filter flags normally but 'escaped' is activated nonetheless.
146-
let (args, _) = filter_flags(args.into_iter());
144+
let (args, _) = filter_flags(args);
147145
(
148-
args,
146+
Box::new(args),
149147
Options {
150148
trailing_newline: false,
151149
..Options::posixly_correct_default()
@@ -154,24 +152,29 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
154152
} else {
155153
// if POSIXLY_CORRECT is set and the first argument is not the "-n" flag
156154
// we just collect all arguments as no arguments are interpreted as flags.
157-
(args, Options::posixly_correct_default())
155+
(Box::new(args), Options::posixly_correct_default())
158156
}
159-
} else if args.len() == 1 && args[0] == "--help" {
160-
// If POSIXLY_CORRECT is not set and the first argument
161-
// is `--help`, GNU coreutils prints the help message.
162-
//
163-
// Verify this using:
164-
//
165-
// POSIXLY_CORRECT=1 echo --help
166-
// echo --help
167-
uu_app().print_help()?;
168-
return Ok(());
169-
} else if args.len() == 1 && args[0] == "--version" {
170-
print!("{}", uu_app().render_version());
171-
return Ok(());
172-
} else {
157+
} else if let Some(first_arg) = args.next() {
158+
if first_arg == "--help" && args.peek().is_none() {
159+
// If POSIXLY_CORRECT is not set and the first argument
160+
// is `--help`, GNU coreutils prints the help message.
161+
//
162+
// Verify this using:
163+
//
164+
// POSIXLY_CORRECT=1 echo --help
165+
// echo --help
166+
uu_app().print_help()?;
167+
return Ok(());
168+
} else if first_arg == "--version" && args.peek().is_none() {
169+
print!("{}", uu_app().render_version());
170+
return Ok(());
171+
}
172+
173173
// if POSIXLY_CORRECT is not set we filter the flags normally
174-
filter_flags(args.into_iter())
174+
let (args, options) = filter_flags(std::iter::once(first_arg).chain(args));
175+
(Box::new(args), options)
176+
} else {
177+
(Box::new(args), Options::default())
175178
};
176179

177180
execute(&mut io::stdout().lock(), args, options)?;
@@ -221,7 +224,11 @@ pub fn uu_app() -> Command {
221224
)
222225
}
223226

224-
fn execute(stdout: &mut StdoutLock, args: Vec<OsString>, options: Options) -> UResult<()> {
227+
fn execute(
228+
stdout: &mut StdoutLock,
229+
args: impl Iterator<Item = OsString>,
230+
options: Options,
231+
) -> UResult<()> {
225232
for (i, arg) in args.into_iter().enumerate() {
226233
let bytes = os_str_as_bytes(&arg)?;
227234

tests/by-util/test_echo.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,11 @@ fn test_no_trailing_newline() {
1919
new_ucmd!().arg("-n").arg("hi").succeeds().stdout_only("hi");
2020
}
2121

22+
#[test]
23+
fn test_empty_args() {
24+
new_ucmd!().succeeds().stdout_only("\n");
25+
}
26+
2227
#[test]
2328
fn test_escape_alert() {
2429
new_ucmd!()
@@ -523,12 +528,30 @@ fn full_version_argument() {
523528
.stdout_matches(&Regex::new(r"^echo \(uutils coreutils\) (\d+\.\d+\.\d+)\n$").unwrap());
524529
}
525530

531+
#[test]
532+
fn multiple_version_argument() {
533+
new_ucmd!()
534+
.arg("--version")
535+
.arg("--version")
536+
.succeeds()
537+
.stdout_is("--version --version\n");
538+
}
539+
526540
#[test]
527541
fn full_help_argument() {
528542
assert_ne!(new_ucmd!().arg("--help").succeeds().stdout(), b"--help\n");
529543
assert_ne!(new_ucmd!().arg("--help").succeeds().stdout(), b"--help"); // This one is just in case.
530544
}
531545

546+
#[test]
547+
fn multiple_help_argument() {
548+
new_ucmd!()
549+
.arg("--help")
550+
.arg("--help")
551+
.succeeds()
552+
.stdout_is("--help --help\n");
553+
}
554+
532555
#[test]
533556
fn multibyte_escape_unicode() {
534557
// spell-checker:disable-next-line

0 commit comments

Comments
 (0)