読み出しにはos.Open()
書き込みにはos.Create()
を使います。
テキストの読み書きはいろいろ方法があると思いますが、ここでは 読み出しにbufio.NewScanner()
を、書き込みにbufio.NewWriter()
を使います。
下記のようにWriteFile/ReadFileを使うと、一行で読み書きができます。ただし 引数はstringではなくbyte[]です。
// Write
ioutil.WriteFile(filename, []byte(s), os.ModePerm)
// Read
b, err := ioutil.ReadFile(filename)
s=string(b)
package main
import "fmt"
import "os"
import "bufio"
func main() {
// 書き込み
fpw, err := os.Create("test.txt")
if err != nil {
panic(err)
}
w := bufio.NewWriter(fpw)
fmt.Fprint(w, "Hello, golang!")
w.Flush()
fpw.Close()
// 読み出し
fp, err := os.Open("test.txt")
if err != nil {
panic(err)
}
scanner := bufio.NewScanner(fp)
for scanner.Scan() {
fmt.Println(scanner.Text())
}
fp.Close()
}
ファイルをオープンすると同じ内容ですが。
package main
import "fmt"
import "os"
import "bufio"
func main() {
fp, err := os.Open("test.txt")
if err != nil {
panic(err)
}
scanner := bufio.NewScanner(fp)
for scanner.Scan() {
fmt.Println(scanner.Text())
}
fp.Close()
}
[]byteバッファを作ってRead()に渡すと、バッファの長さ分だけ 読み込んでくれます。
package main
import "fmt"
import "os"
func main() {
fp, err := os.Open("test.txt")
if err != nil {
panic(err)
}
data := make([]byte, 10)
count, err := fp.Read(data)
if err != nil {
panic(err)
}
fmt.Printf("Read %d bytes: %s\n", count, data)
}
io/ioutilのioutil.ReadFile()を使います。
package main
import "fmt"
import "io/ioutil"
func main() {
data, err := ioutil.ReadFile("test.txt")
if err != nil {
panic(err)
}
fmt.Println(string(data))
}
bufioのScannerをつかってみます。この例では、foo.csvから行数と総エントリ数をカウントします。
package main
import "fmt"
import "bufio"
import "os"
import "strings"
func main() {
data, err := readAll("foo.csv")
if err != nil {
panic(err)
}
fmt.Println(string(data))
}
func readAll(filename string) (string, error) {
fp, err := os.Open(filename)
if err != nil {
return "", fmt.Errorf(filename + " can't be opened")
}
ans := ""
lines := 0
entries := 0
scanner := bufio.NewScanner(fp)
for scanner.Scan() {
entry := scanner.Text()
lines += 1
slice := strings.Split(entry, ",")
entries += len(slice)
}
fp.Close()
fmt.Printf("Read %d lines, %d entries\n", lines, entries)
return string(ans), nil
}
rubyのreadlinesのように、配列に読み込むような標準関数はありません。 改行でSplitすれば代替になりますが、でかいファイルの場合は気をつけないとですね。
package main
import "fmt"
import "ioutil"
import "strings"
func main() {
ans, err := readLines("foo.csv")
if err != nil {
panic(err)
}
fmt.Println(ans[1])
}
func readLines(filename string) ([]string, error) {
ans := make([]string, 10)
data, err := ioutil.ReadFile(filename)
if err != nil {
return ans, fmt.Errorf(filename + " can't be opened")
}
ans = strings.Split(string(data), "\n")
return ans, err
}
ioutil.TempFile(dir,prefix string)を使います。dirは一時ファイルを 作成するフォルダです。""を指定すると osごとのデフォルトのディレクトリが使われるようです。prefixに指定した文字が ファイル名に入ります。
下記の例は、test.txtの内容を、TempFileを利用して大文字に変換します。
package main
import "fmt"
import "bufio"
import "ioutil"
import "os"
func main() {
f, err := os.Open("test.txt")
if err != nil {
panic(err)
}
fw, err := ioutil.TempFile(".", "temp")
if err != nil {
panic(err)
}
scanner := bufio.NewScanner(f)
for scanner.Scan() {
entry := scanner.Text()
fw.Write([]byte(strings.ToUpper(entry)))
}
fw.Close()
f.Close()
os.Rename(fw.Name(), f.Name())
}
以下の様な固定長レコードを読みます。
■レコード形式
従業員番号 6桁|氏名 utf5文字|部課コード 4桁|入社年度 4桁
■レコード例
100001鈴木一郎太12342001
従業員番号: 100001
氏名: 鈴木一郎太
部課コード: 1234
入社年度: 2001
record という構造体を作り、その配列recordsに対して、 レコード追加・表示用のインターフェイス(Insert,List)と、 ソート用のインターフェイス(Len,Less,Swap)を準備。
package main
import "fmt"
import "bufio"
import "sort"
import "strconv"
import "os"
func main() {
var rs records
f, err := os.Open("fmtTxt.txt")
if err != nil {
panic(err)
}
scanner := bufio.NewScanner(f)
for scanner.Scan() {
entry := scanner.Text()
rs.InsertFromString(entry)
}
sort.Sort(rs)
rs.List()
}
type record struct {
empno int
name string
deptno int
year int
}
type records []record
func (rs *records) InsertFromString(s string) {
var newRecord record
r := []rune(s)
newRecord.empno, _ = strconv.Atoi(string(r[0:6]))
newRecord.name = string(r[6:11])
newRecord.deptno, _ = strconv.Atoi(string(r[11:15]))
newRecord.year, _ = strconv.Atoi(string(r[15:19]))
*rs = append(*rs, newRecord)
}
func (r records) List() {
for _, x := range r {
fmt.Printf("従業員番号:\t%d\n", x.empno)
fmt.Printf("氏名:\t\t%s\n", x.name)
fmt.Printf("部課コード:\t%d\n", x.deptno)
fmt.Printf("入社年度:\t%d\n", x.year)
fmt.Println(strings.Repeat("-", 20))
}
}
func (r records) Len() int {
return len(r)
}
func (r records) Swap(i, j int) {
r[i], r[j] = r[j], r[i]
}
func (r records) Less(i, j int) bool {
return r[i].year < r[j].year
}
os.Link()が簡単でしょう。
package main
import "fmt"
import "os"
func main() {
src := "test.txt"
dest := "test.bak"
_ = os.Link(src, dest)
}
標準入力を受け取りたいときはos.Stdinを使います。 rubyのように、ファイル名指定されたら勝手に標準入力扱いという器用なことは できないので、os.Argsでとれるコマンドライン引数リストを走査します。
echo hoge | go run ./main.go
go run ./main.go test.txt test.bak
のどちらでも機能します。
C言語と違って、os.Argsがどこでも機能する(mainの引数を変更する必要がない)のは 一瞬気持ち悪いですが、スッキリ書けますね。
package main
import "fmt"
import "bufio"
import "os"
func main() {
l := len(os.Args)
if l == 1 { // 引数なしの場合
scanner := bufio.NewScanner(os.Stdin)
for scanner.Scan() {
t := scanner.Text()
if t != "" {
fmt.Println(t)
} else {
break
}
}
} else { // ファイル名渡しの場合
for _, x := range os.Args[1:l] {
f, err := os.Open(x)
if err != nil {
panic(err)
}
scanner := bufio.NewScanner(f)
for scanner.Scan() {
fmt.Println(scanner.Text())
}
}
}
}
こちらを参考に。 os.Stat()でos.FileInfoが返ります。
存在有無は、os.Stat()のエラーをos.IsExist()に渡すと確認できます。 ちょっとくどいですね。
package main
import "fmt"
import "os"
func main() {
fmt.Println(isDir("/etc/passwd")) // => false
fmt.Println(isDir("/etc")) // => true
fmt.Println(isExist("/etc/passwd")) // => true
fmt.Println(isExist("/etc/password")) // => false
}
func isDir(filename string) bool {
fInfo, _ := os.Stat(filename)
return fInfo.IsDir()
}
func isExist(filename string) bool {
_, err := os.Stat(filename)
if err == nil {
return true
} else {
return os.IsExist(err)
}
}
むかしはFileStatでi-node番号とかもろもろ取得できたようですが、 少なくともGo1.4ではsyscallにいます。Atimespec->AtimになったのはGo1.5から?
package main
import "fmt"
import "syscall"
func main() {
var s syscall.Stat_t
syscall.Stat("/etc/passwd", &s)
fmt.Println(s.Dev)
fmt.Println(s.Ino)
fmt.Println(s.Mode)
fmt.Println(s.Nlink)
fmt.Println(s.Uid)
fmt.Println(s.Gid)
fmt.Println(s.Size)
fmt.Println(s.Blocks)
// fmt.Println(s.Atim.Unix()) // Go1.4ではエラー
// fmt.Println(s.Mtim.Unix()) // Go1.4ではエラー
fmt.Println(s.Atimespec.Unix())
fmt.Println(s.Mtimespec.Unix())
}
os.Chmod()を使います。FileMode構造体が返るのですが、String()メソッドが定義されているので、 Printlnすると"-rw-rw-rw-"のような表示が出ます。便利なのか?と思いましたが、 8進数を誤解しやすいからかしら。
package main
import "fmt"
import "os"
func main() {
filename := "test.txt"
s, _ := os.Stat(filename)
fmt.Println(s.Mode()) // -> "-rw-------"
os.Chmod(filename, 0666)
s, _ = os.Stat(filename)
fmt.Println(s.Mode()) // -> "-rw-rw-rw-"
}
os.Chown()があります。もちろんchwonできる権限がないとダメですが。
ちなみにOSXでユーザIDを調べるのは、
id
dscl . -read /users/$username uid
などの方法があります。
package main
import "fmt"
import "os"
func main() {
err := os.Chown("test.txt", 502, 20) //uid=502,gid=20
if err != nil {
fmt.Println("chown not permitted")
}
}
下記の例は、test.txtの 最終アクセス日を2001-5-22 23:59:59(JST)、 最終更新日を2001-5-1 00:00:00(JST)に変更するものです。
package main
import "fmt"
import "syscall"
import "time"
func main() {
// 変更
atime := time.Date(2001, 5, 22, 23, 59, 59, 0, time.Local)
mtime := time.Date(2001, 5, 1, 00, 00, 00, 0, time.Local)
os.Chtimes("test.txt", atime, mtime)
// 確認
var s syscall.Stat_t
syscall.Stat("test.txt", &s)
sec, nsec := s.Atimespec.Unix() // Go1.5以降ではAtimespec -> Atim
fmt.Println(time.Unix(sec, nsec)) // => "2001-05-22 23:59:59 +0900 JST"
sec, nsec = s.Mtimespec.Unix() // Go1.5以降ではAtimespec -> Atim
fmt.Println(time.Unix(sec, nsec)) // => "2001-05-01 00:00:00 +0900 JST"
}
カレントを基準に相対パスを絶対パスに変換するにはpath/filepathのAbs()を使います。
指定したベースパスを基準に相対パスを絶対パスに変換するにはJoin()を使います。
絶対パスを、ベースパスを基準に相対パスに変換するにはRel()を使います。
package main
import "fmt"
import "path/filepath"
func main() {
apath, _ := filepath.Abs("./test.txt")
fmt.Println(apath)
apath = filepath.Join("/etc", "passwd")
fmt.Println(apath) // => "/etc/passwd"
rpath, _ := filepath.Rel("/etc", "/etc/passwd")
fmt.Println(rpath) // -> "passwd"
}
path/filepath のDir()を使います。 Windowsのパスはうまく展開されませんけど、OSXで実行したからかしら。
package main
import "fmt"
import "path/filepath"
func main() {
d := filepath.Dir("/hoge/piyo")
fmt.Println(d) // =>"/hoge"
d = filepath.Dir("/hoge/piyo/")
fmt.Println(d) // =>"/hoge/piyo"
d = filepath.Dir("c:\\hoge\\piyo")
fmt.Println(d) // =>"." ??
}
path/filepath のBase()でファイル名を分離できます。 拡張子を取り出すのはExt()ですが、拡張子を取り除いたbasenameを 取る方法は簡単にはなさそうですので、正規表現で拡張子を取り除いてからBase()を実行しました。
package main
import "fmt"
import "path/filepath"
import "regexp"
func main() {
b := filepath.Base("/hoge/piyo")
fmt.Println(b) // => "piyo"
e := filepath.Ext("/hoge/piyo.c")
fmt.Println(e) // => ".c"
rep := regexp.MustCompile(`.c$`)
e = filepath.Base(rep.ReplaceAllString("/hoge/piyo.c", ""))
fmt.Println(e) // => "piyo"
}
path/filepath のSplit()でディレクトリ名とファイル名を分離できます。
package main
import "fmt"
import "path/filepath"
func main() {
d, f := filepath.Split("/hoge/piyo")
fmt.Println(d) // => "/hoge/"
fmt.Println(f) // => "piyo"
}
path/filepath のExt()で拡張子を取り出せます。
package main
import "fmt"
import "path/filepath"
func main() {
e := filepath.Ext("/hoge/piyo.c")
fmt.Println(e) // => ".c"
}