【Go言語】単体コード集

単機能コードを書き残しただけのページ
 
大半は、挙動を見るために作成するなどした簡単なテストコード集。
捨ててしまうと自身のスキルやノウハウとして残せないが、かと言って素材集としてまとめることも面倒なので、そのまま書き出している。
なお、ブログ記載のために行頭タブを半角スペース2個にしている*1


1章 パッケージ使用例
 1.1 flags
   flags1.go
 1.2 termbox
   termbox1.go
   termbox2.go
   termbox3.go

2章 ファイル入出力系
 2.1 ファイル入力
   os.Open.1.go
   os.Open.2.go
   os.Open.3.go
 2.2 標準入力
   os.Stdin.1.go
 2.3 一時ファイルの作成・削除をするプログラム
   ioutil.TempFile1.go
 2.4 ファイルへの書き込みを行う
   ioutil.WriteFile1.go
   os.File.WriteFile2.go

3章 ファイルシステム操作系
 3.1 find $dir -type f 相当をする
   find_f1.go

4章 システム関数系
 4.1 find $* -type f を呼び出す
   exec.Command_find1.go
 4.2 find $* -type d を並列実行する
   exec.Command_find2.go
 4.3 find $* -type d > 一時ファイル を並列実行する
   exec.Command_find3.go
 4.4 最上位階層のディレクトリ名を取得し、除外対象は取得結果から削除する
   exec.Command_find4.go

5章 文字列操作系
 5.1 fmt.Sprintf() を使って文字列を組み立てる
   fmt.Sprintf1.go
 5.2 []byte を整数値に変換する
   byte2int_1.go

 
 

1.1

 
概要
flags を使ったオプションパース例。

GoDoc
flags - GoDoc

事前準備

% go get github.com/jessevdk/go-flags

 

flags1.go
// flag パッケージの使用メモ
// -h を引数に指定すると、Help メッセージが表示される.

package main

import (
  "fmt"
  "os"

  "github.com/jessevdk/go-flags"
)

type CmdOptions struct {
  Help bool   `short:"h" long:"help" description:"show this help message and exit"`
  TTY  string `long:"tty" description:"path to the TTY (usually, the value of $TTY)"`
}

func main() {
  var err error

  // CmdOptions 構造体データへのポインタを取得する
  opts := &CmdOptions{}

  // flag パッケージのインスタンスを獲得する。(NewXXXX は C++ のコンストラクタに相当する)
  p := flags.NewParser(opts, flags.PrintErrors)
  args, err := p.Parse() // &opts, os.Args)
  if err != nil {
    panic(err)
    os.Exit(1)
  }

  if opts.Help {
    fmt.Printf("[TEST] Help Message\n")
    os.Exit(1)
  }

  // args にはオプション以外の引数が格納される.下記の場合には、a.txt b.txt の 2つをポイントしている。
  // ./flags1 --tty=/dev/stdin a.txt b.txt
  if len(args) > 0 {
    fmt.Printf("os.Open(%s)\n", args[0])
    if err != nil {
      os.Exit(1)
    }
  }
}

 
 

1.2

 
termbox1.go の概要
termbox により標準入力を受信し, エコーバックするプログラム。Esc で終了する。
本プログラムは Peco のコードを参考にした。
 
termbox2.go の概要
termbox1.go に対して、入力したキーコードを連結する処理を追加した。
 

termbox3.go の概要
termbox2.go に対して、Backspace により文字列の末尾1文字を消す処理を追加した。

 

GoDoc
termbox - GoDoc

事前準備

% go get github.com/nsf/termbox-go

ビルド&実行

% go build termbox1.go

 

termbox1.go
package main

import (
  "fmt"
  "os"

  "github.com/nsf/termbox-go"
)

func main() {
  var err error

  err = termbox.Init()

  if err != nil {
    fmt.Fprintln(os.Stderr, err)
    os.Exit(1)
  }
  defer termbox.Close()

  termbox.SetInputMode(termbox.InputEsc)

  mainLoop()
}

func mainLoop() {
  var loop bool = true

  for loop {
    ev := termbox.PollEvent()
    if ev.Type == termbox.EventError {
      //update = false
    } else if ev.Type == termbox.EventKey {
      handleKeyEvent(ev)
    }
  }
}

