hexdump: Add tool

This supports the same functionality as util-linux hexdump except for
--format and --color options. Output should be bit-exact, except as
added bonus this supports the `-s` option on pipes and other non-seekable
files.
This commit is contained in:
Tuomas Tynkkynen
2025-09-12 23:24:33 +03:00
parent fbc413ecbf
commit 5283e85556
8 changed files with 825 additions and 0 deletions

9
Cargo.lock generated
View File

@@ -1359,6 +1359,7 @@ dependencies = [
"uu_ctrlaltdel", "uu_ctrlaltdel",
"uu_dmesg", "uu_dmesg",
"uu_fsfreeze", "uu_fsfreeze",
"uu_hexdump",
"uu_last", "uu_last",
"uu_lscpu", "uu_lscpu",
"uu_lsipc", "uu_lsipc",
@@ -1443,6 +1444,14 @@ dependencies = [
"uucore", "uucore",
] ]
[[package]]
name = "uu_hexdump"
version = "0.0.1"
dependencies = [
"clap",
"uucore",
]
[[package]] [[package]]
name = "uu_last" name = "uu_last"
version = "0.0.1" version = "0.0.1"

View File

@@ -32,6 +32,7 @@ feat_common_core = [
"ctrlaltdel", "ctrlaltdel",
"dmesg", "dmesg",
"fsfreeze", "fsfreeze",
"hexdump",
"last", "last",
"lscpu", "lscpu",
"lsipc", "lsipc",
@@ -99,6 +100,7 @@ chcpu = { optional = true, version = "0.0.1", package = "uu_chcpu", path = "src/
ctrlaltdel = { optional = true, version = "0.0.1", package = "uu_ctrlaltdel", path = "src/uu/ctrlaltdel" } ctrlaltdel = { optional = true, version = "0.0.1", package = "uu_ctrlaltdel", path = "src/uu/ctrlaltdel" }
dmesg = { optional = true, version = "0.0.1", package = "uu_dmesg", path = "src/uu/dmesg" } dmesg = { optional = true, version = "0.0.1", package = "uu_dmesg", path = "src/uu/dmesg" }
fsfreeze = { optional = true, version = "0.0.1", package = "uu_fsfreeze", path = "src/uu/fsfreeze" } fsfreeze = { optional = true, version = "0.0.1", package = "uu_fsfreeze", path = "src/uu/fsfreeze" }
hexdump = { optional = true, version = "0.0.1", package = "uu_hexdump", path = "src/uu/hexdump" }
last = { optional = true, version = "0.0.1", package = "uu_last", path = "src/uu/last" } last = { optional = true, version = "0.0.1", package = "uu_last", path = "src/uu/last" }
lscpu = { optional = true, version = "0.0.1", package = "uu_lscpu", path = "src/uu/lscpu" } lscpu = { optional = true, version = "0.0.1", package = "uu_lscpu", path = "src/uu/lscpu" }
lsipc = { optional = true, version = "0.0.1", package = "uu_lsipc", path = "src/uu/lsipc" } lsipc = { optional = true, version = "0.0.1", package = "uu_lsipc", path = "src/uu/lsipc" }

16
src/uu/hexdump/Cargo.toml Normal file
View File

@@ -0,0 +1,16 @@
[package]
name = "uu_hexdump"
version = "0.0.1"
edition = "2021"
description = "hexdump ~ display file contents in hexadecimal, decimal, octal, or ascii"
[lib]
path = "src/hexdump.rs"
[[bin]]
name = "hexdump"
path = "src/main.rs"
[dependencies]
clap = { workspace = true }
uucore = { workspace = true, features = ["parser"] }

View File

@@ -0,0 +1,7 @@
# hexdump
```
hexdump [options] <file>...
```
Display file contents in hexadecimal, decimal, octal, or ascii

View File

@@ -0,0 +1,474 @@
// This file is part of the uutils util-linux package.
//
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.
use std::collections::BTreeMap;
use std::fs::File;
use std::io::{BufReader, Read, Seek, SeekFrom};
use clap::{crate_version, Arg, ArgAction, Command};
use uucore::{
error::{set_exit_code, UResult, USimpleError},
format_usage, help_about, help_usage,
parser::parse_size,
};
struct ChainedFileReader {
file_paths: Vec<String>,
current_file: Option<BufReader<File>>,
current_file_index: usize,
remaining_bytes: Option<u64>,
open_error_count: usize,
}
impl ChainedFileReader {
fn new(file_paths: Vec<String>, length_limit: Option<u64>) -> Self {
Self {
file_paths,
current_file: None,
current_file_index: 0,
remaining_bytes: length_limit,
open_error_count: 0,
}
}
fn ensure_current_file(&mut self) -> bool {
if self.current_file.is_some() {
return true;
}
while self.current_file_index < self.file_paths.len() {
let file_path = &self.file_paths[self.current_file_index];
match File::open(file_path) {
Ok(file) => {
self.current_file = Some(BufReader::new(file));
return true;
}
Err(e) => {
uucore::show_error!("cannot open '{}': {}", file_path, e);
set_exit_code(1);
self.open_error_count += 1;
self.current_file_index += 1;
}
}
}
false
}
fn current_file(&mut self) -> &mut BufReader<File> {
self.current_file.as_mut().unwrap()
}
// Note: Callers assume partial reads won't happen (except on EOF)
fn read(&mut self, buf: &mut [u8]) -> usize {
if self.remaining_bytes == Some(0) {
return 0;
}
let mut offset = 0;
while self.ensure_current_file() {
let remaining_in_buffer = (buf.len() - offset) as u64;
let nbytes = remaining_in_buffer.min(self.remaining_bytes.unwrap_or(u64::MAX)) as usize;
if nbytes == 0 {
break;
}
match self.current_file().read(&mut buf[offset..offset + nbytes]) {
Ok(0) => {
self.current_file = None;
self.current_file_index += 1;
}
Ok(n) => {
offset += n;
self.remaining_bytes = self.remaining_bytes.map(|x| x - n as u64);
}
Err(e) => {
uucore::show_error!(
"cannot read '{}': {}",
self.file_paths[self.current_file_index],
e
);
set_exit_code(1);
self.current_file = None;
self.current_file_index += 1;
}
}
}
offset
}
fn skip_bytes(&mut self, bytes_to_skip: u64) {
let mut remaining = bytes_to_skip;
while remaining > 0 && self.ensure_current_file() {
match self.current_file().seek(SeekFrom::End(0)) {
Ok(file_size) => {
if remaining >= file_size {
remaining -= file_size;
self.current_file = None;
self.current_file_index += 1;
} else {
match self.current_file().seek(SeekFrom::Start(remaining)) {
Ok(_) => return,
Err(e) => {
uucore::show_error!(
"cannot seek '{}': {}",
self.file_paths[self.current_file_index],
e
);
set_exit_code(1);
self.current_file = None;
self.current_file_index += 1;
}
}
}
}
Err(_) => {
// This file doesn't support seeking, fall back to dummy reads
while remaining > 0 {
let mut dummy_buf = vec![0u8; remaining.min(65536) as usize];
match self.current_file().read(&mut dummy_buf) {
Ok(0) => {
self.current_file = None;
self.current_file_index += 1;
break;
}
Ok(n) => remaining -= n as u64,
Err(e) => {
uucore::show_error!(
"cannot read '{}': {}",
self.file_paths[self.current_file_index],
e
);
set_exit_code(1);
self.current_file = None;
self.current_file_index += 1;
break;
}
}
}
}
}
}
}
}
const ABOUT: &str = help_about!("hexdump.md");
const USAGE: &str = help_usage!("hexdump.md");
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum DisplayFormat {
Canonical, // -C: hex + ASCII display
OneByteOctal, // -b: one-byte octal display
OneByteHex, // -X: one-byte hex display
OneByteChar, // -c: one-byte character display
TwoBytesDecimal, // -d: two-byte decimal display
TwoBytesOctal, // -o: two-byte octal display
TwoBytesHex, // -x: two-byte hex display
TwoBytesHexDefault, // default: two-byte hex display with compact spacing
}
#[derive(Debug)]
struct HexdumpOptions {
formats: Vec<DisplayFormat>,
length: Option<u64>,
skip: u64,
no_squeezing: bool,
files: Vec<String>,
}
#[uucore::main]
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let matches = uu_app().try_get_matches_from(args)?;
// Collect formats in same order they were in command line
let format_mappings = [
("canonical", DisplayFormat::Canonical),
("one-byte-octal", DisplayFormat::OneByteOctal),
("one-byte-hex", DisplayFormat::OneByteHex),
("one-byte-char", DisplayFormat::OneByteChar),
("two-bytes-decimal", DisplayFormat::TwoBytesDecimal),
("two-bytes-octal", DisplayFormat::TwoBytesOctal),
("two-bytes-hex", DisplayFormat::TwoBytesHex),
];
let mut formats = BTreeMap::new();
for (id, format) in format_mappings {
if matches.value_source(id) == Some(clap::parser::ValueSource::CommandLine) {
for index in matches.indices_of(id).unwrap() {
formats.insert(index, format);
}
}
}
if formats.is_empty() {
formats.insert(0, DisplayFormat::TwoBytesHexDefault);
}
let options = HexdumpOptions {
formats: formats.into_values().collect(),
length: matches.get_one::<u64>("length").copied(),
skip: matches.get_one::<u64>("skip").copied().unwrap_or(0),
no_squeezing: matches.get_flag("no-squeezing"),
// TODO: /dev/stdin doesn't work on Windows
files: matches
.get_many::<String>("files")
.map(|files| files.cloned().collect())
.unwrap_or_else(|| vec!["/dev/stdin".to_string()]),
};
run_hexdump(options)
}
fn run_hexdump(options: HexdumpOptions) -> UResult<()> {
let mut reader = ChainedFileReader::new(options.files.clone(), options.length);
reader.skip_bytes(options.skip);
let mut offset = options.skip;
let mut last_line: Vec<u8> = Vec::new();
let mut squeezing = false;
loop {
let mut line_data = [0u8; 16];
let bytes_read = reader.read(&mut line_data);
if bytes_read == 0 {
break;
}
let line_data = &line_data[..bytes_read];
// Handle line squeezing (consolidate identical lines into one '*')
if !options.no_squeezing && last_line == line_data {
if !squeezing {
println!("*");
squeezing = true;
}
} else {
for format in &options.formats {
print_hexdump_line(*format, offset, line_data);
}
last_line.clear();
last_line.extend_from_slice(line_data);
squeezing = false;
}
offset += line_data.len() as u64;
}
if offset != 0 {
// Formatting of end offset must match last format's
print_offset(offset, *options.formats.last().unwrap());
println!();
}
if reader.open_error_count == reader.file_paths.len() {
Err(USimpleError::new(1, "all input file arguments failed"))
} else {
Ok(())
}
}
fn print_hexdump_line(format: DisplayFormat, offset: u64, line_data: &[u8]) {
print_offset(offset, format);
match format {
DisplayFormat::Canonical => print_canonical(line_data),
DisplayFormat::OneByteOctal => print_bytes(line_data, |b| print!(" {:03o}", b)),
DisplayFormat::OneByteHex => print_bytes(line_data, |b| print!(" {:02x}", b)),
DisplayFormat::OneByteChar => print_bytes(line_data, print_char_byte),
DisplayFormat::TwoBytesDecimal => print_words(line_data, 8, |w| print!(" {:05}", w)),
DisplayFormat::TwoBytesOctal => print_words(line_data, 8, |w| print!(" {:06o}", w)),
DisplayFormat::TwoBytesHex => print_words(line_data, 8, |w| print!(" {:04x}", w)),
DisplayFormat::TwoBytesHexDefault => print_words(line_data, 5, |w| print!(" {:04x}", w)),
}
}
fn print_offset(offset: u64, format: DisplayFormat) {
if format == DisplayFormat::Canonical {
print!("{:08x}", offset);
} else {
print!("{:07x}", offset);
}
}
fn print_canonical(line_data: &[u8]) {
print!(" ");
for i in 0..16 {
// Extra space between halfs
if i == 8 {
print!(" ");
}
if i < line_data.len() {
print!("{:02x} ", line_data[i]);
} else {
print!(" ");
}
}
print!(" |");
for &byte in line_data {
if byte.is_ascii_graphic() || byte == b' ' {
print!("{}", byte as char);
} else {
print!(".");
}
}
println!("|");
}
fn print_bytes<F>(line_data: &[u8], byte_printer: F)
where
F: Fn(u8),
{
for &byte in line_data {
byte_printer(byte);
}
// Original hexdump pads all lines to same length
println!("{:width$}", "", width = (16 - line_data.len()) * 4);
}
fn print_char_byte(byte: u8) {
match byte {
b'\0' => print!(" \\0"),
b'\x07' => print!(" \\a"),
b'\x08' => print!(" \\b"),
b'\t' => print!(" \\t"),
b'\n' => print!(" \\n"),
b'\x0B' => print!(" \\v"),
b'\x0C' => print!(" \\f"),
b'\r' => print!(" \\r"),
b if b.is_ascii_graphic() || b == b' ' => print!(" {}", b as char),
b => print!(" {:03o}", b),
}
}
fn print_words<F>(line_data: &[u8], chars_per_word: usize, word_printer: F)
where
F: Fn(u16),
{
for i in 0..(line_data.len() / 2) {
word_printer(u16::from_le_bytes([line_data[i * 2], line_data[i * 2 + 1]]));
}
if line_data.len() % 2 == 1 {
word_printer(*line_data.last().unwrap() as u16);
}
// Original hexdump pads all lines to same length
let word_count = line_data.len().div_ceil(2);
println!("{:width$}", "", width = (8 - word_count) * chars_per_word);
}
pub fn uu_app() -> Command {
Command::new(uucore::util_name())
.version(crate_version!())
.about(ABOUT)
.override_usage(format_usage(USAGE))
.infer_long_args(true)
.arg(
Arg::new("one-byte-octal")
.short('b')
.long("one-byte-octal")
.help("one-byte octal display")
.action(ArgAction::Append)
.num_args(0)
.default_value("0")
.default_missing_value("0"),
)
.arg(
Arg::new("one-byte-hex")
.short('X')
.long("one-byte-hex")
.help("one-byte hexadecimal display")
.action(ArgAction::Append)
.num_args(0)
.default_value("0")
.default_missing_value("0"),
)
.arg(
Arg::new("one-byte-char")
.short('c')
.long("one-byte-char")
.help("one-byte character display")
.action(ArgAction::Append)
.num_args(0)
.default_value("0")
.default_missing_value("0"),
)
.arg(
Arg::new("canonical")
.short('C')
.long("canonical")
.help("canonical hex+ASCII display")
.action(ArgAction::Append)
.num_args(0)
.default_value("0")
.default_missing_value("0"),
)
.arg(
Arg::new("two-bytes-decimal")
.short('d')
.long("two-bytes-decimal")
.help("two-byte decimal display")
.action(ArgAction::Append)
.num_args(0)
.default_value("0")
.default_missing_value("0"),
)
.arg(
Arg::new("two-bytes-octal")
.short('o')
.long("two-bytes-octal")
.help("two-byte octal display")
.action(ArgAction::Append)
.num_args(0)
.default_value("0")
.default_missing_value("0"),
)
.arg(
Arg::new("two-bytes-hex")
.short('x')
.long("two-bytes-hex")
.help("two-byte hexadecimal display")
.action(ArgAction::Append)
.num_args(0)
.default_value("0")
.default_missing_value("0"),
)
.arg(
Arg::new("length")
.short('n')
.long("length")
.help("interpret only length bytes of input")
.action(ArgAction::Set)
.num_args(1)
.value_parser(|s: &str| -> Result<u64, String> {
parse_size::parse_size_u64(s).map_err(|e| format!("invalid length: {}", e))
}),
)
.arg(
Arg::new("skip")
.short('s')
.long("skip")
.help("skip offset bytes from the beginning")
.action(ArgAction::Set)
.num_args(1)
.value_parser(|s: &str| -> Result<u64, String> {
parse_size::parse_size_u64(s).map_err(|e| format!("invalid skip: {}", e))
}),
)
.arg(
Arg::new("no-squeezing")
.short('v')
.long("no-squeezing")
.help("output identical lines")
.action(ArgAction::SetTrue),
)
.arg(
Arg::new("files")
.help("input files")
.action(ArgAction::Append)
.num_args(0..),
)
}

View File

@@ -0,0 +1 @@
uucore::bin!(uu_hexdump);

View File

@@ -0,0 +1,312 @@
// This file is part of the uutils util-linux package.
//
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.
use uutests::{new_ucmd, util::TestScenario};
#[test]
fn test_invalid_arg() {
new_ucmd!().arg("--definitely-invalid").fails().code_is(1);
}
#[test]
#[cfg(unix)]
fn test_hexdump_empty_input() {
new_ucmd!().pipe_in("").succeeds().stdout_is("");
}
#[test]
#[cfg(unix)]
fn test_hexdump_default_format() {
new_ucmd!()
.pipe_in("ABCD")
.succeeds()
.stdout_is("0000000 4241 4443 \n0000004\n");
new_ucmd!()
.pipe_in("ABC")
.succeeds()
.stdout_is("0000000 4241 0043 \n0000003\n");
}
#[test]
#[cfg(unix)]
fn test_hexdump_canonical_format() {
new_ucmd!()
.arg("-C")
.pipe_in("Hello World!")
.succeeds()
.stdout_is(
"00000000 48 65 6c 6c 6f 20 57 6f 72 6c 64 21 |Hello World!|\n0000000c\n",
);
}
#[test]
#[cfg(unix)]
fn test_hexdump_one_byte_char() {
let input = b"Hello \t\n\0\x07\x08\x0B\x0C\r\x80\xFF";
let expected = vec![
"0000000 H e l l o \\t \\n \\0 \\a \\b \\v \\f \\r 200 377\n",
"0000010\n",
];
new_ucmd!()
.arg("-c")
.pipe_in(input)
.succeeds()
.stdout_is(expected.join(""));
}
#[test]
#[cfg(unix)]
fn test_hexdump_one_byte_octal() {
new_ucmd!().arg("-b").pipe_in("ABC").succeeds().stdout_is(
"0000000 101 102 103 \n0000003\n",
);
}
#[test]
#[cfg(unix)]
fn test_hexdump_one_byte_hex() {
new_ucmd!().arg("-X").pipe_in("ABC").succeeds().stdout_is(
"0000000 41 42 43 \n0000003\n",
);
}
#[test]
#[cfg(unix)]
fn test_hexdump_two_bytes_hex() {
new_ucmd!().arg("-x").pipe_in("ABCD").succeeds().stdout_is(
"0000000 4241 4443 \n0000004\n",
);
new_ucmd!().arg("-x").pipe_in("ABC").succeeds().stdout_is(
"0000000 4241 0043 \n0000003\n",
);
}
#[test]
#[cfg(unix)]
fn test_hexdump_two_bytes_decimal() {
new_ucmd!().arg("-d").pipe_in("ABCD").succeeds().stdout_is(
"0000000 16961 17475 \n0000004\n",
);
new_ucmd!().arg("-d").pipe_in("ABC").succeeds().stdout_is(
"0000000 16961 00067 \n0000003\n",
);
}
#[test]
#[cfg(unix)]
fn test_hexdump_two_bytes_octal() {
new_ucmd!().arg("-o").pipe_in("ABCD").succeeds().stdout_is(
"0000000 041101 042103 \n0000004\n",
);
new_ucmd!().arg("-o").pipe_in("ABC").succeeds().stdout_is(
"0000000 041101 000103 \n0000003\n",
);
}
#[test]
#[cfg(unix)]
fn test_hexdump_multiple_formats() {
let expected = vec![
"00000000 41 42 |AB|\n",
"0000000 4241 \n",
"00000000 41 42 |AB|\n",
"00000002\n",
];
new_ucmd!()
.args(&["-C", "-x", "-C"])
.pipe_in("AB")
.succeeds()
.stdout_is(expected.join(""));
}
#[test]
#[cfg(unix)]
fn test_hexdump_squeezing() {
let input = vec![
"AAAAAAAAAAAAAAAA",
"AAAAAAAAAAAAAAAA",
"AAAAAAAAAAAAAAAA",
"AAAAAAAA",
];
let expected_no_squeezing = vec![
"00000000 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 |AAAAAAAAAAAAAAAA|\n",
"00000010 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 |AAAAAAAAAAAAAAAA|\n",
"00000020 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 |AAAAAAAAAAAAAAAA|\n",
"00000030 41 41 41 41 41 41 41 41 |AAAAAAAA|\n",
"00000038\n",
];
let expected_with_squeezing = vec![
"00000000 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 |AAAAAAAAAAAAAAAA|\n",
"*\n",
"00000030 41 41 41 41 41 41 41 41 |AAAAAAAA|\n",
"00000038\n",
];
new_ucmd!()
.args(&["-C", "-v"])
.pipe_in(input.join(""))
.succeeds()
.stdout_is(expected_no_squeezing.join(""));
new_ucmd!()
.arg("-C")
.pipe_in(input.join(""))
.succeeds()
.stdout_is(expected_with_squeezing.join(""));
}
#[test]
fn test_hexdump_multiple_files() {
let scene = TestScenario::new("hexdump");
scene.fixtures.write("file1.txt", "hello");
scene.fixtures.write("file2.txt", "world");
scene
.ucmd()
.args(&["-C", "file1.txt", "file2.txt"])
.succeeds()
.stdout_is(
"00000000 68 65 6c 6c 6f 77 6f 72 6c 64 |helloworld|\n0000000a\n",
);
}
#[test]
fn test_hexdump_multiple_files_with_skip() {
let scene = TestScenario::new("hexdump");
scene.fixtures.write("file1.txt", "abc");
scene.fixtures.write("file2.txt", "def");
scene
.ucmd()
.args(&["-C", "-s", "2", "file1.txt", "file2.txt"])
.succeeds()
.stdout_is(
"00000002 63 64 65 66 |cdef|\n00000006\n",
);
}
#[test]
fn test_hexdump_multiple_files_with_length() {
let scene = TestScenario::new("hexdump");
scene.fixtures.write("file1.txt", "abcdefgh");
scene.fixtures.write("file2.txt", "ijklmnop");
scene
.ucmd()
.args(&["-C", "-n", "10", "file1.txt", "file2.txt"])
.succeeds()
.stdout_is(
"00000000 61 62 63 64 65 66 67 68 69 6a |abcdefghij|\n0000000a\n",
);
}
#[test]
fn test_hexdump_multiple_files_with_skip_and_length() {
let scene = TestScenario::new("hexdump");
scene.fixtures.write("file1.txt", "abcdefgh");
scene.fixtures.write("file2.txt", "ijklmnop");
scene
.ucmd()
.args(&["-C", "-s", "3", "-n", "6", "file1.txt", "file2.txt"])
.succeeds()
.stdout_is(
"00000003 64 65 66 67 68 69 |defghi|\n00000009\n",
);
}
#[test]
fn test_hexdump_open_error() {
let scene = TestScenario::new("hexdump");
scene.fixtures.write("valid1.txt", "ABC");
scene.fixtures.write("valid2.txt", "DEF");
scene
.ucmd()
.args(&["-C", "valid1.txt", "nonexistent.txt", "valid2.txt"])
.fails()
.code_is(1)
.stderr_contains("cannot open 'nonexistent.txt'")
.stdout_is(
"00000000 41 42 43 44 45 46 |ABCDEF|\n00000006\n",
);
}
#[test]
#[cfg(target_os = "linux")]
fn test_hexdump_read_error() {
let scene = TestScenario::new("hexdump");
scene.fixtures.write("file1.txt", "hello");
scene.fixtures.write("file2.txt", "world");
scene
.ucmd()
.args(&["-C", "file1.txt", "/proc/self/mem", "file2.txt"])
.fails()
.code_is(1)
.stderr_contains("cannot read '/proc/self/mem'")
.stdout_is(
"00000000 68 65 6c 6c 6f 77 6f 72 6c 64 |helloworld|\n0000000a\n",
);
}
#[test]
fn test_hexdump_all_files_nonexistent() {
new_ucmd!()
.args(&["missing1.txt", "missing2.txt"])
.fails()
.code_is(1)
.stderr_contains("cannot open 'missing1.txt'")
.stderr_contains("cannot open 'missing2.txt'")
.stderr_contains("all input file arguments failed");
}
#[test]
#[cfg(unix)]
fn test_hexdump_size_and_length_suffixes() {
new_ucmd!()
.args(&["-n", "1K"])
.pipe_in("A".repeat(2048))
.succeeds()
.stdout_is("0000000 4141 4141 4141 4141 4141 4141 4141 4141\n*\n0000400\n");
new_ucmd!()
.args(&["-s", "1K"])
.pipe_in("A".repeat(2048))
.succeeds()
.stdout_is("0000400 4141 4141 4141 4141 4141 4141 4141 4141\n*\n0000800\n");
}
#[test]
fn test_hexdump_invalid_skip_and_length() {
new_ucmd!()
.args(&["-C", "-s", "invalid"])
.fails()
.code_is(1)
.stderr_contains("invalid skip");
new_ucmd!()
.args(&["-C", "-n", "invalid"])
.fails()
.code_is(1)
.stderr_contains("invalid length");
}
#[test]
#[cfg(unix)]
fn test_hexdump_skip_beyond_file() {
new_ucmd!()
.args(&["-C", "-s", "100"])
.pipe_in("ABC")
.succeeds()
.stdout_is("00000064\n");
}

View File

@@ -79,6 +79,10 @@ mod test_dmesg;
#[path = "by-util/test_fsfreeze.rs"] #[path = "by-util/test_fsfreeze.rs"]
mod test_fsfreeze; mod test_fsfreeze;
#[cfg(feature = "hexdump")]
#[path = "by-util/test_hexdump.rs"]
mod test_hexdump;
#[cfg(feature = "mcookie")] #[cfg(feature = "mcookie")]
#[path = "by-util/test_mcookie.rs"] #[path = "by-util/test_mcookie.rs"]
mod test_mcookie; mod test_mcookie;