はじめに
Goを勉強したいので、手始めにlsコマンドを一部実装してみようかなと思います。今回は、lsでファイル名の一覧が表示されること、またオプションは、-lでファイルの詳細一覧が出力されることを目標として設定します。
実装
今回のリポジトリですmm
では最初に、lsを実装していきます。ポイントは、osライブラリが提供しているos.Getwd()でカレントディレクトリのパスを取得し、os.ReadDir()でディレクトリ内のファイルを全て返して、ファイル名を出力しているようにしている点です。
package main
import (
"fmt"
"os"
)
func main() {
dir, err := os.Getwd()
if err != nil {
panic(err)
}
files, _ := os.ReadDir(dir)
listFiles(files)
}
func listFiles(files []fs.DirEntry) {
for _, f := range files {
fmt.Printf("%s ", f.Name())
}
}
では、スクリプトを実行してみます。
$ go run main.go
README.md go.mod main.go
ここまでは、上手くいっていそうです。つづけて、オプションの-lを実装します。
ポイントは、flagライブラリが提供するflag.Bool()でコマンドライン引数を受け取るようにし、fsライブラリのfs.DirEntryといったインターフェースに属する関数を活用し、ファイルの各種情報を出力している点です。
package main
import (
"flag"
"fmt"
"io/fs"
"os"
"time"
)
func main() {
+ isListed := flag.Bool("l", false, "List files")
+ flag.Parse()
dir, err := os.Getwd()
if err != nil {
panic(err)
}
files, _ := os.ReadDir(dir)
+ if *isListed {
+ listFilesWithDetails(files)
+ return
+ }
listFiles(files)
}
func listFiles(files []fs.DirEntry) {
for _, f := range files {
fmt.Printf("%s ", f.Name())
}
}
+func listFilesWithDetails(files []fs.DirEntry) {
+ for _, f := range files {
+ info, err := f.Info()
+ if err != nil {
+ panic(err)
+ }
+ fmt.Printf("%s %s %d %s\n", info.Mode(), formatDate(info.ModTime()), info.Size(), f.Name())
+ }
+}
+func formatDate(t time.Time) string {
+ return t.Format("Jan 02 15:04")
+}
では、スクリプトを実行してみます。
$ go run main.go -l
-rw-r--r-- May 16 22:51 8 README.md
-rw-r--r-- May 16 18:18 30 go.mod
-rw-r--r-- May 18 15:42 719 main.go
こちらも、期待通りの結果が返ってきました。なお、今回は本来の-lにあるような、ハードリンクの数や所有者・所有グループまでは実装しませんでした。特に後者の理由は、Goの標準ライブラリのみではシンプルには書けないようだからです。Goの限界なのでしょう。
おわりに
こうして実装に取り組むと学びが色々とあると感じますし、やはり面白いです。
アドバイスや指摘があれば、頂けると幸いです。次は、grepコマンドの実装に挑戦してみようかな~