func handleKeyEvent(ev termbox.Event) {
  switch ev.Key {
  case termbox.KeyEsc:
    termbox.Close()
    os.Exit(1)
  default:
    fmt.Printf("%c", ev.Ch)
  }
}

 

termbox2.go
// 入力した文字はグローバル変数の g_query に格納される
package main

import (
  "fmt"
  "os"

  "github.com/nsf/termbox-go"
)

func main() {
  var err error

  err = termbox.Init()

  if err != nil {
    fmt.Fprintln(os.Stderr, err)
    os.Exit(1)
  }
  defer termbox.Close()

  termbox.SetInputMode(termbox.InputEsc)

  mainLoop()
}

func mainLoop() {
  var loop bool = true

  for loop {
    ev := termbox.PollEvent()
    if ev.Type == termbox.EventError {
      //update = false
    } else if ev.Type == termbox.EventKey {
      handleKeyEvent(ev)
    }
  }
}

var g_query []rune

func handleKeyEvent(ev termbox.Event) {

  switch ev.Key {
  case termbox.KeyEsc:
    termbox.Close()
    os.Exit(1)
  default:
    g_query = append(g_query, ev.Ch)
    fmt.Printf("[%c]", ev.Ch)
    fmt.Println(string(g_query))
  }
}

 

termbox3.go
// 入力した文字はグローバル変数の g_query に格納される
package main

import (
  "fmt"
  "os"

  "github.com/nsf/termbox-go"
)

func main() {
  var err error

  err = termbox.Init()

  if err != nil {
    fmt.Fprintln(os.Stderr, err)
    os.Exit(1)
  }
  defer termbox.Close()

  termbox.SetInputMode(termbox.InputEsc)

  mainLoop()
}

func mainLoop() {
  var loop bool = true

  for loop {
    ev := termbox.PollEvent()
    if ev.Type == termbox.EventError {
      //update = false
    } else if ev.Type == termbox.EventKey {
      handleKeyEvent(ev)
    }
  }
}

var g_query []rune

func handleKeyEvent(ev termbox.Event) {

  switch ev.Key {
  case termbox.KeyEsc:
    termbox.Close()
    os.Exit(1)
  case termbox.KeyBackspace, termbox.KeyBackspace2:
    // ポイントする位置を (配列の) 1つ前の要素に戻す
    if len(g_query) != 0 {
      g_query = g_query[:len(g_query)-1]
    }
  default:
    g_query = append(g_query, ev.Ch)
    fmt.Printf("[%c]", ev.Ch)
    fmt.Println(string(g_query))
  }
}

 
 

2.1

 
ファイルを開くだけのプログラム
os.Open.3.go の Scanner を使う方法が良い模様。
os.Open.1.go と os.Open.2.go は EOF を見つけると panic になった。(が原因不明)

os.Open.1.go
ReadString() による読み出しをする。(Windows 環境で '\r' が残る問題があるとのこと)
ファイルが存在しない場合や、引数が空の場合はエラーとなる。

os.Open.2.go
os.Open.1.go の ReadString() 部分を ReadLine に変更したプログラム。

os.Open.3.go
os.Open.1.go の ReadString() 部分を ReadLine に変更したプログラム。
 

os.Open.1.go
package main

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

func main() {

  var err error
  var input *os.File // os.File 型へのポインタ input を用意する

  if len(os.Args) > 0 {
    input, err = os.Open(os.Args[1])
    if err != nil {
      os.Exit(1)
    }
  }

  defer input.Close()

  // readline 相当
  rdr := bufio.NewReader(input)
  for {
    line, err := rdr.ReadString('\n')
    if err != nil {
      fmt.Fprintln(os.Stderr, err)
      break
    }

    fmt.Fprintf(os.Stderr, "%s", line)
  }
}

 

os.Open.2.go
package main

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

func main() {

  var err error
  var input *os.File // os.File 型へのポインタ input を用意する

  fmt.Println(os.Args[1])
  if len(os.Args) > 0 {
    input, err = os.Open(os.Args[1])
    if err != nil {
      fmt.Fprintln(os.Stderr, err)
      os.Exit(1)
    }
  }

  defer input.Close()

  // readline 相当
  rdr := bufio.NewReader(input)
  for {
    line, _, err := rdr.ReadLine()
    if err != nil {
      fmt.Fprintln(os.Stderr, err)
      break
    }

    fmt.Fprintf(os.Stderr, "%s\n", line)
  }
}

 

