mirror of
https://github.com/uutils/coreutils
synced 2024-11-16 09:48:03 +00:00
head: don't add trailing newline to end of file
Prevent `head` from adding a trailing newline to the end of a file that did not originally have one when using `head --lines=-0`.
This commit is contained in:
parent
7b3cfcf708
commit
cba0696b90
3 changed files with 88 additions and 5 deletions
|
@ -38,6 +38,7 @@ mod options {
|
||||||
mod lines;
|
mod lines;
|
||||||
mod parse;
|
mod parse;
|
||||||
mod take;
|
mod take;
|
||||||
|
use lines::lines;
|
||||||
use lines::zlines;
|
use lines::zlines;
|
||||||
use take::take_all_but;
|
use take::take_all_but;
|
||||||
use take::take_lines;
|
use take::take_lines;
|
||||||
|
@ -285,8 +286,8 @@ fn read_but_last_n_lines(
|
||||||
stdout.write_all(&bytes?)?;
|
stdout.write_all(&bytes?)?;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for line in take_all_but(input.lines(), n) {
|
for line in take_all_but(lines(input), n) {
|
||||||
println!("{}", line?);
|
print!("{}", line?);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
@ -1,11 +1,75 @@
|
||||||
|
// * This file is part of the uutils coreutils package.
|
||||||
|
// *
|
||||||
|
// * For the full copyright and license information, please view the LICENSE
|
||||||
|
// * file that was distributed with this source code.
|
||||||
// spell-checker:ignore (vars) zline zlines
|
// spell-checker:ignore (vars) zline zlines
|
||||||
|
//! Iterate over lines, including the line ending character(s).
|
||||||
//! Iterate over zero-terminated lines.
|
//!
|
||||||
|
//! This module provides the [`lines`] and [`zlines`] functions,
|
||||||
|
//! similar to the [`BufRead::lines`] method. While the
|
||||||
|
//! [`BufRead::lines`] method yields [`String`] instances that do not
|
||||||
|
//! include the line ending characters (`"\n"` or `"\r\n"`), our
|
||||||
|
//! functions yield [`String`] instances that include the line ending
|
||||||
|
//! characters. This is useful if the input data does not end with a
|
||||||
|
//! newline character and you want to preserve the exact form of the
|
||||||
|
//! input data.
|
||||||
use std::io::BufRead;
|
use std::io::BufRead;
|
||||||
|
|
||||||
/// The zero byte, representing the null character.
|
/// The zero byte, representing the null character.
|
||||||
const ZERO: u8 = 0;
|
const ZERO: u8 = 0;
|
||||||
|
|
||||||
|
/// Returns an iterator over the lines, including line ending characters.
|
||||||
|
///
|
||||||
|
/// This function is just like [`BufRead::lines`], but it includes the
|
||||||
|
/// line ending characters in each yielded [`String`] if the input
|
||||||
|
/// data has them.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// 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();
|
||||||
|
///
|
||||||
|
/// 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(), None);
|
||||||
|
/// ```
|
||||||
|
pub(crate) fn lines<B>(reader: B) -> Lines<B>
|
||||||
|
where
|
||||||
|
B: BufRead,
|
||||||
|
{
|
||||||
|
Lines { buf: reader }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An iterator over the lines of an instance of `BufRead`.
|
||||||
|
///
|
||||||
|
/// This struct is generally created by calling [`lines`] on a `BufRead`.
|
||||||
|
/// Please see the documentation of [`lines`] for more details.
|
||||||
|
pub(crate) struct Lines<B> {
|
||||||
|
buf: B,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<B: BufRead> Iterator for Lines<B> {
|
||||||
|
type Item = std::io::Result<String>;
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<std::io::Result<String>> {
|
||||||
|
let mut buf = String::new();
|
||||||
|
match self.buf.read_line(&mut buf) {
|
||||||
|
Ok(0) => None,
|
||||||
|
Ok(_n) => Some(Ok(buf)),
|
||||||
|
Err(e) => Some(Err(e)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns an iterator over the lines of the given reader.
|
/// Returns an iterator over the lines of the given reader.
|
||||||
///
|
///
|
||||||
/// The iterator returned from this function will yield instances of
|
/// The iterator returned from this function will yield instances of
|
||||||
|
@ -50,6 +114,7 @@ impl<B: BufRead> Iterator for ZLines<B> {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
|
||||||
|
use crate::lines::lines;
|
||||||
use crate::lines::zlines;
|
use crate::lines::zlines;
|
||||||
use std::io::Cursor;
|
use std::io::Cursor;
|
||||||
|
|
||||||
|
@ -72,4 +137,15 @@ mod tests {
|
||||||
assert_eq!(iter.next(), Some(b"z".to_vec()));
|
assert_eq!(iter.next(), Some(b"z".to_vec()));
|
||||||
assert_eq!(iter.next(), None);
|
assert_eq!(iter.next(), None);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_lines() {
|
||||||
|
let cursor = Cursor::new(b"x\ny\nz");
|
||||||
|
let mut it = lines(cursor).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(), None);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -157,11 +157,17 @@ fn test_negative_byte_syntax() {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_negative_zero_lines() {
|
fn test_negative_zero_lines() {
|
||||||
new_ucmd!()
|
new_ucmd!()
|
||||||
.args(&["--lines=-0"])
|
.arg("--lines=-0")
|
||||||
.pipe_in("a\nb\n")
|
.pipe_in("a\nb\n")
|
||||||
.succeeds()
|
.succeeds()
|
||||||
.stdout_is("a\nb\n");
|
.stdout_is("a\nb\n");
|
||||||
|
new_ucmd!()
|
||||||
|
.arg("--lines=-0")
|
||||||
|
.pipe_in("a\nb")
|
||||||
|
.succeeds()
|
||||||
|
.stdout_is("a\nb");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_negative_zero_bytes() {
|
fn test_negative_zero_bytes() {
|
||||||
new_ucmd!()
|
new_ucmd!()
|
||||||
|
|
Loading…
Reference in a new issue