-
Notifications
You must be signed in to change notification settings - Fork 0
/
main.go
200 lines (158 loc) · 4.22 KB
/
main.go
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
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
package main
import (
"bufio"
"errors"
"fmt"
"log"
"os"
"strconv"
"strings"
)
const (
defaultLines = 10
)
type config struct {
lines int
files []string
}
// 出力内容を出力する
func printLines(file *os.File) error {
b := bufio.NewReader(file)
for {
line, err := b.ReadString('\n')
if err != nil {
fmt.Print(line)
break
}
fmt.Print(line)
}
return nil
}
// 出力行数に応じた対象ファイルの出力開始地点を決定する
// ファイル末尾から1byteずつ読みこみ、改行コードが存在すれば出力行数をデクリメントし、
// 出力行数がゼロになった時点で出力開始地点とみなす
// 現状、1byteずつ読み込んでいるが、バッファリングなどの改善をしていきたい
func startPoint(lines int, file *os.File) error {
info, err := file.Stat()
if err != nil {
return err
}
size := info.Size() - 1
var offset int64
buf := make([]byte, 1)
// 対象ファイルの末尾に改行コードのみ存在した場合
// 既存のtailコマンドではその行を無視して、出力行としてカウントしていない挙動をしている。
// 改行コードが存在すれば出力行数をデクリメントする実装とした場合、既存のtailコマンドと比較すると
// 出力される行数が1行減ってしまうため、この for ループではファイル末尾に改行コードのみ存在した場合
// 出力行数に含めないようにしている
for {
b := make([]byte, 1)
offset, err = file.Seek(size, os.SEEK_SET)
if err != nil {
break
}
file.ReadAt(b, offset)
if b[0] == '\r' || b[0] == '\n' {
size--
} else {
break
}
}
// ファイルの出力開始位置を決定する
for lines > 0 {
offset, err = file.Seek(size, os.SEEK_SET)
if err != nil {
break
}
file.ReadAt(buf, offset)
if buf[0] == '\n' {
lines--
}
size--
}
// 出力行数より多い行数を持つファイルの場合、出力する際に余計な改行コードが入ってしまうため
// 意図的に出力開始位置をインクリメントしている。
// [TODO] 修正したいが、現状どうすればよいか理解できていない。
if offset != 0 {
size++
size++
file.Seek(size, os.SEEK_SET)
}
return nil
}
func tailFiles(lines int, name string, printHeaders bool) error {
if printHeaders {
fmt.Printf("==> %s <==\n", name)
}
f, err := os.Open(name)
if err != nil {
return err
}
defer f.Close()
if err = startPoint(lines, f); err != nil {
return err
}
if err = printLines(f); err != nil {
return err
}
return nil
}
// 設定内容に応じて動作を変更し、実際のtail処理をtailFiles関数に委譲する
func tail(c *config) error {
var l int
// 表示する行数の設定
// 0以下の値が引数として設定されている場合には、デフォルトの行数(10)を表示する
if c.lines > 0 {
l = c.lines
} else {
l = defaultLines
}
// 複数ファイルが指定されている場合に、ファイル名をヘッダ情報として出力する
var printHeaders bool
if len(c.files) > 1 {
printHeaders = true
}
for i, f := range c.files {
// 複数行ファイルを対象とした際に、tailコマンドの表示に合わせるため改行を出力する
if i >= 1 {
println()
}
if err := tailFiles(l, f, printHeaders); err != nil {
return err
}
}
return nil
}
// コマンドライン引数を解析する関数
func parseArgs(args []string) (*config, error) {
var config config
for _, v := range args {
// コマンドライン引数の解析
// -nオプションのみに対応し、現状それ以外の引数はtail対象のファイルとして扱う
if strings.HasPrefix(v, "-n") {
arg := strings.Split(v, "=")
if len(arg) < 2 {
return nil, errors.New("-n=出力行数 の形式で指定してください")
}
n, err := strconv.Atoi(arg[1])
if err != nil {
return nil, err
}
config.lines = n
} else {
config.files = append(config.files, v)
}
}
return &config, nil
}
func main() {
c, err := parseArgs(os.Args[1:])
if err != nil {
log.Fatal(err)
os.Exit(-1)
}
if err = tail(c); err != nil {
log.Fatal(err)
os.Exit(-1)
}
}