diff --git a/Makefile b/Makefile index 3a6bba447..6808bd223 100644 --- a/Makefile +++ b/Makefile @@ -37,6 +37,7 @@ PROGS := \ wc \ yes \ head \ + tail \ UNIX_PROGS := \ hostid \ diff --git a/README.md b/README.md index 4abdbfb52..907c98284 100644 --- a/README.md +++ b/README.md @@ -132,7 +132,7 @@ To do - stty (in progress) - sync - tac-pipe -- tail +- tail (not all features implemented) - test - timeout - tsort diff --git a/tail/README.md b/tail/README.md new file mode 100644 index 000000000..8d7faf594 --- /dev/null +++ b/tail/README.md @@ -0,0 +1,17 @@ +Rudamentary tail implementation. + +##Missing features: + +### Flags with features +* `--bytes` : output the last K bytes; alternatively, use `-c` +K to output bytes starting with the Kth of each file +* `--max-unchanged-stats` : with `--follow=name`, reopen a FILE which has not changed size after N (default 5) iterations to see if it has been unlinked or renamed (this is the usual case of rotated log files). With inotify, this option is rarely useful. +* `--pid` : with `-f`, terminate after process ID, PID dies +* `--quiet` : never output headers giving file names +* `--retry` : keep trying to open a file even when it is or becomes inaccessible; useful when follow‐ing by name, i.e., with `--follow=name` +* `--verbose` : always output headers giving file names + +### Others +The current implementation does not handle `-` as an alias for stdin. + +##Possible optimizations: +* Don't read the whole file if not using `-f` and input is regular file. Read in chunks from the end going backwards, reading each individual chunk forward. diff --git a/tail/tail.rs b/tail/tail.rs new file mode 100644 index 000000000..50588d8a5 --- /dev/null +++ b/tail/tail.rs @@ -0,0 +1,185 @@ +#![crate_id(name="tail", vers="1.0.0", author="Morten Olsen Lysgaard")] +/* + * This file is part of the uutils coreutils package. + * + * (c) Morten Olsen Lysgaard + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + */ + +extern crate getopts; + +use std::os; +use std::char; +use std::io::{stdin}; +use std::io::BufferedReader; +use std::io::fs::File; +use std::path::Path; +use getopts::{optopt, optflag, getopts, usage}; +use std::collections::Deque; +use std::collections::ringbuf::RingBuf; +use std::io::timer::sleep; + +static PROGRAM: &'static str = "tail"; + +#[allow(dead_code)] +fn main () { uumain(os::args()); } + +pub fn uumain(args: Vec) { + let mut line_count = 10u; + let mut sleep_sec = 1000u; + + // handle obsolete -number syntax + let options = match obsolete(args.tail()) { + (args, Some(n)) => { line_count = n; args }, + (args, None) => args + }; + + let args = options; + + let possible_options = [ + optopt("n", "number", "Number of lines to print", "n"), + optflag("f", "follow", "Print the file as it grows"), + optopt("s", "sleep-interval", "Number or seconds to sleep between polling the file when running with -f", "n"), + optflag("h", "help", "help"), + optflag("V", "version", "version"), + ]; + + let given_options = match getopts(args.as_slice(), possible_options) { + Ok (m) => { m } + Err(_) => { + println!("{:s}", usage(PROGRAM, possible_options)); + return + } + }; + + if given_options.opt_present("h") { + println!("{:s}", usage(PROGRAM, possible_options)); + return; + } + if given_options.opt_present("V") { version(); return } + + let follow = given_options.opt_present("f"); + if follow { + match given_options.opt_str("s") { + Some(n) => { + let parsed : Option = from_str(n.as_slice()); + match parsed { + Some(m) => { sleep_sec = m*1000 } + None => {} + } + } + None => {} + }; + } + + match given_options.opt_str("n") { + Some(n) => { + match from_str(n.as_slice()) { + Some(m) => { line_count = m } + None => {} + } + } + None => {} + }; + + let files = given_options.free; + + if files.is_empty() { + let mut buffer = BufferedReader::new(stdin()); + tail(&mut buffer, line_count, follow, sleep_sec); + } else { + let mut multiple = false; + let mut firstime = true; + + if files.len() > 1 { + multiple = true; + } + + + for file in files.iter() { + if multiple { + if !firstime { println!(""); } + println!("==> {:s} <==", file.as_slice()); + } + firstime = false; + + let path = Path::new(file.as_slice()); + let reader = File::open(&path).unwrap(); + let mut buffer = BufferedReader::new(reader); + tail(&mut buffer, line_count, follow, sleep_sec); + } + } +} + +// It searches for an option in the form of -123123 +// +// In case is found, the options vector will get rid of that object so that +// getopts works correctly. +fn obsolete (options: &[String]) -> (Vec, Option) { + let mut options: Vec = Vec::from_slice(options); + let mut a = 0; + let b = options.len(); + + while a < b { + let current = options.get(a).clone(); + let current = current.as_slice(); + + if current.len() > 1 && current[0] == '-' as u8 { + let len = current.len(); + for pos in range(1, len) { + // Ensure that the argument is only made out of digits + if !char::is_digit(current.char_at(pos)) { break; } + + // If this is the last number + if pos == len - 1 { + options.remove(a); + let number : Option = from_str(current.slice(1,len)); + return (options, Some(number.unwrap())); + } + } + } + + a += 1; + }; + + (options, None) +} + +fn tail (reader: &mut BufferedReader, line_count:uint, follow:bool, sleep_sec:u64) { + // read trough each line and store them in a ringbuffer that always contains + // line_count lines. When reaching the end of file, output the lines in the + // ringbuf. + let mut ringbuf : RingBuf = RingBuf::new(); + for io_line in reader.lines(){ + match io_line { + Ok(line) => { + if line_count<=ringbuf.len(){ + ringbuf.pop_front(); + } + ringbuf.push_back(line); + } + Err(err) => fail!(err) + } + } + for line in ringbuf.iter() { + print!("{}", line); + } + + // if we follow the file, sleep a bit and print the rest if the file has grown. + while follow { + sleep(sleep_sec); + for io_line in reader.lines() { + match io_line { + Ok(line) => print!("{}", line), + Err(err) => fail!(err) + } + } + } +} + +fn version () { + println!("tail version 0.0.1"); +}