いろいろ備忘録日記

主に .NET とか Go とか Flutter とか Python絡みのメモを公開しています。

Goメモ-615 (ファイルディスクリプタが端末を参照しているか判定)(isatty, go-isatty, ioctl, TCGETS)

関連記事

GitHub - devlights/blog-summary: ブログ「いろいろ備忘録日記」のまとめ

概要

以下、自分用のメモです。

たまに、自分のプログラムの挙動にて「そのまま実行されている」場合と「パイプやリダイレクトされている」場合で出力形式を変えたい場合があります。

Linux(UNIX)の世界では、昔から isatty(3) というライブラリ関数が用意されているので、よくそれを利用して判定しますね。

んで、Goでもちょっと使いたいときがあったので、ライブラリ探してみたら mattn さんの go-isatty パッケージを発見。

github.com

ちなみに、isatty(3)という関数名をよく is-atty って感じで読む人いますが、この関数は is-a-tty って意味です。

そのまんま、「ttyですか?」って意味ですね。

サンプル

isatty/main.go

package main

import (
    "log"
    "os"

    "github.com/mattn/go-isatty"
)

func main() {
    log.SetFlags(0)
    log.SetPrefix(">>> ")

    // isatty(3) は、指定したファイルディスクリプタが端末(tty)を参照しているかをチェックする関数.
    // そのディスクリプタが端末を指している場合は 1, それ以外は 0 を返す.
    // go-isattyパッケージは、Goにてtty判定を利用する場合に最も利用されているパッケージ.
    //
    // - ディスクリプタ自体が無効な場合(開かれていない/存在しない)は0を返し、errnoにEBADFが設定
    // - ディスクリプタが存在するが「端末ではない」(例えば通常のファイルやパイプなど)場合は0を返し、errnoにENOTTYが設定
    //
    // OSがLinuxの場合、内部で unix.IoctlGetTermios(int(fd), unix.TCGETS) が呼び出されている.
    // ioctl(2)にTCGETSを渡すと指定のディスクリプタが指す端末情報を取得できる.
    //
    //     #include <sys/ioctl.h>
    //     #include <termios.h>
    //
    //     struct termios tty = {};
    //     ioctl(1, TCGETS, &tty);
    //
    // ちなみに、isattyという名前は is-atty ではなく、is-a-tty という意味。
    //
    // REF: https://man7.org/linux/man-pages/man3/isatty.3.htm
    switch isatty.IsTerminal(os.Stdout.Fd()) {
    case true:
        // 端末に接続されている
        log.Println("Terminal")
    default:
        // 端末に接続されていない
        log.Println("Pipe or Redirect")
    }
}

Taskfile.yml

# https://taskfile.dev

version: '3'

tasks:
  default:
    cmds:
      - go build -o app main.go
      - defer: rm -f app
      - ./app
      - ./app | tee
      - ./app > out.txt
      - defer: rm -f out.txt
      - echo hello | ./app

実行結果

$ task
task: [default] go build -o app main.go
task: [default] ./app
>>> Terminal
task: [default] ./app | tee
>>> Pipe or Redirect
task: [default] ./app > out.txt
>>> Pipe or Redirect
task: [default] echo hello | ./app
>>> Terminal
task: [default] rm -f out.txt
task: [default] rm -f app

ちゃんと、パイプとリダイレクトの場合に判定出来ていますね。

参考情報

www.man7.org

Goのおすすめ書籍


過去の記事については、以下のページからご参照下さい。

サンプルコードは、以下の場所で公開しています。