課題17: ファイル処理ユーティリティ
課題概要
この課題では、Goの標準ライブラリを活用して、実用的なファイル処理ユーティリティを作成します。io、os、filepath、strings、time、encoding/json などのパッケージを組み合わせて使います。
マンダトリー要件(80点)
要件1: ファイル統計ツール(30点)
ディレクトリ内のファイル情報を収集し、統計を表示するツールを作成してください。
実装ファイル: filestats/filestats.go
package main
import (
"fmt"
"os"
"path/filepath"
)
type FileStats struct {
TotalFiles int
TotalDirs int
TotalSize int64
FilesByExt map[string]int
LargestFile string
LargestFileSize int64
}
// AnalyzeDirectory はディレクトリを再帰的に解析します
func AnalyzeDirectory(rootPath string) (*FileStats, error) {
stats := &FileStats{
FilesByExt: make(map[string]int),
}
// TODO: filepath.Walk を使ってディレクトリを走査
// - ファイル数とディレクトリ数をカウント
// - 合計サイズを計算
// - 拡張子ごとのファイル数を記録
// - 最大ファイルを追跡
return stats, nil
}
// PrintStats は統計情報を表示します
func (s *FileStats) PrintStats() {
// TODO: 統計情報を整形して表示
fmt.Printf("Total Files: %d\n", s.TotalFiles)
fmt.Printf("Total Directories: %d\n", s.TotalDirs)
fmt.Printf("Total Size: %d bytes (%.2f MB)\n", s.TotalSize, float64(s.TotalSize)/(1024*1024))
fmt.Println("\nFiles by extension:")
for ext, count := range s.FilesByExt {
if ext == "" {
ext = "(no extension)"
}
fmt.Printf(" %s: %d\n", ext, count)
}
if s.LargestFile != "" {
fmt.Printf("\nLargest file: %s (%d bytes)\n", s.LargestFile, s.LargestFileSize)
}
}
func main() {
if len(os.Args) < 2 {
fmt.Println("Usage: filestats <directory>")
os.Exit(1)
}
dir := os.Args[1]
stats, err := AnalyzeDirectory(dir)
if err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
stats.PrintStats()
}
要件2: CSV変換ツール(25点)
CSVファイルをJSON形式に変換するツールを作成してください。
実装ファイル: csvtojson/converter.go
package main
import (
"encoding/csv"
"encoding/json"
"fmt"
"io"
"os"
)
// ConvertCSVToJSON はCSVファイルをJSONに変換します
func ConvertCSVToJSON(csvFile, jsonFile string) error {
// TODO: 実装
// 1. CSVファイルを開く
// 2. ヘッダー行を読み込む
// 3. データ行を読み込み、map[string]stringのスライスに変換
// 4. JSONファイルに書き込む
// CSVファイルを開く
file, err := os.Open(csvFile)
if err != nil {
return err
}
defer file.Close()
reader := csv.NewReader(file)
// ヘッダーを読み込み
headers, err := reader.Read()
if err != nil {
return err
}
// データを読み込み
var records []map[string]string
for {
row, err := reader.Read()
if err == io.EOF {
break
}
if err != nil {
return err
}
// TODO: 行をマップに変換
// record := make(map[string]string)
// for i, header := range headers {
// record[header] = row[i]
// }
// records = append(records, record)
}
// TODO: JSONファイルに書き込み
return nil
}
func main() {
if len(os.Args) < 3 {
fmt.Println("Usage: csvtojson <input.csv> <output.json>")
os.Exit(1)
}
csvFile := os.Args[1]
jsonFile := os.Args[2]
err := ConvertCSVToJSON(csvFile, jsonFile)
if err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
fmt.Printf("Converted %s to %s\n", csvFile, jsonFile)
}
テスト用CSVファイル: test.csv
name,age,email
Alice,30,alice@example.com
Bob,25,bob@example.com
Charlie,35,charlie@example.com
要件3: テキスト処理ツール(25点)
テキストファイルに対して様々な操作を行うツールを作成してください。
実装ファイル: textutil/textutil.go
package main
import (
"bufio"
"fmt"
"os"
"strings"
)
type TextStats struct {
Lines int
Words int
Characters int
UniqueWords map[string]int
}
// AnalyzeText はテキストファイルを解析します
func AnalyzeText(filename string) (*TextStats, error) {
file, err := os.Open(filename)
if err != nil {
return nil, err
}
defer file.Close()
stats := &TextStats{
UniqueWords: make(map[string]int),
}
scanner := bufio.NewScanner(file)
for scanner.Scan() {
line := scanner.Text()
stats.Lines++
// TODO: 文字数をカウント
// TODO: 単語を分割してカウント
// TODO: ユニークな単語を記録
words := strings.Fields(line)
stats.Words += len(words)
stats.Characters += len(line)
for _, word := range words {
word = strings.ToLower(strings.Trim(word, ".,!?;:"))
stats.UniqueWords[word]++
}
}
return stats, scanner.Err()
}
// PrintStats は統計情報を表示します
func (s *TextStats) PrintStats() {
fmt.Printf("Lines: %d\n", s.Lines)
fmt.Printf("Words: %d\n", s.Words)
fmt.Printf("Characters: %d\n", s.Characters)
fmt.Printf("Unique words: %d\n", len(s.UniqueWords))
// 上位10単語を表示
// TODO: 頻度順にソートして表示
}
// SearchAndReplace はテキスト内の文字列を置換します
func SearchAndReplace(inputFile, outputFile, search, replace string) error {
// TODO: 実装
// 1. 入力ファイルを読み込み
// 2. 文字列を置換
// 3. 出力ファイルに書き込み
data, err := os.ReadFile(inputFile)
if err != nil {
return err
}
// TODO: 置換処理
// content := string(data)
// newContent := strings.ReplaceAll(content, search, replace)
// TODO: 書き込み
return nil
}
func main() {
if len(os.Args) < 2 {
fmt.Println("Usage:")
fmt.Println(" textutil analyze <file>")
fmt.Println(" textutil replace <input> <output> <search> <replace>")
os.Exit(1)
}
command := os.Args[1]
switch command {
case "analyze":
if len(os.Args) < 3 {
fmt.Println("Usage: textutil analyze <file>")
os.Exit(1)
}
stats, err := AnalyzeText(os.Args[2])
if err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
stats.PrintStats()
case "replace":
if len(os.Args) < 6 {
fmt.Println("Usage: textutil replace <input> <output> <search> <replace>")
os.Exit(1)
}
err := SearchAndReplace(os.Args[2], os.Args[3], os.Args[4], os.Args[5])
if err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
fmt.Println("Replacement complete")
default:
fmt.Println("Unknown command:", command)
os.Exit(1)
}
}
期待される出力
filestats の実行例
$ go run filestats.go /path/to/directory
Total Files: 42
Total Directories: 8
Total Size: 1048576 bytes (1.00 MB)
Files by extension:
.go: 15
.txt: 10
.json: 5
(no extension): 12
Largest file: /path/to/directory/large.txt (524288 bytes)
csvtojson の実行例
$ go run converter.go test.csv output.json
Converted test.csv to output.json
$ cat output.json
[
{
"age": "30",
"email": "alice@example.com",
"name": "Alice"
},
{
"age": "25",
"email": "bob@example.com",
"name": "Bob"
}
]
textutil の実行例
$ go run textutil.go analyze test.txt
Lines: 100
Words: 500
Characters: 3000
Unique words: 200
Top 10 words:
the: 45
and: 30
to: 25
...
ボーナス課題(20点)
ボーナス1: ログ解析ツール(10点)
構造化されたログファイルを解析し、統計とフィルタリング機能を提供するツールを作成してください。
実装ファイル: loganalyzer/analyzer.go
package main
import (
"bufio"
"encoding/json"
"fmt"
"os"
"strings"
"time"
)
type LogEntry struct {
Timestamp time.Time `json:"timestamp"`
Level string `json:"level"`
Message string `json:"message"`
}
// ParseLogFile はログファイルを解析します
func ParseLogFile(filename string) ([]LogEntry, error) {
// TODO: 実装
// フォーマット: "2024-01-15 10:30:00 [INFO] Message"
}
// FilterByLevel は指定されたレベルのログをフィルタします
func FilterByLevel(entries []LogEntry, level string) []LogEntry {
// TODO: 実装
}
// FilterByTimeRange は時間範囲でフィルタします
func FilterByTimeRange(entries []LogEntry, start, end time.Time) []LogEntry {
// TODO: 実装
}
// ExportToJSON はログをJSON形式でエクスポートします
func ExportToJSON(entries []LogEntry, filename string) error {
// TODO: 実装
}
// PrintStatistics は統計情報を表示します
func PrintStatistics(entries []LogEntry) {
stats := make(map[string]int)
for _, entry := range entries {
stats[entry.Level]++
}
fmt.Println("Log Statistics:")
for level, count := range stats {
fmt.Printf(" %s: %d\n", level, count)
}
}
func main() {
// TODO: コマンドライン引数処理
// - parse <logfile>: ログファイルを解析
// - filter <logfile> <level>: レベルでフィルタ
// - export <logfile> <output.json>: JSON形式でエクスポート
}
ボーナス2: ファイル同期ツール(5点)
2つのディレクトリを比較し、差分を表示するツールを作成してください。
実装ファイル: filesync/sync.go
package main
import (
"crypto/md5"
"fmt"
"io"
"os"
"path/filepath"
)
type FileDiff struct {
OnlyInSource []string
OnlyInDest []string
Different []string
}
// CompareDirectories は2つのディレクトリを比較します
func CompareDirectories(source, dest string) (*FileDiff, error) {
// TODO: 実装
// 1. 両方のディレクトリのファイルリストを取得
// 2. 存在チェック
// 3. MD5ハッシュで内容を比較
}
// CalculateMD5 はファイルのMD5ハッシュを計算します
func CalculateMD5(filename string) (string, error) {
// TODO: 実装
}
func main() {
// TODO: 実装
}
ボーナス3: 設定ファイル管理ツール(5点)
JSON設定ファイルを読み書きし、環境変数で上書きできるツールを作成してください。
実装ファイル: config/manager.go
package main
import (
"encoding/json"
"fmt"
"os"
"strings"
)
type Config struct {
Database struct {
Host string `json:"host"`
Port int `json:"port"`
Username string `json:"username"`
Password string `json:"password"`
} `json:"database"`
Server struct {
Port int `json:"port"`
Host string `json:"host"`
Timeout int `json:"timeout"`
} `json:"server"`
}
// LoadConfig は設定ファイルを読み込みます
func LoadConfig(filename string) (*Config, error) {
// TODO: 実装
// 1. JSONファイルを読み込み
// 2. 環境変数で上書き(例: DB_HOST -> Database.Host)
}
// SaveConfig は設定をファイルに保存します
func SaveConfig(config *Config, filename string) error {
// TODO: 実装
}
// PrintConfig は設定を表示します
func (c *Config) PrintConfig() {
// TODO: 実装
}
func main() {
// TODO: 実装
// - load <file>: 設定を読み込んで表示
// - set <file> <key> <value>: 値を設定
}
評価基準
| 項目 | 配点 | 詳細 |
|---|---|---|
| ファイル統計ツール | 30点 | ディレクトリ走査と統計計算が正確 |
| CSV変換ツール | 25点 | CSV解析とJSON出力が正しい |
| テキスト処理ツール | 25点 | テキスト解析と置換が動作する |
| **ボーナス1: ログ解析** | 10点 | ログのパース、フィルタリング、エクスポートが実装されている |
| **ボーナス2: ファイル同期** | 5点 | ディレクトリ比較とMD5ハッシュが正しく実装されている |
| **ボーナス3: 設定管理** | 5点 | JSON設定の読み書きと環境変数の統合が動作する |
提出方法
以下のディレクトリ構造で提出してください:
submission/
├── filestats/
│ └── filestats.go
├── csvtojson/
│ ├── converter.go
│ └── test.csv
├── textutil/
│ └── textutil.go
├── bonus/ # ボーナス課題(オプション)
│ ├── loganalyzer/
│ ├── filesync/
│ └── config/
└── README.md # 実行方法と使用例
ヒント
- filepath.Walk: ディレクトリを再帰的に走査
- csv.Reader: CSVファイルの読み込み
- json.MarshalIndent: 整形されたJSON出力
- bufio.Scanner: 行単位のテキスト読み込み
- strings.Fields: 空白文字で文字列を分割
- io Package
- os Package
- filepath Package
- encoding/json Package
- encoding/csv Package