1
Fork 0
mirror of https://github.com/RGBCube/uutils-coreutils synced 2025-07-28 19:47:45 +00:00

dd: only print concise byte counts if large enough

Update `dd` to only print a concise form of the number of bytes with
an SI prefix (like "1 MB" or "2 GB") if the number is at least
1000. Similarly, only print the concise form with an IEC prefix (like
"1 MiB" or "2 GiB") if the number is at least 1024. For example,

    $ head -c 999 /dev/zero | dd > /dev/null
    1+1 records in
    1+1 records out
    999 bytes copied, 0.0 s, 999.0 KB/s

    $ head -c 1000 /dev/zero | dd > /dev/null
    1+1 records in
    1+1 records out
    1000 bytes (1000 B) copied, 0.0 s, 1000.0 KB/s

    $ head -c 1024 /dev/zero | dd > /dev/null
    2+0 records in
    2+0 records out
    1024 bytes (1 KB, 1024 B) copied, 0.0 s, 1.0 MB/s
This commit is contained in:
Jeffrey Finkelstein 2022-06-11 22:41:54 -04:00
parent 98cf16586e
commit a375644c50
2 changed files with 96 additions and 47 deletions

View file

@ -118,10 +118,7 @@ impl ProgUpdate {
/// let mut cursor = Cursor::new(vec![]); /// let mut cursor = Cursor::new(vec![]);
/// let rewrite = false; /// let rewrite = false;
/// prog_update.write_prog_line(&mut cursor, rewrite).unwrap(); /// prog_update.write_prog_line(&mut cursor, rewrite).unwrap();
/// assert_eq!( /// assert_eq!(cursor.get_ref(), b"0 bytes copied, 1.0 s, 0 B/s\n");
/// cursor.get_ref(),
/// b"0 bytes (0 B, 0 B) copied, 1.0 s, 0 B/s\n"
/// );
/// ``` /// ```
fn write_prog_line(&self, w: &mut impl Write, rewrite: bool) -> std::io::Result<()> { fn write_prog_line(&self, w: &mut impl Write, rewrite: bool) -> std::io::Result<()> {
let btotal_metric = Byte::from_bytes(self.write_stat.bytes_total) let btotal_metric = Byte::from_bytes(self.write_stat.bytes_total)
@ -137,17 +134,38 @@ impl ProgUpdate {
let btotal = self.write_stat.bytes_total; let btotal = self.write_stat.bytes_total;
let duration = self.duration.as_secs_f64(); let duration = self.duration.as_secs_f64();
if rewrite {
// If we are rewriting the progress line, do write a carriage
// return (`\r`) at the beginning and don't write a newline
// (`\n`) at the end.
let (carriage_return, newline) = if rewrite { ("\r", "") } else { ("", "\n") };
// If the number of bytes written is sufficiently large, then
// print a more concise representation of the number, like
// "1.2 kB" and "1.0 KiB".
if btotal < 1000 {
write!( write!(
w, w,
"\r{} bytes ({}, {}) copied, {:.1} s, {}/s", "{}{} bytes copied, {:.1} s, {}/s{}",
btotal, btotal_metric, btotal_bin, duration, transfer_rate carriage_return, btotal, duration, transfer_rate, newline,
)
} else if btotal < 1024 {
write!(
w,
"{}{} bytes ({}) copied, {:.1} s, {}/s{}",
carriage_return, btotal, btotal_metric, duration, transfer_rate, newline,
) )
} else { } else {
writeln!( write!(
w, w,
"{} bytes ({}, {}) copied, {:.1} s, {}/s", "{}{} bytes ({}, {}) copied, {:.1} s, {}/s{}",
btotal, btotal_metric, btotal_bin, duration, transfer_rate carriage_return,
btotal,
btotal_metric,
btotal_bin,
duration,
transfer_rate,
newline,
) )
} }
} }
@ -176,10 +194,7 @@ impl ProgUpdate {
/// let mut iter = cursor.get_ref().split(|v| *v == b'\n'); /// let mut iter = cursor.get_ref().split(|v| *v == b'\n');
/// assert_eq!(iter.next().unwrap(), b"0+0 records in"); /// assert_eq!(iter.next().unwrap(), b"0+0 records in");
/// assert_eq!(iter.next().unwrap(), b"0+0 records out"); /// assert_eq!(iter.next().unwrap(), b"0+0 records out");
/// assert_eq!( /// assert_eq!(iter.next().unwrap(), b"0 bytes copied, 1.0 s, 0 B/s");
/// iter.next().unwrap(),
/// b"0 bytes (0 B, 0 B) copied, 1.0 s, 0 B/s"
/// );
/// assert_eq!(iter.next().unwrap(), b""); /// assert_eq!(iter.next().unwrap(), b"");
/// assert!(iter.next().is_none()); /// assert!(iter.next().is_none());
/// ``` /// ```
@ -437,6 +452,17 @@ mod tests {
use super::{ProgUpdate, ReadStat, WriteStat}; use super::{ProgUpdate, ReadStat, WriteStat};
fn prog_update_write(n: u128) -> ProgUpdate {
ProgUpdate {
read_stat: Default::default(),
write_stat: WriteStat {
bytes_total: n,
..Default::default()
},
duration: Duration::new(1, 0), // one second
}
}
#[test] #[test]
fn test_read_stat_report() { fn test_read_stat_report() {
let read_stat = ReadStat::new(1, 2, 3); let read_stat = ReadStat::new(1, 2, 3);
@ -474,12 +500,7 @@ mod tests {
#[test] #[test]
fn test_prog_update_write_prog_line() { fn test_prog_update_write_prog_line() {
let prog_update = ProgUpdate { let prog_update = prog_update_write(0);
read_stat: Default::default(),
write_stat: Default::default(),
duration: Duration::new(1, 0), // one second
};
let mut cursor = Cursor::new(vec![]); let mut cursor = Cursor::new(vec![]);
let rewrite = false; let rewrite = false;
prog_update.write_prog_line(&mut cursor, rewrite).unwrap(); prog_update.write_prog_line(&mut cursor, rewrite).unwrap();
@ -489,9 +510,38 @@ mod tests {
// $ : | dd // $ : | dd
// 0 bytes copied, 7.9151e-05 s, 0.0 kB/s // 0 bytes copied, 7.9151e-05 s, 0.0 kB/s
// //
// The throughput still does not match GNU dd. For the ones
// that include the concise byte counts, the format is also
// not right.
assert_eq!(cursor.get_ref(), b"0 bytes copied, 1.0 s, 0 B/s\n");
let prog_update = prog_update_write(999);
let mut cursor = Cursor::new(vec![]);
prog_update.write_prog_line(&mut cursor, rewrite).unwrap();
assert_eq!(cursor.get_ref(), b"999 bytes copied, 1.0 s, 0 B/s\n");
let prog_update = prog_update_write(1000);
let mut cursor = Cursor::new(vec![]);
prog_update.write_prog_line(&mut cursor, rewrite).unwrap();
assert_eq!( assert_eq!(
cursor.get_ref(), cursor.get_ref(),
b"0 bytes (0 B, 0 B) copied, 1.0 s, 0 B/s\n" b"1000 bytes (1000 B) copied, 1.0 s, 1000 B/s\n"
);
let prog_update = prog_update_write(1023);
let mut cursor = Cursor::new(vec![]);
prog_update.write_prog_line(&mut cursor, rewrite).unwrap();
assert_eq!(
cursor.get_ref(),
b"1023 bytes (1 KB) copied, 1.0 s, 1000 B/s\n"
);
let prog_update = prog_update_write(1024);
let mut cursor = Cursor::new(vec![]);
prog_update.write_prog_line(&mut cursor, rewrite).unwrap();
assert_eq!(
std::str::from_utf8(cursor.get_ref()).unwrap(),
"1024 bytes (1 KB, 1024 B) copied, 1.0 s, 1000 B/s\n"
); );
} }
@ -507,10 +557,7 @@ mod tests {
let mut iter = cursor.get_ref().split(|v| *v == b'\n'); let mut iter = cursor.get_ref().split(|v| *v == b'\n');
assert_eq!(iter.next().unwrap(), b"0+0 records in"); assert_eq!(iter.next().unwrap(), b"0+0 records in");
assert_eq!(iter.next().unwrap(), b"0+0 records out"); assert_eq!(iter.next().unwrap(), b"0+0 records out");
assert_eq!( assert_eq!(iter.next().unwrap(), b"0 bytes copied, 1.0 s, 0 B/s");
iter.next().unwrap(),
b"0 bytes (0 B, 0 B) copied, 1.0 s, 0 B/s"
);
assert_eq!(iter.next().unwrap(), b""); assert_eq!(iter.next().unwrap(), b"");
assert!(iter.next().is_none()); assert!(iter.next().is_none());
} }

View file

@ -248,17 +248,10 @@ fn test_final_stats_noxfer() {
#[test] #[test]
fn test_final_stats_unspec() { fn test_final_stats_unspec() {
let output = vec![ new_ucmd!()
"0+0 records in", .run()
"0+0 records out", .stderr_only("0+0 records in\n0+0 records out\n0 bytes copied, 0.0 s, 0 B/s\n")
"0 bytes (0 B, 0 B) copied, 0.0 s, 0 B/s", .success();
];
let output = output.into_iter().fold(String::new(), |mut acc, s| {
acc.push_str(s);
acc.push('\n');
acc
});
new_ucmd!().run().stderr_only(&output).success();
} }
#[cfg(any(target_os = "linux", target_os = "android"))] #[cfg(any(target_os = "linux", target_os = "android"))]
@ -370,20 +363,10 @@ fn test_existing_file_truncated() {
#[test] #[test]
fn test_null_stats() { fn test_null_stats() {
let stats = vec![
"0+0 records in\n",
"0+0 records out\n",
"0 bytes (0 B, 0 B) copied, 0.0 s, 0 B/s\n",
];
let stats = stats.into_iter().fold(String::new(), |mut acc, s| {
acc.push_str(s);
acc
});
new_ucmd!() new_ucmd!()
.args(&["if=null.txt"]) .args(&["if=null.txt"])
.run() .run()
.stderr_only(stats) .stderr_only("0+0 records in\n0+0 records out\n0 bytes copied, 0.0 s, 0 B/s\n")
.success(); .success();
} }
@ -1184,3 +1167,22 @@ fn test_bytes_oseek_seek_additive() {
.succeeds() .succeeds()
.stdout_is_fixture_bytes("dd-bytes-alphabet-null.spec"); .stdout_is_fixture_bytes("dd-bytes-alphabet-null.spec");
} }
#[test]
fn test_final_stats_si_iec() {
let result = new_ucmd!().pipe_in("0".repeat(999)).succeeds();
let s = result.stderr_str();
assert!(s.starts_with("1+1 records in\n1+1 records out\n999 bytes copied,"));
let result = new_ucmd!().pipe_in("0".repeat(1000)).succeeds();
let s = result.stderr_str();
assert!(s.starts_with("1+1 records in\n1+1 records out\n1000 bytes (1000 B) copied,"));
let result = new_ucmd!().pipe_in("0".repeat(1023)).succeeds();
let s = result.stderr_str();
assert!(s.starts_with("1+1 records in\n1+1 records out\n1023 bytes (1 KB) copied,"));
let result = new_ucmd!().pipe_in("0".repeat(1024)).succeeds();
let s = result.stderr_str();
assert!(s.starts_with("2+0 records in\n2+0 records out\n1024 bytes (1 KB, 1024 B) copied,"));
}