『ふつうのLinuxプログラミング』サンプルプログラムをGoで書いてみる② #golang

前回はLinuxのコマンドライン引数の扱いについて、CのコードをGoで書いてみながら学びました。
[『ふつうのLinuxプログラミング』サンプルプログラムをGoで書いてみる①](https://qiita.com/mom0tomo/items/bd70d9840d83e26d4549)

今回はストリーム、ファイルを操作するプログラムをGoで書いてみます。

## ストリームの定義
[ふつうのLinuxプログラミング(第二版)](https://www.amazon.co.jp/%E3%81%B5%E3%81%A4%E3%81%86%E3%81%AELinux%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0-%E7%AC%AC2%E7%89%88-Linux%E3%81%AE%E4%BB%95%E7%B5%84%E3%81%BF%E3%81%8B%E3%82%89%E5%AD%A6%E3%81%B9%E3%82%8Bgcc%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0%E3%81%AE%E7%8E%8B%E9%81%93-%E9%9D%92%E6%9C%A8-%E5%B3%B0%E9%83%8E/dp/4797386479/ref=pd_lpo_sbs_14_t_0?_encoding=UTF8&psc=1&refRID=E0EMCHJ2XMS5BSREFVWW)では、ストリームは以下のように定義されています。

> ファイルディスクリプタで表現され、read()またはwrite()で操作できるもの

ファイルをopen()すると、read()またはwrite()を実行できるようになるため、そこに「ストリームがある」ということができます。

## catコマンドをつくる
サンプルプログラムがいきなり難しくなりました...が、処理の流れを確認しながらGoで書いてみます。

```:cat.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

static void do_cat(const char *path);
static void die(const char *s);

int
main(int argc, char *argv[])
{
    int i;
    if (argc <2) {
        fprintf(stderr, "%s: file name not given\n", argv[0]);
        exit(1);
    }
    for (i = 1; i < argc; i++) {
        do_cat(argv[i]);    
    }
    exit(0);
}

#define BUFFER_SIZE 2048

static void
do_cat(const char *path)
{
    int fd;
    unsigned char buf[BUFFER_SIZE];
    int n;

    fd = open(path, O_RDONLY);
    if (fd < 0) die(path);
    for(;;) {
        n = read(fd, buf, sizeof buf);
        if (n<0) die(path);
        if (n==0) break;
        if (write(STDOUT_FILENO, buf, n) < 0) die(path);
    }
    if (close(fd) < 0) die(path);
}

static void
die(const char *s)
{
    perror(s);
    exit(1);
}
```
出典: [ふつうのLinuxプログラミング(第二版)](https://www.amazon.co.jp/%E3%81%B5%E3%81%A4%E3%81%86%E3%81%AELinux%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0-%E7%AC%AC2%E7%89%88-Linux%E3%81%AE%E4%BB%95%E7%B5%84%E3%81%BF%E3%81%8B%E3%82%89%E5%AD%A6%E3%81%B9%E3%82%8Bgcc%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0%E3%81%AE%E7%8E%8B%E9%81%93-%E9%9D%92%E6%9C%A8-%E5%B3%B0%E9%83%8E/dp/4797386479/ref=pd_lpo_sbs_14_t_0?_encoding=UTF8&psc=1&refRID=E0EMCHJ2XMS5BSREFVWW) P86

主要な処理はdo_cat関数に書かれています。

この関数でははじめに、引数で渡されるファイルを読み取り専用で開いています(open())。

ループで回しながら、ファイルの終わりまでストリームからバイト列を読み込み(read())、読み込んだバイト数のぶんだけ標準出力に書き込んでいます(write())。

処理の最後にはファイルを閉じています(close())。

また、die()という関数でエラー時の処理を行っています。
エラーが発生したらメッセージを出し、exit()しています。

## Goで書いてみる
Goではファイルを開くためにosパッケージのOpen、読み込みのためにioパッケージのReader、書き込みのためにioパッケージのWriteStringを利用しました。

``` :cat.go
package main

import (
	"fmt"
	"io"
	"os"
)

const bufferSize = 2048

func doCat(path string) error {
	fd, err := os.Open(path)
	if err != nil {
		return fmt.Errorf("ファイルが開けませんでした%s", fd)
	}
	defer fd.Close()

	buf := make([]byte, bufferSize)
	for {
		n, err := fd.Read(buf)
		if n == 0 {
			break
		}
		if err == io.EOF {
			break
		}
		if err != nil {
			return fmt.Errorf("ファイルが読み込めませんでした%s", fd)
		}
		io.WriteString(os.Stdout, string(buf))
	}
	return nil
}

func run() error {
	argc := len(os.Args)
	argv := os.Args
	if argc < 2 {
		fmt.Fprintf(os.Stderr, "%s: ファイルを指定してください\n", argv[0])
	}
	for i := 1; i < argc; i++ {
		return doCat(argv[i])
	}
	return nil
}

func main() {
	if err := run(); err != nil {
		fmt.Fprintf(os.Stderr, "%s\n", err.Error())
		os.Exit(1)
	}
}
```

os.Openを使うと読み込み専用のファイルを開くことができます。

また、今回はC言語のサンプルに極力近づけるために、バッファを自分で定義してio.Readerに渡すようにしました。
バッファを厳密に定義する必要がなければ、bufioパッケージのScannerを使うのが便利だと思います。Scannerはファイルの終端までScanを実行し、終端に至ると停止します。

今回はio.EOFを使い、終端に至ったら処理を終了するようにしました。

また、読み込みを行うio.Readerは「何バイト読み込み、書き込んだか」をint型で返すため、負の数が返ることはないと考え、参考にしたC言語のサンプル内に存在した「0より小さかったらファイルを閉じる」という処理は省きました。

書き込みについても、今回はサンプルに近づけるためにio.WriteStringを使いました。
特別な目的がなければ、io.Writerを内部的に呼んでいる fmt.Printlnなどを使うのが一般的かつフォーマットもできて便利かと思います。

## 追記
実は本ではこのあと、システムコールではなくstdioを使ってつくるcatコマンドのサンプルがありました！！(下のcat2.cの例です）

上で書いたcat.goのコードでは、Cでシステムコールを使って直接プログラムをする例に寄せようとしたため、冗長だったり強引に書いているところがありました:sob:
そこで、改めてGoでよく見る書き方に直してみました。

``` :cat2.c
#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[])
{
	int i;
	for (i =1; i < argc; i++) {
		FILE *f;
		int c;

		f = fopen(argv[i], "r");
		if (!f) {
			perror(argv[i]);
			exit(1);
		}
		while ((c = fgetc(f)) != EOF) {
			if (putchar(c) < 0) exit(1);
		}
		fclose(f);
	}
	exit(0);
}

```
出典: [ふつうのLinuxプログラミング(第二版)](https://www.amazon.co.jp/%E3%81%B5%E3%81%A4%E3%81%86%E3%81%AELinux%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0-%E7%AC%AC2%E7%89%88-Linux%E3%81%AE%E4%BB%95%E7%B5%84%E3%81%BF%E3%81%8B%E3%82%89%E5%AD%A6%E3%81%B9%E3%82%8Bgcc%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0%E3%81%AE%E7%8E%8B%E9%81%93-%E9%9D%92%E6%9C%A8-%E5%B3%B0%E9%83%8E/dp/4797386479/ref=pd_lpo_sbs_14_t_0?_encoding=UTF8&psc=1&refRID=E0EMCHJ2XMS5BSREFVWW) P110


``` :cat2.go
package main

import (
	"bufio"
	"fmt"
	"os"
)

func doCat() error {
	argv := os.Args
	argc := len(argv)

	if argc < 2 {
		return fmt.Errorf("ファイルを指定してください")
	}

	for i := 1; i < argc; i++ {
		f, err := os.Open(argv[i])
		defer f.Close()
		if err != nil {
			return fmt.Errorf("ファイルを読み込めませんでした。%s", f)
		}
		sc := bufio.NewScanner(f)
		for sc.Scan() {
			fmt.Print(sc.Text())
		}
		if err := sc.Err(); err != nil {
			panic(err)
		}
	}
	return nil
}

func main() {
	if err := doCat(); err != nil {
		fmt.Fprintf(os.Stderr, "%s\n", err.Error())
		os.Exit(1)
	}
}
```

bufio.Scannerを使ってファイルから1行ずつ読み込み、fmt.Printを使ってデフォルトのフォーマットで標準出力に書き込むようにしました。


以上、もし間違い等ありましたら、コメントでご指摘いただければ幸いです :bow:

## 参考
* [GoDoc | io | Reader](https://golang.org/pkg/io/#PipeReader.Read)
* [GoDoc | bufio | Scanner](https://golang.org/pkg/bufio/#Scanner)
* [Goならわかるシステムプログラミング ― 第2回 | 低レベルアクセスへの入り口（1）：io.Writer]
(http://ascii.jp/elem/000/001/243/1243667/)
* [Goならわかるシステムプログラミング ― 第3回 | 低レベルアクセスへの入り口（2）：io.Reader前編](http://ascii.jp/elem/000/001/252/1252961/#ファイル入力)
* [DRYな備忘録 | 【Go言語】可変長のioをReadしたい【bufio.Scanner】【io.Rader】](http://otiai10.hatenablog.com/entry/2014/08/24/140438)
* [お気楽 Go 言語プログラミング入門 | ファイル入出力](http://www.geocities.jp/m_hiroi/golang/abcgo11.html)
[github.com/tenntenn/gohandon | step3](https://github.com/tenntenn/gohandson/tree/master/imgconv/ja/solution/src/step3)

## 前回
[『ふつうのLinuxプログラミング』サンプルプログラムをGoで書いてみる①](https://qiita.com/mom0tomo/items/bd70d9840d83e26d4549)