diff --git a/src/uu/printf/src/printf.rs b/src/uu/printf/src/printf.rs index ad42e3894..c98bb59a1 100644 --- a/src/uu/printf/src/printf.rs +++ b/src/uu/printf/src/printf.rs @@ -11,7 +11,7 @@ use std::ops::ControlFlow; use clap::{crate_version, Arg, ArgAction, Command}; use uucore::error::{UResult, UUsageError}; -use uucore::format::{parse_spec_and_escape, FormatArgument}; +use uucore::format::{parse_spec_and_escape, FormatArgument, FormatItem}; use uucore::{format_usage, help_about, help_section, help_usage}; const VERSION: &str = "version"; @@ -38,14 +38,24 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { None => vec![], }; + let mut format_seen = false; let mut args = values.iter().peekable(); for item in parse_spec_and_escape(format_string.as_ref()) { + if let Ok(FormatItem::Spec(_)) = item { + format_seen = true; + } match item?.write(stdout(), &mut args)? { ControlFlow::Continue(()) => {} ControlFlow::Break(()) => return Ok(()), }; } + // Without format specs in the string, the iter would not consume any args, + // leading to an infinite loop. Thus, we exit early. + if !format_seen { + return Ok(()); + } + while args.peek().is_some() { for item in parse_spec_and_escape(format_string.as_ref()) { match item?.write(stdout(), &mut args)? { diff --git a/tests/by-util/test_printf.rs b/tests/by-util/test_printf.rs index 4f2e1dc10..411285a0c 100644 --- a/tests/by-util/test_printf.rs +++ b/tests/by-util/test_printf.rs @@ -649,3 +649,8 @@ fn partial_char() { fn char_as_byte() { new_ucmd!().args(&["%c", "🙃"]).succeeds().stdout_only("ð"); } + +#[test] +fn no_infinite_loop() { + new_ucmd!().args(&["a", "b"]).succeeds().stdout_only("a"); +}