os.Open.3.go
package main

import (
  "bufio"
  _ "errors"
  "fmt"
  "os"
)

func main() {
  fp, _ := os.Open("/tmp/golang_144548915")

  defer fp.Close()

  scanner := bufio.NewScanner(fp)
  for scanner.Scan() {
    fmt.Println(scanner.Text())
  }
}

 
 

2.2

 
標準入力の内容を stderr にエコーバックするプログラム
 

os.Stdin.1.go
package main

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

func main() {

  var input *os.File = os.Stdin

  // readline 相当
  rdr := bufio.NewReader(input)
  for {
    line, err := rdr.ReadString('\n')
    if err != nil {
      fmt.Fprintln(os.Stderr, err)
      break
    }

    fmt.Fprintf(os.Stderr, "%s", line)
  }
}

 
 

2.3

 
一時ファイルの作成・削除をするプログラム

マルチスレッド時にタスク(goroutine)ごとに専用の一時ファイルを使うことを想定した作りにしている。
 

ioutil.TempFile1.go
// システムに搭載されている CPU の数だけ一時ファイルを作成する。
// 作成したファイルには os.Fileポインタ配列を使ってアクセスする。
package main

import (
  "fmt"
  "io/ioutil"
  "os"
  "runtime"
)

func main() {

  cpus := runtime.NumCPU() // システムに搭載されている CPU の個数
  runtime.GOMAXPROCS(cpus) // 並列処理する際に使用する CPU の個数

  var tmpfList []*os.File

  // 一時ファイルを作成する
  for i := 0; i < cpus; i++ {
    tmpf, _ := ioutil.TempFile("", "golang_")

    fmt.Println("[DEBUG(1)] ", tmpf.Name())
    tmpfList = append(tmpfList, tmpf)
  }

  // os.File ポインタ配列の内容を確認する. (上記で作成したファイルと一致していれば OK)
  for i := 0; i < len(tmpfList); i++ {
    fmt.Println("[DEBUG(2)] ", tmpfList[i].Name())
  }

  // 一時ファイルを削除する
  for i := 0; i < len(tmpfList); i++ {
    fmt.Printf("[DEBUG(3)] defer os.Remove(%s)\n", tmpfList[i].Name())
    defer os.Remove(tmpfList[i].Name())
  }
  return
}

 
 

2.4

 
ファイルへの書き込みを行う
 

ioutil.WriteFile1.go
2.3 で作成した一時ファイル作成処理に書き込み処理を追加したパターン。
以下の処理で上書き書き込みをしている。

    ioutil.WriteFile(tmpfList[i].Name(), []byte("piyo"), os.ModePerm)

 
os.File.WriteFile2.go
2.3 で作成した一時ファイル作成処理に書き込み処理を追加したパターン。
以下の処理で上書き書き込みをしている。

    tmpfList[i].WriteFile("piyo")

 

ioutil.WriteFile1.go
package main

import (
  "fmt"
  "io/ioutil"
  "os"
  "runtime"
)

func main() {

  cpus := runtime.NumCPU() // システムに搭載されている CPU の個数
  runtime.GOMAXPROCS(cpus) // 並列処理する際に使用する CPU の個数

  var tmpfList []*os.File

  // 一時ファイルを作成する
  for i := 0; i < cpus; i++ {
    tmpf, _ := ioutil.TempFile("", "golang_")

    fmt.Println("[DEBUG(1)] ", tmpf.Name())
    tmpfList = append(tmpfList, tmpf)
  }

  // os.File ポインタ配列の内容を確認する. (上記で作成したファイルと一致していれば OK)
  for i := 0; i < len(tmpfList); i++ {
    fmt.Println("[DEBUG(2)] ", tmpfList[i].Name())
    // 作成した一時ファイルへの書き込みを行う
    ioutil.WriteFile(tmpfList[i].Name(), []byte("piyo"), os.ModePerm)
  }

  // 一時ファイルを削除する
  for i := 0; i < len(tmpfList); i++ {
    fmt.Printf("[DEBUG(3)] defer os.Remove(%s)\n", tmpfList[i].Name())
    //      defer os.Remove(tmpfList[i].Name())
  }
  return
}

 

os.File.WriteFile2.go
package main

