From 2fd7164cda09c5e028c23f7db19d65eb6e9bd256 Mon Sep 17 00:00:00 2001 From: Valentin Lorentz Date: Sat, 2 Apr 2016 12:32:33 +0200 Subject: [PATCH] tail: Implement `tail -z`. This options makes tail use NULL instead of newline as a line delimiter. --- src/tail/tail.rs | 27 +++++++++++------- tests/fixtures/tail/foobar_with_null.txt | Bin 0 -> 60 bytes .../tail/foobar_with_null_default.expected | Bin 0 -> 56 bytes tests/tail.rs | 8 ++++++ 4 files changed, 25 insertions(+), 10 deletions(-) create mode 100644 tests/fixtures/tail/foobar_with_null.txt create mode 100644 tests/fixtures/tail/foobar_with_null_default.expected diff --git a/src/tail/tail.rs b/src/tail/tail.rs index 865f4f40c..d8b6d28cb 100755 --- a/src/tail/tail.rs +++ b/src/tail/tail.rs @@ -28,7 +28,7 @@ static VERSION: &'static str = env!("CARGO_PKG_VERSION"); enum FilterMode { Bytes(usize), - Lines(usize), + Lines(usize, u8), // (number of lines, delimiter) } struct Settings { @@ -41,7 +41,7 @@ struct Settings { impl Default for Settings { fn default() -> Settings { Settings { - mode: FilterMode::Lines(10), + mode: FilterMode::Lines(10, '\n' as u8), sleep_msec: 1000, beginning: false, follow: false, @@ -54,7 +54,7 @@ pub fn uumain(args: Vec) -> i32 { // handle obsolete -number syntax let options = match obsolete(&args[1..]) { - (args, Some(n)) => { settings.mode = FilterMode::Lines(n); args }, + (args, Some(n)) => { settings.mode = FilterMode::Lines(n, '\n' as u8); args }, (args, None) => args }; @@ -66,6 +66,7 @@ pub fn uumain(args: Vec) -> i32 { opts.optopt("n", "lines", "Number of lines to print", "k"); opts.optflag("f", "follow", "Print the file as it grows"); opts.optopt("s", "sleep-interval", "Number or seconds to sleep between polling the file when running with -f", "n"); + opts.optflag("z", "zero-terminated", "Line delimiter is NUL, not newline"); opts.optflag("h", "help", "help"); opts.optflag("V", "version", "version"); @@ -105,7 +106,7 @@ pub fn uumain(args: Vec) -> i32 { slice = &slice[1..]; } match parse_size(slice) { - Some(m) => settings.mode = FilterMode::Lines(m), + Some(m) => settings.mode = FilterMode::Lines(m, '\n' as u8), None => { show_error!("invalid number of lines ({})", slice); return 1; @@ -131,6 +132,12 @@ pub fn uumain(args: Vec) -> i32 { } }; + if given_options.opt_present("z") { + if let FilterMode::Lines(count, _) = settings.mode { + settings.mode = FilterMode::Lines(count, 0); + } + } + let files = given_options.free; if files.is_empty() { @@ -269,7 +276,7 @@ fn follow(mut reader: BufReader, settings: &Settings) { /// Iterate over bytes in the file, in reverse, until `should_stop` returns /// true. The `file` is left seek'd to the position just after the byte that /// `should_stop` returned true for. -fn backwards_thru_file(file: &mut File, size: u64, buf: &mut Vec, should_stop: &mut F) +fn backwards_thru_file(file: &mut File, size: u64, buf: &mut Vec, delimiter: u8, should_stop: &mut F) where F: FnMut(u8) -> bool { assert!(buf.len() >= BLOCK_SIZE as usize); @@ -295,7 +302,7 @@ fn backwards_thru_file(file: &mut File, size: u64, buf: &mut Vec, should_ let slice = &buf[0..(block_size as usize)]; for (i, ch) in slice.iter().enumerate().rev() { // Ignore one trailing newline. - if block_idx == 0 && i as u64 == block_size - 1 && *ch == ('\n' as u8) { + if block_idx == 0 && i as u64 == block_size - 1 && *ch == delimiter { continue; } @@ -326,9 +333,9 @@ fn bounded_tail(mut file: File, settings: &Settings) { // Find the position in the file to start printing from. match settings.mode { - FilterMode::Lines(mut count) => { - backwards_thru_file(&mut file, size, &mut buf, &mut |byte| { - if byte == ('\n' as u8) { + FilterMode::Lines(mut count, delimiter) => { + backwards_thru_file(&mut file, size, &mut buf, delimiter, &mut |byte| { + if byte == delimiter { count -= 1; count == 0 } else { @@ -367,7 +374,7 @@ fn unbounded_tail(mut reader: BufReader, settings: &Settings) { // contains count lines/chars. When reaching the end of file, output the // data in the ringbuf. match settings.mode { - FilterMode::Lines(mut count) => { + FilterMode::Lines(mut count, _delimiter) => { let mut ringbuf: VecDeque = VecDeque::new(); let mut skip = if settings.beginning { let temp = count; diff --git a/tests/fixtures/tail/foobar_with_null.txt b/tests/fixtures/tail/foobar_with_null.txt new file mode 100644 index 0000000000000000000000000000000000000000..f6b05b48a6a06dc56a8209a220ad31380fdc4014 GIT binary patch literal 60 zcmXTT%V$W*FJ>qyN-bt6EKMvaDavO^&df{BXDCk1EM_RqOf4x%Wynv?$Y;naO)X1h PNXbmCV#v=+PGtZ9hrkrl literal 0 HcmV?d00001 diff --git a/tests/fixtures/tail/foobar_with_null_default.expected b/tests/fixtures/tail/foobar_with_null_default.expected new file mode 100644 index 0000000000000000000000000000000000000000..53a2397c91f42ccac9abc042a48dc35f01ebb940 GIT binary patch literal 56 zcmYevFJ>qyN-bt6EKMvaDavO^&df{BXDCk1EM_RqOf4x%Wynv?$Y;naO)X1hNXbmC LV#v=+PGtZ9InWb; literal 0 HcmV?d00001 diff --git a/tests/tail.rs b/tests/tail.rs index ab4abaccd..d840e9c84 100644 --- a/tests/tail.rs +++ b/tests/tail.rs @@ -8,6 +8,7 @@ use common::util::*; static UTIL_NAME: &'static str = "tail"; static FOOBAR_TXT: &'static str = "foobar.txt"; +static FOOBAR_WITH_NULL_TXT: &'static str = "foobar_with_null.txt"; #[test] fn test_stdin_default() { @@ -30,6 +31,13 @@ fn test_n_greater_than_number_of_lines() { assert_eq!(result.stdout, at.read(FOOBAR_TXT)); } +#[test] +fn test_null_default() { + let (at, mut ucmd) = testing(UTIL_NAME); + let result = ucmd.arg("-z").arg(FOOBAR_WITH_NULL_TXT).run(); + assert_eq!(result.stdout, at.read("foobar_with_null_default.expected")); +} + #[test] fn test_single_big_args() { const FILE: &'static str = "single_big_args.txt";