mirror of
https://github.com/uutils/coreutils.git
synced 2026-06-10 15:48:22 -07:00
@@ -66,6 +66,7 @@ od-help-address-radix = Select the base in which file offsets are printed.
|
||||
od-help-skip-bytes = Skip bytes input bytes before formatting and writing.
|
||||
od-help-read-bytes = limit dump to BYTES input bytes
|
||||
od-help-endian = byte order to use for multi-byte formats
|
||||
od-help-strings = output strings of at least BYTES graphic chars. 3 is assumed when BYTES is not specified.
|
||||
od-help-a = named characters, ignoring high-order bit
|
||||
od-help-b = octal bytes
|
||||
od-help-c = ASCII characters or backslash escapes
|
||||
@@ -76,3 +77,17 @@ od-help-output-duplicates = do not use * to mark line suppression
|
||||
od-help-width = output BYTES bytes per output line. 32 is implied when BYTES is not
|
||||
specified.
|
||||
od-help-traditional = compatibility mode with one input, offset and label.
|
||||
od-help-o = octal 2-byte units
|
||||
od-help-capital-i = decimal 8-byte units
|
||||
od-help-capital-l = decimal 8-byte units
|
||||
od-help-i = decimal 4-byte units
|
||||
od-help-l = decimal 8-byte units
|
||||
od-help-x = hexadecimal 2-byte units
|
||||
od-help-h = hexadecimal 2-byte units
|
||||
od-help-capital-o = octal 4-byte units
|
||||
od-help-s = decimal 2-byte units
|
||||
od-help-capital-x = hexadecimal 4-byte units
|
||||
od-help-capital-h = hexadecimal 4-byte units
|
||||
od-help-e = floating point double precision (64-bit) units
|
||||
od-help-f = floating point double precision (32-bit) units
|
||||
od-help-capital-f = floating point double precision (64-bit) units
|
||||
|
||||
@@ -67,6 +67,7 @@ od-help-address-radix = Sélectionner la base dans laquelle les décalages de fi
|
||||
od-help-skip-bytes = Ignorer les octets d'entrée avant le formatage et l'écriture.
|
||||
od-help-read-bytes = limiter le dump à OCTETS octets d'entrée
|
||||
od-help-endian = ordre des octets à utiliser pour les formats multi-octets
|
||||
od-help-strings = afficher les chaînes d'au moins OCTETS caractères graphiques. 3 est supposé quand OCTETS n'est pas spécifié.
|
||||
od-help-a = caractères nommés, ignorant le bit d'ordre supérieur
|
||||
od-help-b = octets octaux
|
||||
od-help-c = caractères ASCII ou échappements antislash
|
||||
@@ -77,3 +78,17 @@ od-help-output-duplicates = ne pas utiliser * pour marquer la suppression de lig
|
||||
od-help-width = sortir OCTETS octets par ligne de sortie. 32 est impliqué quand OCTETS n'est pas
|
||||
spécifié.
|
||||
od-help-traditional = mode de compatibilité avec une entrée, décalage et étiquette.
|
||||
od-help-o = unités octales 2-octets
|
||||
od-help-capital-i = unités décimales 8-octets
|
||||
od-help-capital-l = unités décimales 8-octets
|
||||
od-help-i = unités décimales 4-octets
|
||||
od-help-l = unités décimales 8-octets
|
||||
od-help-x = unités hexadécimales 2-octets
|
||||
od-help-h = unités hexadécimales 2-octets
|
||||
od-help-capital-o = unités octales 4-octets
|
||||
od-help-s = unités décimales 2-octets
|
||||
od-help-capital-x = unités hexadécimales 4-octets
|
||||
od-help-capital-h = unités hexadécimales 4-octets
|
||||
od-help-e = unités virgule flottante double précision (64-bits)
|
||||
od-help-f = unités virgule flottante double précision (32-bits)
|
||||
od-help-capital-f = unités virgule flottante double précision (64-bits)
|
||||
|
||||
+186
-70
@@ -26,7 +26,7 @@ mod prn_int;
|
||||
|
||||
use std::cmp;
|
||||
use std::fmt::Write;
|
||||
use std::io::BufReader;
|
||||
use std::io::{BufReader, Read};
|
||||
|
||||
use crate::byteorder_io::ByteOrder;
|
||||
use crate::formatter_item_info::FormatWriter;
|
||||
@@ -76,6 +76,21 @@ struct OdOptions {
|
||||
line_bytes: usize,
|
||||
output_duplicates: bool,
|
||||
radix: Radix,
|
||||
string_min_length: Option<usize>,
|
||||
}
|
||||
|
||||
/// Helper function to parse bytes with error handling
|
||||
fn parse_bytes_option(matches: &ArgMatches, option_name: &str) -> UResult<Option<u64>> {
|
||||
match matches.get_one::<String>(option_name) {
|
||||
None => Ok(None),
|
||||
Some(s) => match parse_number_of_bytes(s) {
|
||||
Ok(n) => Ok(Some(n)),
|
||||
Err(e) => Err(USimpleError::new(
|
||||
1,
|
||||
format_error_message(&e, s, option_name),
|
||||
)),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
impl OdOptions {
|
||||
@@ -95,18 +110,7 @@ impl OdOptions {
|
||||
ByteOrder::Native
|
||||
};
|
||||
|
||||
let mut skip_bytes = match matches.get_one::<String>(options::SKIP_BYTES) {
|
||||
None => 0,
|
||||
Some(s) => match parse_number_of_bytes(s) {
|
||||
Ok(n) => n,
|
||||
Err(e) => {
|
||||
return Err(USimpleError::new(
|
||||
1,
|
||||
format_error_message(&e, s, options::SKIP_BYTES),
|
||||
));
|
||||
}
|
||||
},
|
||||
};
|
||||
let mut skip_bytes = parse_bytes_option(matches, options::SKIP_BYTES)?.unwrap_or(0);
|
||||
|
||||
let mut label: Option<u64> = None;
|
||||
|
||||
@@ -156,17 +160,16 @@ impl OdOptions {
|
||||
|
||||
let output_duplicates = matches.get_flag(options::OUTPUT_DUPLICATES);
|
||||
|
||||
let read_bytes = match matches.get_one::<String>(options::READ_BYTES) {
|
||||
let read_bytes = parse_bytes_option(matches, options::READ_BYTES)?;
|
||||
|
||||
let string_min_length = match parse_bytes_option(matches, options::STRINGS)? {
|
||||
None => None,
|
||||
Some(s) => match parse_number_of_bytes(s) {
|
||||
Ok(n) => Some(n),
|
||||
Err(e) => {
|
||||
return Err(USimpleError::new(
|
||||
1,
|
||||
format_error_message(&e, s, options::READ_BYTES),
|
||||
));
|
||||
}
|
||||
},
|
||||
Some(n) => Some(usize::try_from(n).map_err(|_| {
|
||||
USimpleError::new(
|
||||
1,
|
||||
translate!("od-error-argument-too-large", "option" => "-S", "value" => n.to_string()),
|
||||
)
|
||||
})?),
|
||||
};
|
||||
|
||||
let radix = match matches.get_one::<String>(options::ADDRESS_RADIX) {
|
||||
@@ -208,6 +211,7 @@ impl OdOptions {
|
||||
line_bytes,
|
||||
output_duplicates,
|
||||
radix,
|
||||
string_min_length,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -224,28 +228,39 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||
|
||||
let od_options = OdOptions::new(&clap_matches, &args)?;
|
||||
|
||||
let mut input_offset =
|
||||
InputOffset::new(od_options.radix, od_options.skip_bytes, od_options.label);
|
||||
// Check if we're in strings mode
|
||||
if let Some(min_length) = od_options.string_min_length {
|
||||
extract_strings_from_input(
|
||||
&od_options.input_strings,
|
||||
od_options.skip_bytes,
|
||||
od_options.read_bytes,
|
||||
min_length,
|
||||
od_options.radix,
|
||||
)
|
||||
} else {
|
||||
let mut input_offset =
|
||||
InputOffset::new(od_options.radix, od_options.skip_bytes, od_options.label);
|
||||
|
||||
let mut input = open_input_peek_reader(
|
||||
&od_options.input_strings,
|
||||
od_options.skip_bytes,
|
||||
od_options.read_bytes,
|
||||
);
|
||||
let mut input_decoder = InputDecoder::new(
|
||||
&mut input,
|
||||
od_options.line_bytes,
|
||||
PEEK_BUFFER_SIZE,
|
||||
od_options.byte_order,
|
||||
);
|
||||
let mut input = open_input_peek_reader(
|
||||
&od_options.input_strings,
|
||||
od_options.skip_bytes,
|
||||
od_options.read_bytes,
|
||||
);
|
||||
let mut input_decoder = InputDecoder::new(
|
||||
&mut input,
|
||||
od_options.line_bytes,
|
||||
PEEK_BUFFER_SIZE,
|
||||
od_options.byte_order,
|
||||
);
|
||||
|
||||
let output_info = OutputInfo::new(
|
||||
od_options.line_bytes,
|
||||
&od_options.formats[..],
|
||||
od_options.output_duplicates,
|
||||
);
|
||||
let output_info = OutputInfo::new(
|
||||
od_options.line_bytes,
|
||||
&od_options.formats[..],
|
||||
od_options.output_duplicates,
|
||||
);
|
||||
|
||||
odfunc(&mut input_offset, &mut input_decoder, &output_info)
|
||||
odfunc(&mut input_offset, &mut input_decoder, &output_info)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn uu_app() -> Command {
|
||||
@@ -263,7 +278,7 @@ pub fn uu_app() -> Command {
|
||||
Arg::new(options::HELP)
|
||||
.long(options::HELP)
|
||||
.help(translate!("od-help-help"))
|
||||
.action(ArgAction::Help)
|
||||
.action(ArgAction::Help),
|
||||
)
|
||||
.arg(
|
||||
Arg::new(options::ADDRESS_RADIX)
|
||||
@@ -297,10 +312,8 @@ pub fn uu_app() -> Command {
|
||||
Arg::new(options::STRINGS)
|
||||
.short('S')
|
||||
.long(options::STRINGS)
|
||||
.help(
|
||||
"NotImplemented: output strings of at least BYTES graphic chars. 3 is assumed when \
|
||||
BYTES is not specified.",
|
||||
)
|
||||
.help(translate!("od-help-strings"))
|
||||
.num_args(0..=1)
|
||||
.default_missing_value("3")
|
||||
.value_name("BYTES"),
|
||||
)
|
||||
@@ -337,85 +350,85 @@ pub fn uu_app() -> Command {
|
||||
.arg(
|
||||
Arg::new("o")
|
||||
.short('o')
|
||||
.help("octal 2-byte units")
|
||||
.help(translate!("od-help-o"))
|
||||
.action(ArgAction::SetTrue),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("I")
|
||||
.short('I')
|
||||
.help("decimal 8-byte units")
|
||||
.help(translate!("od-help-capital-i"))
|
||||
.action(ArgAction::SetTrue),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("L")
|
||||
.short('L')
|
||||
.help("decimal 8-byte units")
|
||||
.help(translate!("od-help-capital-l"))
|
||||
.action(ArgAction::SetTrue),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("i")
|
||||
.short('i')
|
||||
.help("decimal 4-byte units")
|
||||
.help(translate!("od-help-i"))
|
||||
.action(ArgAction::SetTrue),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("l")
|
||||
.short('l')
|
||||
.help("decimal 8-byte units")
|
||||
.help(translate!("od-help-l"))
|
||||
.action(ArgAction::SetTrue),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("x")
|
||||
.short('x')
|
||||
.help("hexadecimal 2-byte units")
|
||||
.help(translate!("od-help-x"))
|
||||
.action(ArgAction::SetTrue),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("h")
|
||||
.short('h')
|
||||
.help("hexadecimal 2-byte units")
|
||||
.help(translate!("od-help-h"))
|
||||
.action(ArgAction::SetTrue),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("O")
|
||||
.short('O')
|
||||
.help("octal 4-byte units")
|
||||
.action(ArgAction::SetTrue)
|
||||
.help(translate!("od-help-capital-o"))
|
||||
.action(ArgAction::SetTrue),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("s")
|
||||
.short('s')
|
||||
.help("decimal 2-byte units")
|
||||
.help(translate!("od-help-s"))
|
||||
.action(ArgAction::SetTrue),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("X")
|
||||
.short('X')
|
||||
.help("hexadecimal 4-byte units")
|
||||
.help(translate!("od-help-capital-x"))
|
||||
.action(ArgAction::SetTrue),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("H")
|
||||
.short('H')
|
||||
.help("hexadecimal 4-byte units")
|
||||
.help(translate!("od-help-capital-h"))
|
||||
.action(ArgAction::SetTrue),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("e")
|
||||
.short('e')
|
||||
.help("floating point double precision (64-bit) units")
|
||||
.help(translate!("od-help-e"))
|
||||
.action(ArgAction::SetTrue),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("f")
|
||||
.short('f')
|
||||
.help("floating point double precision (32-bit) units")
|
||||
.help(translate!("od-help-f"))
|
||||
.action(ArgAction::SetTrue),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("F")
|
||||
.short('F')
|
||||
.help("floating point double precision (64-bit) units")
|
||||
.help(translate!("od-help-capital-f"))
|
||||
.action(ArgAction::SetTrue),
|
||||
)
|
||||
.arg(
|
||||
@@ -532,6 +545,105 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// Extract and display printable strings from input (od -S option)
|
||||
fn extract_strings_from_input(
|
||||
input_strings: &[String],
|
||||
skip_bytes: u64,
|
||||
read_bytes: Option<u64>,
|
||||
min_length: usize,
|
||||
radix: Radix,
|
||||
) -> UResult<()> {
|
||||
let inputs = map_input_strings(input_strings);
|
||||
let mut mf = MultifileReader::new(inputs);
|
||||
|
||||
// Apply skip_bytes by reading and discarding
|
||||
let mut skipped = 0u64;
|
||||
while skipped < skip_bytes {
|
||||
let to_skip = std::cmp::min(8192, skip_bytes - skipped);
|
||||
let mut skip_buf = vec![0u8; to_skip as usize];
|
||||
match mf.read(&mut skip_buf) {
|
||||
Ok(0) => break, // EOF reached
|
||||
Ok(n) => skipped += n as u64,
|
||||
Err(_) => break,
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to format and print a string
|
||||
let print_string = |offset: u64, string: &[u8]| {
|
||||
let string_content = String::from_utf8_lossy(string);
|
||||
match radix {
|
||||
Radix::NoPrefix => println!("{string_content}"),
|
||||
Radix::Decimal => println!("{offset:07} {string_content}"),
|
||||
Radix::Hexadecimal => println!("{offset:07x} {string_content}"),
|
||||
Radix::Octal => println!("{offset:07o} {string_content}"),
|
||||
}
|
||||
};
|
||||
|
||||
let mut current_string = Vec::new();
|
||||
let mut string_start_offset = 0u64;
|
||||
let mut current_offset = skip_bytes;
|
||||
let mut bytes_read = 0u64;
|
||||
let mut buf = [0u8; 1];
|
||||
|
||||
loop {
|
||||
// Check if we've reached the read_bytes limit
|
||||
if let Some(limit) = read_bytes {
|
||||
if bytes_read >= limit {
|
||||
// Special case: when -N limit is reached with a pending string
|
||||
// that meets min_length, output it even without null terminator
|
||||
if current_string.len() >= min_length {
|
||||
print_string(string_start_offset, ¤t_string);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Read one byte at a time
|
||||
match mf.read(&mut buf) {
|
||||
Ok(0) => break, // EOF
|
||||
Ok(_) => {
|
||||
bytes_read += 1;
|
||||
let byte = buf[0];
|
||||
|
||||
// Check if it's a printable character (including space)
|
||||
if (0x20..=0x7E).contains(&byte) {
|
||||
if current_string.is_empty() {
|
||||
string_start_offset = current_offset;
|
||||
}
|
||||
current_string.push(byte);
|
||||
} else {
|
||||
// Either null terminator or non-printable character
|
||||
if byte == 0 && current_string.len() >= min_length {
|
||||
// Null terminator found with valid string
|
||||
print_string(string_start_offset, ¤t_string);
|
||||
}
|
||||
current_string.clear();
|
||||
}
|
||||
|
||||
current_offset += 1;
|
||||
}
|
||||
Err(e) => {
|
||||
// Note: GNU od does not output unterminated strings at EOF
|
||||
// Strings must be null-terminated to be output
|
||||
if mf.has_error() {
|
||||
show_error!("{}", e);
|
||||
return Err(1.into());
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// GNU od doesn't output an offset when strings mode finds no valid strings
|
||||
// This includes cases with only unterminated or too-short strings
|
||||
|
||||
if mf.has_error() {
|
||||
Err(1.into())
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Outputs a single line of input, into one or more lines human readable output.
|
||||
fn print_bytes(prefix: &str, input_decoder: &MemoryDecoder, output_info: &OutputInfo) {
|
||||
let mut first = true; // First line of a multi-format raster.
|
||||
@@ -595,6 +707,17 @@ fn print_bytes(prefix: &str, input_decoder: &MemoryDecoder, output_info: &Output
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper function to convert input strings to InputSource
|
||||
fn map_input_strings(input_strings: &[String]) -> Vec<InputSource<'_>> {
|
||||
input_strings
|
||||
.iter()
|
||||
.map(|w| match w as &str {
|
||||
"-" => InputSource::Stdin,
|
||||
x => InputSource::FileName(x),
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// returns a reader implementing `PeekRead + Read + HasError` providing the combined input
|
||||
///
|
||||
/// `skip_bytes` is the number of bytes skipped from the input
|
||||
@@ -605,14 +728,7 @@ fn open_input_peek_reader(
|
||||
read_bytes: Option<u64>,
|
||||
) -> PeekReader<BufReader<PartialReader<MultifileReader<'_>>>> {
|
||||
// should return "impl PeekRead + Read + HasError" when supported in (stable) rust
|
||||
let inputs = input_strings
|
||||
.iter()
|
||||
.map(|w| match w as &str {
|
||||
"-" => InputSource::Stdin,
|
||||
x => InputSource::FileName(x),
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let inputs = map_input_strings(input_strings);
|
||||
let mf = MultifileReader::new(inputs);
|
||||
let pr = PartialReader::new(mf, skip_bytes, read_bytes);
|
||||
// Add a BufReader over the top of the PartialReader. This will have the
|
||||
|
||||
@@ -992,3 +992,68 @@ fn test_od_options_after_filename() {
|
||||
.no_stderr()
|
||||
.stdout_is(" 1c68 fdbb\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_od_strings_option() {
|
||||
// Test -S option: output strings of at least N graphic chars
|
||||
|
||||
// Test -S0: output all null-terminated strings regardless of length
|
||||
new_ucmd!()
|
||||
.arg("-S0")
|
||||
.pipe_in(b"hello\x00world\x00")
|
||||
.succeeds()
|
||||
.stdout_is("0000000 hello\n0000006 world\n");
|
||||
|
||||
// Test -S0 with single character strings
|
||||
new_ucmd!()
|
||||
.arg("-S0")
|
||||
.pipe_in(b"a\x00b\x00cd\x00")
|
||||
.succeeds()
|
||||
.stdout_is("0000000 a\n0000002 b\n0000004 cd\n");
|
||||
|
||||
// Test with null-terminated strings
|
||||
new_ucmd!()
|
||||
.arg("-S3")
|
||||
.pipe_in(b"\x01hello\x00world\x00ab\x00")
|
||||
.succeeds()
|
||||
.stdout_is("0000001 hello\n0000007 world\n");
|
||||
|
||||
// Test with -S10 to show only strings with minimum length
|
||||
new_ucmd!()
|
||||
.arg("-S10")
|
||||
.pipe_in(b"\x01 \x00 \x00")
|
||||
.succeeds()
|
||||
.stdout_is("0000001 \n0000014 \n");
|
||||
|
||||
// Test with unterminated string at EOF (should not output)
|
||||
new_ucmd!()
|
||||
.arg("-S10")
|
||||
.pipe_in(b" ")
|
||||
.succeeds()
|
||||
.stdout_is("");
|
||||
|
||||
// Test with -N limit and pending string (should output even without null)
|
||||
let expected = "0000000 \n";
|
||||
new_ucmd!()
|
||||
.arg("-N2")
|
||||
.arg("-S1")
|
||||
.pipe_in(" ")
|
||||
.succeeds()
|
||||
.stdout_is(expected);
|
||||
|
||||
// Test with -N limit and string too short
|
||||
new_ucmd!()
|
||||
.arg("-N11")
|
||||
.arg("-S11")
|
||||
.pipe_in(b" \x00")
|
||||
.succeeds()
|
||||
.stdout_is("");
|
||||
|
||||
// Test with no address prefix (-An)
|
||||
new_ucmd!()
|
||||
.arg("-S3")
|
||||
.arg("-An")
|
||||
.pipe_in(b"hello\x00world\x00")
|
||||
.succeeds()
|
||||
.stdout_is("hello\nworld\n");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user