8

Bash has a 'magical behavior', if you type 'ls', usually you will get colorful output, but if you redirect the output to a file, the color codes are gone. How to achive this effect using Go. e.g. With the following statement:

fmt.Println("\033[1;34mHello World!\033[0m")

I can see the text in color, but if I pipe the output to a file, the color is preserved, which is NOT what I want.

BTW, this question is mostly not related to Go, I just want to achive the effect in my go program.

Jonathan Hall
  • 75,165
  • 16
  • 143
  • 189
xrfang
  • 1,754
  • 4
  • 18
  • 36
  • 1
    [This seems to be the most popular package for implementing `isatty`.](https://github.com/mattn/go-isatty) There are a few others that I have come across. – torek Aug 23 '21 at 08:47
  • It is possible on some OSes. [Logrus](https://github.com/sirupsen/logrus) has support for this for various OSes in the various `terminal_check*` files. – Jonathan Hall Aug 23 '21 at 08:48
  • 1
    `Bash has a 'magical behavior', if you type 'ls', usually you will get colorful output, but if you redirect the output to a file, the color codes are gone.` - this is ls feature, not Bash – Arkadiusz Drabczyk Aug 23 '21 at 08:55
  • The accepted answer isn't great -- @ptx comment is the most portable answer: "A better approach is to use term.IsTerminal from x/term" – Jon Watte Mar 17 '23 at 15:22

3 Answers3

5

Bash has a 'magical behavior', if you type 'ls', usually you will get colorful output, but if you redirect the output to a file, the color codes are gone.

It's not Bash feature, it's ls feature. It calls isatty() to check if stdout file descriptor refers to a terminal. In musl libc isatty is implemented like that:

int isatty(int fd)
{
        struct winsize wsz;
        unsigned long r = syscall(SYS_ioctl, fd, TIOCGWINSZ, &wsz);
        if (r == 0) return 1;
        if (errno != EBADF) errno = ENOTTY;
        return 0;
}

You can use the same method in Go:

package main

import (
        "fmt"
        "os"

        "golang.org/x/sys/unix"
)

func main() {
        _, err := unix.IoctlGetWinsize(int(os.Stdout.Fd()), unix.TIOCGWINSZ)
        if err != nil {
                fmt.Println("Hello World")
        } else {
                fmt.Println("\033[1;34mHello World!\033[0m")
        }
}
Arkadiusz Drabczyk
  • 11,227
  • 2
  • 25
  • 38
  • This actually works. However, my code is: `func Dump(w io.Writer) { ... }`, so, I can simply use your code, plus a wrapper: if w == os.Stdout { ... } – xrfang Aug 23 '21 at 09:13
  • one more question: does this work on Windows? https://github.com/mattn/go-isatty has a function called `IsCygwinTerminal()`, this means tty-color is a feature of *nix system (or windows with an emulator)? – xrfang Aug 23 '21 at 09:15
  • I don't know, I don't use Windows – Arkadiusz Drabczyk Aug 23 '21 at 09:16
  • 2
    A better approach is to use `term.IsTerminal` from [x/term](https://pkg.go.dev/golang.org/x/term#IsTerminal). It works on windows and is maintained by the golang team – ptx Mar 07 '23 at 09:15
  • Note that you actually need to use`term.IsTerminal(int(os.Stdout.Fd()))`, for a complete and usable answer – Lanfeust Aug 25 '23 at 15:09
5

I'm just answering by copying @ptx comment, which is the more canonical answer:

A better approach is to use term.IsTerminal from x/term. It works on windows and is maintained by the golang team

This is to raise visibility, as this answer is portable and doesn't add an external dependency.

package main

import (
    "os"
    "golang.org/x/term"
)

func main() {
    if term.IsTerminal(int(os.Stdout.Fd())) {
        // do stuff when we are attached to a tty
    } else {
        // do stuff when we are not attached
    }
}
Benjamin Buch
  • 4,752
  • 7
  • 28
  • 51
Jon Watte
  • 6,579
  • 4
  • 53
  • 63
0

If you are just viewing a log file and don't want colors being preserved because it makes logs less readable, note that

less -r logfile
more -r logfile
multitail -cT ANSI logfile

will make these commands display ANSI colors.

There is also a vim plugin which does the same: https://github.com/powerman/vim-plugin-AnsiEsc

For some use cases this could be a simpler solution than upgrading an app to detect whether an output is attached to a tty.

Alex Rudakov
  • 377
  • 1
  • 3
  • 7