import (
  "fmt"
  "io/ioutil"
  "os"
  "runtime"
)

func main() {

  cpus := runtime.NumCPU() // システムに搭載されている CPU の個数
  runtime.GOMAXPROCS(cpus) // 並列処理する際に使用する CPU の個数

  var tmpfList []*os.File

  // 一時ファイルを作成する
  for i := 0; i < cpus; i++ {
    tmpf, _ := ioutil.TempFile("", "golang_")

    fmt.Println("[DEBUG(1)] ", tmpf.Name())
    tmpfList = append(tmpfList, tmpf)
  }

  // os.File ポインタ配列の内容を確認する. (上記で作成したファイルと一致していれば OK)
  for i := 0; i < len(tmpfList); i++ {
    fmt.Println("[DEBUG(2)] ", tmpfList[i].Name())
    // 作成した一時ファイルへの書き込みを行う
    tmpfList[i].WriteString("PIYO")
  }

  // 一時ファイルを削除する
  for i := 0; i < len(tmpfList); i++ {
    fmt.Printf("[DEBUG(3)] defer os.Remove(%s)\n", tmpfList[i].Name())
    //      defer os.Remove(tmpfList[i].Name())
  }
  return
}

 
 

3.1

 
find . -type f 相当の処理をする

find_f1.go
探索対象のパスは /home/neko/Pictures で固定とする。
参考にしたサイト
Golangでディレクトリ内のファイル一覧を入手する

 

find_f1.go
package main

import (
  "fmt"
  "io/ioutil"
  "path/filepath"
)

func walkdir(d string) []string {
  nodes, err := ioutil.ReadDir(d)
  if err != nil {
    panic(err)
  }

  var dlist []string
  for _, f := range nodes {
    if f.IsDir() {
      dlist = append(dlist, walkdir(filepath.Join(d, f.Name()))...)
      continue
    }
    dlist = append(dlist, filepath.Join(d, f.Name()))
  }

  return dlist
}

func main() {
  for _, v := range walkdir("/home/neko/Pictures") {
    fmt.Printf("%s\n", v)
  }
}

 
 

3.2

 
 

4.1

 
find コマンドを呼び出す

自分でディレクトリを探索しても速度は遅いので find コマンドを使うようにする。

exec.Command_find1.go
find /home/neko/Pictures -type f を行う
 
参考にしたページ
Golangで外部コマンドを実行する方法まとめ
 

exec.Command_find1.go
package main

import (
  "fmt"
  "os/exec"
  _ "strings"
)

func main() {

  cmd := "find"
  args := []string{"/home/neko/Pictures", "-type", "f"}

  // for k, v := range args {
  //     fmt.Printf("[%d] => %s\n", k, v)
  // }

  out, _ := exec.Command(cmd, args...).Output()

  fmt.Println(string(out))
}

 
 

4.2

 
goroutine を使って以下のコマンドを並列実行させる。

find /tmp -type d
find /var -type d
find /etc -type d
find /opt -type d

 
今回は上記 4つの find コマンドを実行するので生成するタスク数は 4個となる。
goroutine は CPU の個数 (= runtime.NumCPU() の値) だけ生成することにした。

 
参考にしたサイト
Go - Execute a Bash Command n Times using goroutines and Store & Print its result - Stack Overflow
 

exec.Command_find2.go
package main

import (
  "fmt"
  "os/exec"
  "runtime"
  "sync"
)

func main() {

  cpus := runtime.NumCPU() // システムに搭載されている CPU の個数
  runtime.GOMAXPROCS(cpus) // 並列処理する際に使用する CPU の個数
  tasks := make(chan *exec.Cmd, cpus)

  dlist := []string{"/tmp", "/var", "/etc", "/opt"}

  // 起動したすべての goroutine の終了を待ち合わせる。
  // Add で加算、Done で減算し、Wait で 0 になるのを待つ。
  var wg sync.WaitGroup

  // goroutine を 4個作る
  for i := 0; i < cpus; i++ {
    wg.Add(1)
    go func(num int, w *sync.WaitGroup) {
      defer w.Done()
      var (
        //out []byte
        err error
      )
      for cmd := range tasks { // this will exit the loop when the channel closes
        out, _ := cmd.Output()
        if err != nil {
          fmt.Printf("can't get stdou:", err)
        }
        out = out
        //              fmt.Printf("[DEBUG] goroutine(%d)\n", num)
        fmt.Printf("%s", string(out))
      }
    }(i, &wg)
  }

  //Generate Tasks
  for i := 0; i < len(dlist); i++ {
    tasks <- exec.Command("find", dlist[i], "-type", "d")
  }
  close(tasks)

  // wait for the workers to finish
  wg.Wait()

  fmt.Println("Done")
}

 
 

