diff --git a/src/uu/tail/src/lines.rs b/src/uu/tail/src/lines.rs index 6e472b32e..ee8b36662 100644 --- a/src/uu/tail/src/lines.rs +++ b/src/uu/tail/src/lines.rs @@ -17,31 +17,45 @@ use std::io::BufRead; /// /// This function is just like [`BufRead::lines`], but it includes the /// line ending characters in each yielded [`String`] if the input -/// data has them. +/// data has them. Set the `sep` parameter to the line ending +/// character; for Unix line endings, use `b'\n'`. /// /// # Examples /// +/// Use `sep` to specify an alternate character for line endings. For +/// example, if lines are terminated by the null character `b'\0'`: +/// +/// ```rust,ignore +/// use std::io::BufRead; +/// use std::io::Cursor; +/// +/// let cursor = Cursor::new(b"x\0y\0z\0"); +/// let mut it = lines(cursor, b'\0').map(|l| l.unwrap()); +/// +/// assert_eq!(it.next(), Some(Vec::from("x\0"))); +/// assert_eq!(it.next(), Some(Vec::from("y\0"))); +/// assert_eq!(it.next(), Some(Vec::from("z\0"))); +/// assert_eq!(it.next(), None); +/// ``` +/// /// If the input data does not end with a newline character (`'\n'`), /// then the last [`String`] yielded by this iterator also does not /// end with a newline: /// /// ```rust,ignore -/// use std::io::BufRead; -/// use std::io::Cursor; -/// /// let cursor = Cursor::new(b"x\ny\nz"); -/// let mut it = cursor.lines(); +/// let mut it = lines(cursor, b'\n').map(|l| l.unwrap()); /// -/// assert_eq!(it.next(), Some(String::from("x\n"))); -/// assert_eq!(it.next(), Some(String::from("y\n"))); -/// assert_eq!(it.next(), Some(String::from("z"))); +/// assert_eq!(it.next(), Some(Vec::from("x\n"))); +/// assert_eq!(it.next(), Some(Vec::from("y\n"))); +/// assert_eq!(it.next(), Some(Vec::from("z"))); /// assert_eq!(it.next(), None); /// ``` -pub(crate) fn lines(reader: B) -> Lines +pub(crate) fn lines(reader: B, sep: u8) -> Lines where B: BufRead, { - Lines { buf: reader } + Lines { buf: reader, sep } } /// An iterator over the lines of an instance of `BufRead`. @@ -50,14 +64,15 @@ where /// Please see the documentation of [`lines`] for more details. pub(crate) struct Lines { buf: B, + sep: u8, } impl Iterator for Lines { - type Item = std::io::Result; + type Item = std::io::Result>; - fn next(&mut self) -> Option> { - let mut buf = String::new(); - match self.buf.read_line(&mut buf) { + fn next(&mut self) -> Option>> { + let mut buf = Vec::new(); + match self.buf.read_until(self.sep, &mut buf) { Ok(0) => None, Ok(_n) => Some(Ok(buf)), Err(e) => Some(Err(e)), @@ -73,11 +88,24 @@ mod tests { #[test] fn test_lines() { let cursor = Cursor::new(b"x\ny\nz"); - let mut it = lines(cursor).map(|l| l.unwrap()); + let mut it = lines(cursor, b'\n').map(|l| l.unwrap()); - assert_eq!(it.next(), Some(String::from("x\n"))); - assert_eq!(it.next(), Some(String::from("y\n"))); - assert_eq!(it.next(), Some(String::from("z"))); + assert_eq!(it.next(), Some(Vec::from("x\n"))); + assert_eq!(it.next(), Some(Vec::from("y\n"))); + assert_eq!(it.next(), Some(Vec::from("z"))); + assert_eq!(it.next(), None); + } + + #[test] + fn test_lines_zero_terminated() { + use std::io::Cursor; + + let cursor = Cursor::new(b"x\0y\0z\0"); + let mut it = lines(cursor, b'\0').map(|l| l.unwrap()); + + assert_eq!(it.next(), Some(Vec::from("x\0"))); + assert_eq!(it.next(), Some(Vec::from("y\0"))); + assert_eq!(it.next(), Some(Vec::from("z\0"))); assert_eq!(it.next(), None); } } diff --git a/src/uu/tail/src/tail.rs b/src/uu/tail/src/tail.rs index e273cd243..7c2652a7b 100644 --- a/src/uu/tail/src/tail.rs +++ b/src/uu/tail/src/tail.rs @@ -575,9 +575,12 @@ fn unbounded_tail(reader: &mut BufReader, settings: &Settings) -> UR // contains count lines/chars. When reaching the end of file, output the // data in the ringbuf. match settings.mode { - FilterMode::Lines(count, _) => { - for line in unbounded_tail_collect(lines(reader), count, settings.beginning) { - print!("{}", line); + FilterMode::Lines(count, sep) => { + let mut stdout = stdout(); + for line in unbounded_tail_collect(lines(reader, sep), count, settings.beginning) { + stdout + .write_all(&line) + .map_err_context(|| String::from("IO error"))?; } } FilterMode::Bytes(count) => { diff --git a/tests/by-util/test_tail.rs b/tests/by-util/test_tail.rs index 40a229d3a..edb8066d6 100644 --- a/tests/by-util/test_tail.rs +++ b/tests/by-util/test_tail.rs @@ -548,3 +548,17 @@ fn test_no_such_file() { fn test_no_trailing_newline() { new_ucmd!().pipe_in("x").succeeds().stdout_only("x"); } + +#[test] +fn test_lines_zero_terminated() { + new_ucmd!() + .args(&["-z", "-n", "2"]) + .pipe_in("a\0b\0c\0d\0e\0") + .succeeds() + .stdout_only("d\0e\0"); + new_ucmd!() + .args(&["-z", "-n", "+2"]) + .pipe_in("a\0b\0c\0d\0e\0") + .succeeds() + .stdout_only("b\0c\0d\0e\0"); +}