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:
parent
9e2c82d8e7
commit
eeef8290df
3 changed files with 52 additions and 40 deletions
|
@ -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)]
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue