tailの実装
Posted feedbacks - Nested
Flatten Hidden愚直にキューをつかって。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | (use gauche.parseopt)
(use gauche.selector)
(use util.queue)
(define (main args)
(let-args (cdr args)
((nlines "n=n" 10)
(f? "f" #f)
. argv)
(call-with-input-file (car argv)
(lambda (iport)
(let ((q (make-queue)))
(let loop ((n 0)
(line (read-line iport)))
(unless (or (= n nlines) (eof-object? line))
(enqueue! q line)
(loop (+ n 1) (read-line iport))))
(do ((line (read-line iport) (read-line iport)))
((eof-object? line))
(enqueue! q line)
(dequeue! q))
(for-each print (queue->list q)))
(when f?
(with-signal-handlers ((SIGINT => (lambda _ (exit 0))))
(lambda ()
(let ((sel (make <selector>)))
(selector-add! sel
iport
(lambda (input _)
(and-let* ((line (read-line input))
((not (eof-object? line))))
(print line))
(sys-sleep 1))
'(r))
(while #t (selector-select sel))))))
0))))
|
tailの仕様がわかりません。
検索すると概要はわかるのですが、実際にどういった動作をさせれば良いのでしょう?
UNIX環境が無いのでどうしたらよいのか…。
検索すると概要はわかるのですが、実際にどういった動作をさせれば良いのでしょう?
UNIX環境が無いのでどうしたらよいのか…。
たしかにお題を出す人にも、もうすこし丁寧に出題してほしい感じがしますね。 たぶんですが、 意訳すると以下のような感じになるのではないでしょうか? 1. 指定されたファイルの最後尾から N行表示すること 2. ファイルの更新を監視して、指定のファイルが更新された場合に、 更新分のうち、最後尾からN行を表示すること 「2.」更新分なので前回表示続きからという点が注意事項だと思います。 # 「-f」 を実装しなさいというあたり、 以前あったファイルの更新を監視するお題の 派生と考えられるのかな?
Squeak Smalltalk で、それっぽい動きをするものを。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | | filename file numOfLines isFollowMode lineCount savedPosition |
filename := 'in.txt'.
numOfLines := 10.
isFollowMode := false.
file := FileStream fileNamed: filename.
file setToEnd.
file binary.
lineCount := 0.
[file position > 0 and: [lineCount < numOfLines]]
whileTrue: [file back = 13 ifTrue: [lineCount := lineCount + 1]].
file ascii.
World findATranscript: nil.
Transcript cr; show: file upToEnd.
savedPosition := file position.
file close.
isFollowMode ifTrue: [
[Sensor keyboardPressed] whileFalse: [
(Delay forSeconds: 5) wait.
file reopen; position: savedPosition.
Transcript show: file upToEnd.
savedPosition := file position.
file close]]
|
まじめに書いたらCのようになってしまいました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | read.file <- function (infile, offset=0){
con <- file(infile)
open(con)
seek(con, offset)
lines <- readLines(con)
cur.offset <- seek(con)
close(con)
list(l=lines, o=cur.offset)
}
my.tail <- function(infile, n=10, follow=FALSE){
lines <- read.file(infile)
writeLines(tail(lines$l, n))
if(follow){
size <- file.info(infile)$size
repeat{
if(size < (s <- file.info(infile)$size)){
lines <- read.file(infile, lines$o)
writeLines(lines$l)
size <- s
}
Sys.sleep(1)
}
}
}
|
対象がファイルの場合には、予めファイル末尾を基準にシークします。これによってファイルサイズに依存しない処理時間を実現しています。
起動方法は
java Tail [-f] [-<num>] [<ファイル名>]
-f: ファイル末尾まで読み込んでも終了せず、ファイルが成長した部分をポーリングで表示する(ファイル名を与えない場合は無視する)。
num: ファイル末尾からnum行を表示する(デフォルトは10行)。
ファイル名: 表示するファイル名。省略した場合は標準入力。
です。
起動方法は
java Tail [-f] [-<num>] [<ファイル名>]
-f: ファイル末尾まで読み込んでも終了せず、ファイルが成長した部分をポーリングで表示する(ファイル名を与えない場合は無視する)。
num: ファイル末尾からnum行を表示する(デフォルトは10行)。
ファイル名: 表示するファイル名。省略した場合は標準入力。
です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 | import java.io.*;
public class Tail {
private static final int MAX_LINE_LENGTH = 1000;
private static final long SLEEP_TIME = 500; // 500 m sec
private static int numOfLine = 10;
private static boolean fOption = false;
private static String fileName = null;
private static InputStream targetStream = System.in;
public static void main(String[] args) throws Exception {
for (String s: args) {
if ("-help".equals(s)) {
System.err.println("Usage> java Tail [-f] [-<num>] [filename]");
return;
}
else if ("-f".equals(s))
fOption = true;
else if (s.startsWith("-"))
numOfLine = -Integer.parseInt(s);
else
fileName = s;
}
if (fileName != null) {
File target = new File(fileName);
long len = target.length();
targetStream = new FileInputStream(target);
len -= MAX_LINE_LENGTH * numOfLine;
if (len > 0)
targetStream.skip(len);
}
BufferedReader br = new BufferedReader(new InputStreamReader(targetStream));
String line;
String[] lines = new String[numOfLine];
int ip = 0;
while ((line = br.readLine()) != null) {
lines[ip] = line;
if (++ip >= numOfLine)
ip = 0;
}
int i = ip;
do {
if (lines[i] != null)
System.out.println(lines[i]);
if (++i >= numOfLine)
i = 0;
} while (i != ip);
if (fOption && fileName != null) {
while (true) {
Thread.sleep(SLEEP_TIME);
line = br.readLine();
if (line != null)
System.out.println(line);
}
}
}
}
|
末尾から読み込む様にしてみました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 | #! /usr/bin/perl
use strict;
package Tail;
$Tail::BUF_SIZE = 4096;
sub new {
my ($pkg,$lines,$follow,$file) = @_;
my $self = bless({},$pkg);
$self->file($file);
$self->lines(defined($lines) ? $lines : 10);
$self->follow(defined($follow) ? $follow : 0);
$self;
}
sub lines {
my ($self,$lines) = @_;
$self->{'lines'} = $lines if (defined($lines));
$self->{'lines'};
}
sub follow {
my ($self,$follow) = @_;
$self->{'follow'} = $follow if (defined($follow));
$self->{'follow'};
}
sub file {
my ($self,$file) = @_;
$self->{'file'} = $file if (defined($file));
$self->{'file'};
}
sub quit {
my ($self,$quit) = @_;
$self->{'quit'} = $quit if (defined($quit));
$self->{'quit'};
}
sub _fh {
my ($self,$fh) = @_;
$self->{'_fh'} = $fh if (defined($fh));
$self->{'_fh'};
}
sub initialize {
my ($self) = @_;
my $fh;
open ($fh,"<".$self->file()) || die "cannot open file.";
binmode($fh);
$self->_fh($fh);
$self->quit(0);
$|=1;
$self;
}
sub _tailn {
my ($self) = @_;
my $lines = $self->lines();
my $size = -s $self->file();
my $fh = $self->_fh();
my $pos = $size;
my $head;
my $tail = '';
while ($pos > 0) {
my $buf = '';
my $ret;
if ($pos <= $Tail::BUF_SIZE) {
seek($fh,0,0);
$ret = read($fh,$buf,$pos);
} else {
seek($fh,$pos-$Tail::BUF_SIZE,0);
$ret = read($fh,$buf,$Tail::BUF_SIZE);
}
die "cannot read file." unless (defined($ret));
$pos -= $ret;
$tail = $buf.$tail;
last if (scalar(split(/(?:\0x0d\x0a|\x0d|\x0d)/,$tail)) > $lines);
}
$pos = length($tail) - 1;
while ($pos >= 0) {
my $c = substr($tail,$pos,1);
if ($c eq "\x0d" || $c eq "\x0a") {
last if (--$lines <= 0);
$pos-- if ($c eq "\x0a" && $pos > 0 && substr($tail,$pos-1,1) eq "\x0d");
}
$pos--;
};
$tail = substr($tail,$pos+1,length($tail)-$pos-1);
$tail =~ s/(\x0d\x0a|\x0d|\x0a)/\n/g;
print $tail;
seek($fh,$size,0);
$self;
}
sub _tailf {
my ($self) = @_;
my $fh = $self->_fh();
until ($self->quit()) {
my $buf = '';
seek($fh,tell($fh),0);
until (eof($fh)) {
die "cannot read file." unless (defined(read($fh,$buf,$Tail::BUF_SIZE)));
$buf =~ s/(\x0d\x0a|\x0d|\x0a)/\n/g;
print $buf;
}
sleep(1);
}
$self;
}
sub tail {
my ($self) = @_;
$self->_tailn();
$self->_tailf() if ($self->follow());
}
sub finalize {
my ($self) = @_;
close($self->_fh());
}
package main;
use Getopt::Std;
my $opt = {};
my $tail;
eval {
getopts('n:f',$opt);
$tail = new Tail($opt->{'n'},$opt->{'f'},shift(@ARGV))->initialize();
$SIG{INT} = sub { $tail->quit(1); };
$tail->tail();
$tail->finalize();
};
print "error: $@" if ($@);
1;
|
Rubyもまだの様なので書いてみました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 | #! /usr/bin/ruby
class Tail
BUF_SIZE = 4096
attr_accessor :lines, :follow, :file, :quit, :_f
private :_f
def initialize(lines,follow,file)
self.lines = lines ? lines.to_i : 10
self.follow = follow ? follow : false
self.file = file
quit = false
self.send(:_f=,File.open(file,'r').binmode)
self
end
def _tailn
lines = self.lines
size = _f.lstat.size
pos = size
tail = ''
tails = []
until(pos <= 0) do
buf = ''
if pos < BUF_SIZE
_f.pos = 0
buf = _f.read(pos)
else
_f.pos = pos - BUF_SIZE
buf = _f.read(BUF_SIZE)
end
raise IOError.new('cannot read file.') unless(buf)
pos -= buf.size
tail = buf + tail
break if tail.split(/(?:\x0d\x0a|\x0d|\x0a)/).size > lines
end
tail = tail.gsub(/(\x0d\x0a|\x0d|\x0a)/,"\n")
tails = tail.split(/\n/)
tails.push("") if tail =~ /\n\z/
print tails.reverse.first(lines).reverse.join("\n")
STDOUT.flush
_f.pos = size
self
end
def _tailf
until(quit) do
buf = ''
buf = _f.read
raise IOException.new('cannot read file.') unless(buf)
if (buf.size > 0)
buf = buf.gsub(/(\x0d\x0a|\x0d|\x0a)/,"\n")
print buf
STDOUT.flush
end
sleep(1)
end
self
end
def tail
_tailn
_tailf if follow
end
def finalize
_f.close
end
end
require "optparse"
tail = nil
conf = Hash.new
opts = OptionParser.new
opts.on("-n MANDATORY") { |v| conf[:n] = v }
opts.on("-f") { |v| conf[:f] = v }
opts.parse!
if ARGV.size == 0
puts "usage: ruby #{$0} [-n lines] [-f] filename"
else
begin
tail = Tail.new(conf[:n],conf[:f],ARGV.shift)
Signal.trap(:INT) { tail.quit = true }
tail.tail
tail.finalize
rescue => e
puts e.message
end
end
|
C#でやってみました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 | using System;
using System.Text;
using System.Threading;
using System.IO;
class Tail
{
private const int SLEEP_TIME = 1000;
private static bool fOption = false;
private static int outputLines = 10;
private static string filename = string.Empty;
private static string usage = "Usage: Tail.exe [-n number] [-f] filename";
static void Main(string[] args)
{
try {
for (int i = 0; i < args.Length; i++) {
if (args[i][0] == '-') {
switch (args[i][1]) {
case 'n':
outputLines = int.Parse(args[++i]);
break;
case 'f':
fOption = true;
break;
default:
Console.Error.WriteLine("Invalid Option: {0}", args[i]);
Console.Error.WriteLine(usage);
return;
}
}
else
filename = args[i];
}
string buffer = string.Empty;
int allLines = 0;
string line = string.Empty;
using (FileStream fs = new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) {
using (StreamReader sr = new StreamReader(fs, Encoding.Default)) {
while (sr.ReadLine() != null)
allLines++;
fs.Seek(0, SeekOrigin.Begin);
if (allLines <= outputLines)
buffer = sr.ReadToEnd();
else {
for (int i = 0; i < allLines; i++) {
line = sr.ReadLine();
if (i > allLines - outputLines - 1)
buffer += line + "\n";
}
}
Console.WriteLine(buffer);
while (fOption) {
Thread.Sleep(SLEEP_TIME);
while ((line = sr.ReadLine()) != null)
Console.WriteLine(line);
}
}
}
}
catch (Exception ex) {
Console.Error.WriteLine(ex.Message.ToString());
Console.Error.WriteLine(usage);
}
}
}
|
手抜きで行末記号を"\n"で決め打ちです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | import sys
import time
import StringIO
def gettail(fp, n):
start = fp.tell()
fp.seek(0, 2)
end = fp.tell()
s = ''
while len(s) < end - start and s.count('\n') <= n:
a = min(160 * n, fp.tell() - start)
fp.seek(-a, 1)
s = fp.read(a) + s
return StringIO.StringIO(s).readlines()[-n:]
def getnlines(fp, start, n):
fp.seek(0, 2)
pos = fp.tell()
fp.seek(start)
for s in gettail(fp, n):
sys.stdout.write(s)
sys.stdout.flush()
return pos
n = 10
f = False
fp = None
sys.argv.pop(0)
while sys.argv:
s = sys.argv.pop(0)
if s == '-n':
n = int(sys.argv.pop(0))
elif s == '-f':
f = True
elif not s.startswith('-'):
fp = file(s, 'rb')
pos = getnlines(fp, 0, n)
while f:
time.sleep(1)
pos = getnlines(fp, pos, n)
|
バッチです。カバレッジ稼ぎのようでちょっと恥ずかしいのですが、あまり処理を複雑に
したくなかったので、標準入力からの入力や-fオプションは実装しませんでした。
e.g.
C:\>tail 3 tail.bat
set /a i-=%1
endlocal & more +%i% %p%
goto :EOF
以下の参考サイトのものはもっと複雑で頭が下がります。
see: 【続々々】tailコマンドの作成
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | :: tail.bat
@echo off
setlocal
set i=0
set p=%~fs2
echo %1|findstr /r "[^0-9]" >NUL 2>&1
if %ERRORLEVEL% equ 0 (call :usage & exit /b 1)
if "%p%" equ "" (call :usage & exit /b 1)
if not exist %p% (call :usage & exit /b 1)
for /f %%i in ('find /c /v "" ^< %p%') do set i=%%i
set /a i-=%1
endlocal & more +%i% %p%
goto :EOF
:usage
setlocal
echo %~n0 [LINE] [FILE]
endlocal
goto :EOF
|
オプションの処理にかなり場所をとられてしまっていますが...。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 | import java.io.FileNotFoundException
import java.io.File
import java.io.FileInputStream
import scala.collection.mutable.HashMap
class InvalidArgumentException extends Exception {}
class GetOpt(f:String) {
var _params:List[Tuple2[String,Boolean]] = null
var _opt:HashMap[Any,Any] = null
var _rest:Array[String] = null
_params = f.toList.foldLeft(List[Tuple2[String,Boolean]]()) { (l,c) =>
c match {
case ':' => l match {
case List() => throw new InvalidArgumentException
case h::r => (h._1,true)::r
}
case k => (k.toString,false)::l
}
}.reverse
def parse(a:Array[String]):GetOpt = {
def _parse(o:HashMap[Any,Any],l:List[String]):Tuple2[HashMap[Any,Any],List[String]] = l match {
case List() => (o,List())
case h::r => h.toList match {
case '-'::k => {
_params.find { e => e._1 == new String(k.toArray) } match {
case Some(Tuple2(_,true)) => {
if (r.size == 0) throw new InvalidArgumentException
o.update(new String(k.toArray),r.head)
_parse(o,r.tail)
}
case Some(Tuple2(_,false)) => {
o.update(new String(k.toArray),true)
_parse(o,r)
}
case _ => throw new InvalidArgumentException
}
}
case _ => (o,l)
}
}
_parse(new HashMap[Any,Any],a.toList) match {
case Tuple2(o,r) => { _opt = o; _rest = r.toArray }
}
this
}
def getopt(k:String):Any = _opt.isDefinedAt(k) match {
case true => _opt.apply(k)
case _ => false
}
def rest:Array[String] = _rest
}
class CTail(n:Int,f:Boolean,i:File) {
val s:FileInputStream = new FileInputStream(i)
def this(o:GetOpt) =this(o.getopt("n") match { case Some(false) => 10; case n => n.asInstanceOf[String].toInt },o.getopt("f").asInstanceOf[Boolean],new File(o.rest.apply(0)))
def tailn:Unit = {
val BUF_SIZE:Int = 4096
val e:Long = i.length
def _tailn(p:Long,n:Int,b:Array[Byte]):Array[Byte] = {
var t:Array[Byte] = null
var l:Int = n
(p > BUF_SIZE) match {
case true => {
t = new Array[Byte](BUF_SIZE)
s.getChannel.position(p - BUF_SIZE)
s.read(t,0,BUF_SIZE)
}
case _ => {
t = new Array[Byte](p.toInt)
s.read(t,0,p.toInt)
}
}
t = t.reverse.takeWhile { c => if (c == '\n') l = l - 1; (l > 0) }.reverse
((p <= BUF_SIZE) || (l == 0)) match {
case true => t ++ b
case _ => _tailn(p - BUF_SIZE, l, t ++ b)
}
}
print(new String(_tailn(e, n, new Array[Byte](0))))
s.getChannel.position(e)
}
def tailf:Unit = {
def _tailf(b:List[Byte]):List[Byte] = {
val c:Int = s.read
(c >= 0) match {
case true => _tailf(b + c.toByte)
case _ => b
}
}
print(new String(_tailf(List[Byte]()).toArray))
Thread.sleep(100)
tailf
}
def tail:CTail = {
tailn
if (f) tailf
this
}
def close:Unit = s.close
}
object Tail {
def main(args:Array[String]):Unit = {
try {
val o:GetOpt = new GetOpt("n:f").parse(args)
(new CTail(o)).tail.close
} catch {
case e:InvalidArgumentException => println("usage: scala Tail [-n <number of lines>] [-f] file")
case e:FileNotFoundException => print("file not found.")
case e:Exception => e.printStackTrace
}
}
}
|
とりあえず実装してみたというレベル Data.Sequenceを使ってみた 効率については考えてない orz
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 | module Main where
import Control.Concurrent
import Data.Foldable
import Data.Sequence
import Prelude hiding (foldl,concat,mapM_)
import System.Console.GetOpt
import System.Environment
import System.IO
data Options = Options
{ optFollowed :: Bool
, optNumber :: Int
} deriving Show
defaultOptions = Options
{ optFollowed = False
, optNumber = 10
}
options :: [OptDescr (Options -> Options)]
options
= [ Option ['f'] [] (NoArg (\ opts -> opts { optFollowed = True }))
"followed by addition to the file"
, Option ['n'] [] (ReqArg (\ d opts -> opts { optNumber = read d }) "NUMBER")
"display last NUMBER lines"]
compileOpts :: [String] -> IO (Options, [String])
compileOpts argv
= case getOpt Permute options argv of
(o,n,[] ) -> return (foldl (flip id) defaultOptions o, n)
(_,_,errs) -> ioError (userError (concat errs ++ usageInfo header options))
where header = "Usage : tail [OPTION..] file"
main :: IO ()
main = getArgs >>= compileOpts >>= tail'
tail' :: (Options, [String]) -> IO ()
tail' (o,fs) = openFile (head fs) ReadMode
>>= loop empty (optNumber o) 0 (optFollowed o)
loop :: Seq String -> Int -> Int -> Bool -> Handle -> IO ()
loop q n i f h
= do { eof <- hIsEOF h
; if eof then if not f then printQ q
else printQ q >> lineCat h
else do { l <- hGetLine h
; if n > i then loop (q |> l) n (i+1) f h
else case viewl q of
_ :< qs -> loop (qs |> l) n n f h
}
}
printQ :: Seq String -> IO ()
printQ = mapM_ putStrLn
lineCat :: Handle -> IO ()
lineCat h = do { eof <- hIsEOF h
; if eof then threadDelay (10^4) >> lineCat h
else hGetLine h >>= putStrLn >> lineCat h
}
|






takeru #6818() Rating-9/11=-0.82
'tail'を実装してください。
巨大なファイルでも効率的に動作するようにしてください。
最低限必要な機能は、
です。
[ reply ]