1
Fork 0
mirror of https://github.com/RGBCube/uutils-coreutils synced 2025-07-28 11:37:44 +00:00

head: display errors for each input file

Change the behavior of `head` to display an error for each problematic
file, instead of displaying an error message for the first problematic
file and terminating immediately at that point. This change now matches
the behavior of GNU `head`.

Before this commit, the first error caused the program to terminate
immediately:

    $ head a b c
    head: error: head: cannot open 'a' for reading: No such file or directory

After this commit:

    $ head a b c
    head: cannot open 'a' for reading: No such file or directory
    head: cannot open 'b' for reading: No such file or directory
    head: cannot open 'c' for reading: No such file or directory
This commit is contained in:
Jeffrey Finkelstein 2021-05-16 21:21:20 -04:00
parent 9e2c82d8e7
commit eeef8290df
3 changed files with 52 additions and 40 deletions

View file

@ -2,7 +2,7 @@ use clap::{App, Arg};
use std::convert::TryFrom; use std::convert::TryFrom;
use std::ffi::OsString; use std::ffi::OsString;
use std::io::{self, ErrorKind, Read, Seek, SeekFrom, Write}; use std::io::{self, ErrorKind, Read, Seek, SeekFrom, Write};
use uucore::{crash, executable, show_error}; use uucore::{crash, executable, show_error, show_error_custom_description};
const EXIT_FAILURE: i32 = 1; const EXIT_FAILURE: i32 = 1;
const EXIT_SUCCESS: i32 = 0; const EXIT_SUCCESS: i32 = 0;
@ -400,7 +400,8 @@ fn head_file(input: &mut std::fs::File, options: &HeadOptions) -> std::io::Resul
} }
} }
fn uu_head(options: &HeadOptions) { fn uu_head(options: &HeadOptions) -> Result<(), u32> {
let mut error_count = 0;
let mut first = true; let mut first = true;
for fname in &options.files { for fname in &options.files {
let res = match fname.as_str() { let res = match fname.as_str() {
@ -433,30 +434,22 @@ fn uu_head(options: &HeadOptions) {
name => { name => {
let mut file = match std::fs::File::open(name) { let mut file = match std::fs::File::open(name) {
Ok(f) => f, Ok(f) => f,
Err(err) => match err.kind() { Err(err) => {
ErrorKind::NotFound => { let prefix = format!("cannot open '{}' for reading", name);
crash!( match err.kind() {
EXIT_FAILURE, ErrorKind::NotFound => {
"head: cannot open '{}' for reading: No such file or directory", show_error_custom_description!(prefix, "No such file or directory");
name }
); ErrorKind::PermissionDenied => {
show_error_custom_description!(prefix, "Permission denied");
}
_ => {
show_error_custom_description!(prefix, "{}", err);
}
} }
ErrorKind::PermissionDenied => { error_count += 1;
crash!( continue;
EXIT_FAILURE, }
"head: cannot open '{}' for reading: Permission denied",
name
);
}
_ => {
crash!(
EXIT_FAILURE,
"head: cannot open '{}' for reading: {}",
name,
err
);
}
},
}; };
if (options.files.len() > 1 && !options.quiet) || options.verbose { if (options.files.len() > 1 && !options.quiet) || options.verbose {
if !first { if !first {
@ -468,21 +461,22 @@ fn uu_head(options: &HeadOptions) {
} }
}; };
if res.is_err() { if res.is_err() {
if fname.as_str() == "-" { let name = if fname.as_str() == "-" {
crash!( "standard input"
EXIT_FAILURE,
"head: error reading standard input: Input/output error"
);
} else { } else {
crash!( fname
EXIT_FAILURE, };
"head: error reading {}: Input/output error", let prefix = format!("error reading {}", name);
fname show_error_custom_description!(prefix, "Input/output error");
); error_count += 1;
}
} }
first = false; first = false;
} }
if error_count > 0 {
Err(error_count)
} else {
Ok(())
}
} }
pub fn uumain(args: impl uucore::Args) -> i32 { pub fn uumain(args: impl uucore::Args) -> i32 {
@ -492,9 +486,10 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
crash!(EXIT_FAILURE, "head: {}", s); crash!(EXIT_FAILURE, "head: {}", s);
} }
}; };
uu_head(&args); match uu_head(&args) {
Ok(_) => EXIT_SUCCESS,
EXIT_SUCCESS Err(_) => EXIT_FAILURE,
}
} }
#[cfg(test)] #[cfg(test)]

View file

@ -162,6 +162,18 @@ fn test_no_such_file_or_directory() {
.stderr_contains("cannot open 'no_such_file.toml' for reading: No such file or directory"); .stderr_contains("cannot open 'no_such_file.toml' for reading: No such file or directory");
} }
/// Test that each non-existent files gets its own error message printed.
#[test]
fn test_multiple_nonexistent_files() {
new_ucmd!()
.args(&["bogusfile1", "bogusfile2"])
.fails()
.stdout_does_not_contain("==> bogusfile1 <==")
.stderr_contains("cannot open 'bogusfile1' for reading: No such file or directory")
.stdout_does_not_contain("==> bogusfile2 <==")
.stderr_contains("cannot open 'bogusfile2' for reading: No such file or directory");
}
// there was a bug not caught by previous tests // there was a bug not caught by previous tests
// where for negative n > 3, the total amount of lines // where for negative n > 3, the total amount of lines
// was correct, but it would eat from the second line // was correct, but it would eat from the second line

View file

@ -315,7 +315,12 @@ impl CmdResult {
} }
pub fn stdout_does_not_contain<T: AsRef<str>>(&self, cmp: T) -> &CmdResult { pub fn stdout_does_not_contain<T: AsRef<str>>(&self, cmp: T) -> &CmdResult {
assert!(!self.stdout_str().contains(cmp.as_ref())); assert!(
!self.stdout_str().contains(cmp.as_ref()),
"'{}' contains '{}' but should not",
self.stdout_str(),
cmp.as_ref(),
);
self self
} }