4.3

 
goroutine を使って以下のコマンドを並列実行させる。

find /tmp -type d > /tmp/golang_[random]
find /var -type d > /tmp/golang_[random]
find /etc -type d > /tmp/golang_[random]
find /opt -type d > /tmp/golang_[random]

 
今回は上記 4つの find コマンドを実行するので生成するタスク数は 4個となる。
goroutine は CPU の個数 (= runtime.NumCPU() の値) だけ生成することにした。

 
参考にしたサイト
Go - Execute a Bash Command n Times using goroutines and Store & Print its result - Stack Overflow
 

exec.Command_find3.go
package main

import (
  "errors"
  "fmt"
  "io/ioutil"
  "os"
  "os/exec"
  "runtime"
  "sync"
)

func mktmpfiles(nums int) (tmpfList []*os.File, err error) {

  // 一時ファイルを作成する
  for i := 0; i < nums; i++ {
    tmpf, e := ioutil.TempFile("", "golang_")
    if e != nil {
      err = e
      return
    }

    fmt.Println("[DEBUG(created)] ", tmpf.Name())
    tmpfList = append(tmpfList, tmpf)
  }

  // os.File ポインタ配列の内容を確認する. (上記で作成したファイルと一致していれば OK)
  for i := 0; i < len(tmpfList); i++ {
    fmt.Println("[DEBUG(ls)] ", tmpfList[i].Name())
  }

  return
}

func main() {

  cpus := runtime.NumCPU() // システムに搭載されている CPU の個数
  runtime.GOMAXPROCS(cpus) // 並列処理する際に使用する CPU の個数
  tasks := make(chan *exec.Cmd, cpus)

  dlist := []string{"/tmp", "/var", "/etc", "/opt"}

  tmpfList, err := mktmpfiles(cpus)
  if err != nil {
    return
  }

  // 一時ファイルを削除する
  for i := 0; i < len(tmpfList); i++ {
    fmt.Printf("[DEBUG(remove)] defer os.Remove(%s)\n", tmpfList[i].Name())
    //      defer os.Remove(tmpfList[i].Name())
  }

  // 起動したすべての goroutine の終了を待ち合わせる。
  // Add で加算、Done で減算し、Wait で 0 になるのを待つ。
  var wg sync.WaitGroup

  // goroutine を 4個作る
  for i := 0; i < cpus; i++ {
    wg.Add(1)
    go func(num int, fpw *os.File, w *sync.WaitGroup) {
      defer w.Done()
      var (
        //out []byte
        err error
      )

      for cmd := range tasks { // this will exit the loop when the channel closes
        out, _ := cmd.Output()
        if err != nil {
          fmt.Printf("can't get stdou:", err)
        }
        // fmt.Printf("[DEBUG] goroutine(%d)\n", num)
        // fmt.Printf("[DEBUG] %s", string(out))
        fpw.WriteString(string(out))
      }
    }(i, tmpfList[i], &wg)
  }

  //Generate Tasks
  for i := 0; i < len(dlist); i++ {
    tasks <- exec.Command("find", dlist[i], "-type", "d")
  }
  close(tasks)

  // wait for the workers to finish
  wg.Wait()

  fmt.Println("Done")
}

 
 

4.4

 
最上位階層のディレクトリ名を取得し、除外対象はリストから外す

実行していることは以下である。

1.「find / -maxdepth 1 -mindepth 1 -type d 2>/dev/null 」を使って最上位階層のディレクトリ名を取得する.
2. 上記 1 のうち、除外対象のディレクトリ( excludeDirs ) であれば、取得項目から外す。

 

exec.Command_find4.go
package main

import (
  "fmt"
  "os/exec"
  "strings"
)

