mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-07-28 11:37:44 +00:00
wc: Report counts and failures correctly
If reading fails midway through then a count should be reported for what could be read. If opening a file fails then no count should be reported. The exit code shouldn't report the number of failures, that's fragile in case of many failures.
This commit is contained in:
parent
c16e492cd0
commit
9972cd1327
2 changed files with 66 additions and 32 deletions
|
@ -88,7 +88,7 @@ fn count_bytes_using_splice(fd: RawFd) -> Result<usize, usize> {
|
||||||
/// 3. Otherwise, we just read normally, but without the overhead of counting
|
/// 3. Otherwise, we just read normally, but without the overhead of counting
|
||||||
/// other things such as lines and words.
|
/// other things such as lines and words.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(crate) fn count_bytes_fast<T: WordCountable>(handle: &mut T) -> io::Result<usize> {
|
pub(crate) fn count_bytes_fast<T: WordCountable>(handle: &mut T) -> (usize, Option<io::Error>) {
|
||||||
let mut byte_count = 0;
|
let mut byte_count = 0;
|
||||||
|
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
|
@ -98,7 +98,7 @@ pub(crate) fn count_bytes_fast<T: WordCountable>(handle: &mut T) -> io::Result<u
|
||||||
// If the file is regular, then the `st_size` should hold
|
// If the file is regular, then the `st_size` should hold
|
||||||
// the file's size in bytes.
|
// the file's size in bytes.
|
||||||
if (stat.st_mode & S_IFREG) != 0 {
|
if (stat.st_mode & S_IFREG) != 0 {
|
||||||
return Ok(stat.st_size as usize);
|
return (stat.st_size as usize, None);
|
||||||
}
|
}
|
||||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||||
{
|
{
|
||||||
|
@ -106,7 +106,7 @@ pub(crate) fn count_bytes_fast<T: WordCountable>(handle: &mut T) -> io::Result<u
|
||||||
// (or stdin), we use splice to count the number of bytes.
|
// (or stdin), we use splice to count the number of bytes.
|
||||||
if (stat.st_mode & S_IFIFO) != 0 {
|
if (stat.st_mode & S_IFIFO) != 0 {
|
||||||
match count_bytes_using_splice(fd) {
|
match count_bytes_using_splice(fd) {
|
||||||
Ok(n) => return Ok(n),
|
Ok(n) => return (n, None),
|
||||||
Err(n) => byte_count = n,
|
Err(n) => byte_count = n,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -118,28 +118,30 @@ pub(crate) fn count_bytes_fast<T: WordCountable>(handle: &mut T) -> io::Result<u
|
||||||
let mut buf = [0_u8; BUF_SIZE];
|
let mut buf = [0_u8; BUF_SIZE];
|
||||||
loop {
|
loop {
|
||||||
match handle.read(&mut buf) {
|
match handle.read(&mut buf) {
|
||||||
Ok(0) => return Ok(byte_count),
|
Ok(0) => return (byte_count, None),
|
||||||
Ok(n) => {
|
Ok(n) => {
|
||||||
byte_count += n;
|
byte_count += n;
|
||||||
}
|
}
|
||||||
Err(ref e) if e.kind() == ErrorKind::Interrupted => continue,
|
Err(ref e) if e.kind() == ErrorKind::Interrupted => continue,
|
||||||
Err(e) => return Err(e),
|
Err(e) => return (byte_count, Some(e)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn count_bytes_and_lines_fast<R: Read>(handle: &mut R) -> io::Result<WordCount> {
|
pub(crate) fn count_bytes_and_lines_fast<R: Read>(
|
||||||
|
handle: &mut R,
|
||||||
|
) -> (WordCount, Option<io::Error>) {
|
||||||
let mut total = WordCount::default();
|
let mut total = WordCount::default();
|
||||||
let mut buf = [0; BUF_SIZE];
|
let mut buf = [0; BUF_SIZE];
|
||||||
loop {
|
loop {
|
||||||
match handle.read(&mut buf) {
|
match handle.read(&mut buf) {
|
||||||
Ok(0) => return Ok(total),
|
Ok(0) => return (total, None),
|
||||||
Ok(n) => {
|
Ok(n) => {
|
||||||
total.bytes += n;
|
total.bytes += n;
|
||||||
total.lines += bytecount::count(&buf[..n], b'\n');
|
total.lines += bytecount::count(&buf[..n], b'\n');
|
||||||
}
|
}
|
||||||
Err(ref e) if e.kind() == ErrorKind::Interrupted => continue,
|
Err(ref e) if e.kind() == ErrorKind::Interrupted => continue,
|
||||||
Err(e) => return Err(e),
|
Err(e) => return (total, Some(e)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -202,17 +202,21 @@ pub fn uu_app() -> App<'static, 'static> {
|
||||||
fn word_count_from_reader<T: WordCountable>(
|
fn word_count_from_reader<T: WordCountable>(
|
||||||
mut reader: T,
|
mut reader: T,
|
||||||
settings: &Settings,
|
settings: &Settings,
|
||||||
) -> io::Result<WordCount> {
|
) -> (WordCount, Option<io::Error>) {
|
||||||
let only_count_bytes = settings.show_bytes
|
let only_count_bytes = settings.show_bytes
|
||||||
&& (!(settings.show_chars
|
&& (!(settings.show_chars
|
||||||
|| settings.show_lines
|
|| settings.show_lines
|
||||||
|| settings.show_max_line_length
|
|| settings.show_max_line_length
|
||||||
|| settings.show_words));
|
|| settings.show_words));
|
||||||
if only_count_bytes {
|
if only_count_bytes {
|
||||||
return Ok(WordCount {
|
let (bytes, error) = count_bytes_fast(&mut reader);
|
||||||
bytes: count_bytes_fast(&mut reader)?,
|
return (
|
||||||
..WordCount::default()
|
WordCount {
|
||||||
});
|
bytes,
|
||||||
|
..WordCount::default()
|
||||||
|
},
|
||||||
|
error,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// we do not need to decode the byte stream if we're only counting bytes/newlines
|
// we do not need to decode the byte stream if we're only counting bytes/newlines
|
||||||
|
@ -268,27 +272,47 @@ fn word_count_from_reader<T: WordCountable>(
|
||||||
total.bytes += bytes.len();
|
total.bytes += bytes.len();
|
||||||
}
|
}
|
||||||
Err(BufReadDecoderError::Io(e)) => {
|
Err(BufReadDecoderError::Io(e)) => {
|
||||||
return Err(e);
|
return (total, Some(e));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
total.max_line_length = max(current_len, total.max_line_length);
|
total.max_line_length = max(current_len, total.max_line_length);
|
||||||
|
|
||||||
Ok(total)
|
(total, None)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn word_count_from_input(input: &Input, settings: &Settings) -> io::Result<WordCount> {
|
enum CountResult {
|
||||||
|
/// Nothing went wrong.
|
||||||
|
Success(WordCount),
|
||||||
|
/// Managed to open but failed to read.
|
||||||
|
Interrupted(WordCount, io::Error),
|
||||||
|
/// Didn't even manage to open.
|
||||||
|
Failure(io::Error),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If we fail opening a file we only show the error. If we fail reading it
|
||||||
|
/// we show a count for what we managed to read.
|
||||||
|
///
|
||||||
|
/// Therefore the reading implementations always return a total and sometimes
|
||||||
|
/// return an error: (WordCount, Option<io::Error>).
|
||||||
|
fn word_count_from_input(input: &Input, settings: &Settings) -> CountResult {
|
||||||
match input {
|
match input {
|
||||||
Input::Stdin(_) => {
|
Input::Stdin(_) => {
|
||||||
let stdin = io::stdin();
|
let stdin = io::stdin();
|
||||||
let stdin_lock = stdin.lock();
|
let stdin_lock = stdin.lock();
|
||||||
word_count_from_reader(stdin_lock, settings)
|
match word_count_from_reader(stdin_lock, settings) {
|
||||||
}
|
(total, Some(error)) => CountResult::Interrupted(total, error),
|
||||||
Input::Path(path) => {
|
(total, None) => CountResult::Success(total),
|
||||||
let file = File::open(path)?;
|
}
|
||||||
word_count_from_reader(file, settings)
|
|
||||||
}
|
}
|
||||||
|
Input::Path(path) => match File::open(path) {
|
||||||
|
Err(error) => CountResult::Failure(error),
|
||||||
|
Ok(file) => match word_count_from_reader(file, settings) {
|
||||||
|
(total, Some(error)) => CountResult::Interrupted(total, error),
|
||||||
|
(total, None) => CountResult::Success(total),
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -390,7 +414,7 @@ fn wc(inputs: Vec<Input>, settings: &Settings) -> Result<(), u32> {
|
||||||
// The width is the number of digits needed to print the number of
|
// The width is the number of digits needed to print the number of
|
||||||
// bytes in the largest file. This is true regardless of whether
|
// bytes in the largest file. This is true regardless of whether
|
||||||
// the `settings` indicate that the bytes will be displayed.
|
// the `settings` indicate that the bytes will be displayed.
|
||||||
let mut error_count = 0;
|
let mut failure = false;
|
||||||
let max_width = max_width(&inputs);
|
let max_width = max_width(&inputs);
|
||||||
|
|
||||||
let mut total_word_count = WordCount::default();
|
let mut total_word_count = WordCount::default();
|
||||||
|
@ -398,11 +422,19 @@ fn wc(inputs: Vec<Input>, settings: &Settings) -> Result<(), u32> {
|
||||||
let num_inputs = inputs.len();
|
let num_inputs = inputs.len();
|
||||||
|
|
||||||
for input in &inputs {
|
for input in &inputs {
|
||||||
let word_count = word_count_from_input(input, settings).unwrap_or_else(|err| {
|
let word_count = match word_count_from_input(input, settings) {
|
||||||
show_error(input, err);
|
CountResult::Success(word_count) => word_count,
|
||||||
error_count += 1;
|
CountResult::Interrupted(word_count, error) => {
|
||||||
WordCount::default()
|
show_error(input, error);
|
||||||
});
|
failure = true;
|
||||||
|
word_count
|
||||||
|
}
|
||||||
|
CountResult::Failure(error) => {
|
||||||
|
show_error(input, error);
|
||||||
|
failure = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
total_word_count += word_count;
|
total_word_count += word_count;
|
||||||
let result = word_count.with_title(input.to_title());
|
let result = word_count.with_title(input.to_title());
|
||||||
if let Err(err) = print_stats(settings, &result, max_width) {
|
if let Err(err) = print_stats(settings, &result, max_width) {
|
||||||
|
@ -411,7 +443,7 @@ fn wc(inputs: Vec<Input>, settings: &Settings) -> Result<(), u32> {
|
||||||
result.title.unwrap_or_else(|| "<stdin>".as_ref()).display(),
|
result.title.unwrap_or_else(|| "<stdin>".as_ref()).display(),
|
||||||
err
|
err
|
||||||
);
|
);
|
||||||
error_count += 1;
|
failure = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -419,14 +451,14 @@ fn wc(inputs: Vec<Input>, settings: &Settings) -> Result<(), u32> {
|
||||||
let total_result = total_word_count.with_title(Some("total".as_ref()));
|
let total_result = total_word_count.with_title(Some("total".as_ref()));
|
||||||
if let Err(err) = print_stats(settings, &total_result, max_width) {
|
if let Err(err) = print_stats(settings, &total_result, max_width) {
|
||||||
show_warning!("failed to print total: {}", err);
|
show_warning!("failed to print total: {}", err);
|
||||||
error_count += 1;
|
failure = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if error_count == 0 {
|
if failure {
|
||||||
Ok(())
|
Err(1)
|
||||||
} else {
|
} else {
|
||||||
Err(error_count)
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue