diff --git a/src/find/matchers/printf.rs b/src/find/matchers/printf.rs index ac6d9abc..b3265b99 100644 --- a/src/find/matchers/printf.rs +++ b/src/find/matchers/printf.rs @@ -169,6 +169,14 @@ impl FormatStringParser<'_> { const OCTAL_LEN: usize = 3; const OCTAL_RADIX: u32 = 8; + if self.string.is_empty() { + eprintln!( + "{}: warning: escape `\\' followed by nothing at all", + crate::find::program_name() + ); + return Ok(FormatComponent::Literal("\\".to_owned())); + } + // Try parsing an octal sequence first. let first = self.front()?; if first.is_digit(OCTAL_RADIX) { @@ -792,7 +800,17 @@ mod tests { FormatString::parse("\\X").unwrap().components, vec![FormatComponent::Literal("\\X".to_owned())] ); - assert!(FormatString::parse("\\").is_err()); + assert_eq!( + FormatString::parse("\\").unwrap().components, + vec![FormatComponent::Literal("\\".to_owned())] + ); + assert_eq!( + FormatString::parse("abc\\").unwrap().components, + vec![ + FormatComponent::Literal("abc".to_owned()), + FormatComponent::Literal("\\".to_owned()), + ] + ); } #[test] diff --git a/tests/test_find.rs b/tests/test_find.rs index 2a19a3ed..35bcb563 100644 --- a/tests/test_find.rs +++ b/tests/test_find.rs @@ -768,6 +768,35 @@ fn find_printf_unrecognized_escape_prints_literally() { .stderr_contains("unrecognized escape '\\€'"); } +#[test] +fn find_printf_trailing_backslash() { + let expected_warning = b"find: warning: escape `\\' followed by nothing at all\n"; + ucmd() + .args(&["test_data/simple", "-maxdepth", "0", "-printf", "x\\"]) + .succeeds() + .stdout_is_bytes(b"x\\") + .stderr_is_bytes(expected_warning); + + let temp_dir = Builder::new() + .prefix("find_fprintf_backslash_") + .tempdir() + .unwrap(); + let file = temp_dir.path().join("out"); + ucmd() + .args(&[ + "test_data/simple", + "-maxdepth", + "0", + "-fprintf", + file.to_str().unwrap(), + "x\\", + ]) + .succeeds() + .stdout_is_bytes(b"") + .stderr_is_bytes(expected_warning); + assert_eq!(fs::read(file).unwrap(), b"x\\"); +} + #[test] fn find_printf_width_too_large() { ucmd()