func main() {
  cmd := "find"
  args := strings.Fields("/ -maxdepth 1 -mindepth 1 -type d")
  excludeDirs := strings.Fields("/tmp /proc /cdrom /lib /lost+found /mnt /proc /run sys /dev /lib64 /media /root /srv /tmp")

  //  for k, v := range args {
  //      fmt.Printf("args[%d] => %s\n", k, v)
  //  }

  temp, _ := exec.Command(cmd, args...).Output()

  // temp は []byte なので string 型にキャストしてから改行区切りでスライスにする
  topDirs := strings.Split(string(temp), "\n")
  target := []string{}
  for _, v := range topDirs {
    isAppend := true
    for _, e := range excludeDirs {
      // 除外対象あるいは空白の場合
      if strings.HasPrefix(v, e) || v == "" {
        isAppend = false
        break
      }
    }
    if isAppend {
      target = append(target, v)
    }
  }
  for i := 0; i < len(target); i++ {
    fmt.Printf("target[%d] => %s\n", i, target[i])
  }
  excludeDirs = excludeDirs
}

 
 

4.5

 
 

5.1

 
fmt.Sprintf() を使って文字列を組み立てる
 
組み立てた文字列を使って sh -c 'xxxx' としてコマンドを実行している。
 

fmt.Sprintf1.go
package main

import (
  _ "errors"
  "fmt"
  "io/ioutil"
  "os"
  "os/exec"
  "strings"
)

var g_NumCPUs int

func mktmpfiles(nums int) (tmpfList []*os.File, err error) {

  // 一時ファイルを作成する
  for i := 0; i < nums; i++ {
    tmpf, e := ioutil.TempFile("", "golang_")
    if e != nil {
      err = e
      return
    }

    fmt.Println("[DEBUG(created)] ", tmpf.Name())
    tmpfList = append(tmpfList, tmpf)
  }

  return
}

func main() {

  fpDirFiles, _ := mktmpfiles(3)
  inFiles := []string{}
  for i := 0; i < len(fpDirFiles); i++ {
    inFiles = append(inFiles, fpDirFiles[i].Name())
  }

  //----------------------------------------
  // 何かしら作成した一時ファイルに書き込む
  //----------------------------------------

  // ディレクトリ一覧をソートして 1つにまとめる
  cmdline := fmt.Sprintf("cat %s | sort -u > /tmp/combined.txt", strings.Join(inFiles, " "))
  _, _ = exec.Command("sh", "-c", cmdline).Output()
  fmt.Println("[DEBUG] ", cmdline)
}

 
 

5.2

 
バイト配列を整数値に変換する
 
いくつか方法があり、簡単ではなさそうである。
 

byte2int_1.go
[]byte → string → inという経路で変換する。

 
 

byte2int_1.go
package main

import (
  _ "errors"
  "fmt"
  "io/ioutil"
  "os"
  "os/exec"
  "strconv"
  "strings"
)

var g_NumCPUs int

func mktmpfiles(nums int) (tmpfList []*os.File, err error) {

  // 一時ファイルを作成する
  for i := 0; i < nums; i++ {
    tmpf, e := ioutil.TempFile("", "golang_")
    if e != nil {
      err = e
      return
    }

    fmt.Println("[DEBUG(created)] ", tmpf.Name())
    tmpfList = append(tmpfList, tmpf)
  }

  return
}

func main() {

  fpDirFiles, _ := mktmpfiles(3)
  inFiles := []string{}
  for i := 0; i < len(fpDirFiles); i++ {
    inFiles = append(inFiles, fpDirFiles[i].Name())
  }

  //----------------------------------------
  // 何かしら作成した一時ファイルに書き込む
  //----------------------------------------

  // ディレクトリ一覧をソートして 1つにまとめる
  cmdline := fmt.Sprintf("cat %s | sort -u > /tmp/combined.txt", strings.Join(inFiles, " "))
  _, _ = exec.Command("sh", "-c", cmdline).Output()

  // wc -l でファイル /tmp/combined.txt の行数を算出する
  cmdline = "cat /tmp/combined.txt | wc -l"

  // buff には "145\n" といったように改行文字を末尾に含む場合があるので削除する
  buff, _ := exec.Command("sh", "-c", cmdline).Output()

  // 改行を含んでいると dirNums は空になってしまう
  dirNums, _ := strconv.Atoi(strings.TrimRight(string(buff), "\n"))

  // 145 と表示される
  fmt.Printf("%d\n", dirNums)
}

 

*1:gofmt XXXX.go | expand -i -t 2