CLI yang enak dipakai bukan hanya soal menerima masukan; bagaimana program menyampaikan hasilnya ke terminal sama pentingnya. Output yang berantakan menyulitkan pengguna membaca puluhan baris, sedangkan output yang rapi — kolom sejajar, status berlabel, galat berwarna — membuat hasil terbaca dalam sekali lirik. Pada chapter ini kita akan merapikan output dengan lebar kolom fmt.Printf dan text/tabwriter, memberi prefix status seperti [OK] dan [GAGAL], mengenal escape sequence ANSI untuk warna, lalu menutup dengan progress bar sederhana.

Kolom Berlebar Tetap dengan fmt.Printf #

Cara termurah merapikan output adalah format verb lebar pada fmt.Printf: %-12s menampilkan string rata kiri dalam lebar 12 karakter dan %6d menampilkan angka rata kanan dalam lebar 6 karakter. Selama isi kolom tidak melebihi lebar yang dialokasikan, baris-baris akan sejajar:

go
fmt.Printf("%-12s %6s %s\n", "NAMA", "STOK", "STATUS")
fmt.Printf("%-12s %6d %s\n", "kopi", 12, "laku")
fmt.Printf("%-12s %6d %s\n", "teh melati", 3, "hampir habis")

Cara ini rapat biayanya tetapi kaku: begitu satu nilai lebih panjang dari alokasinya, baris di bawahnya bergeser. Untuk data yang panjangnya tidak terduga, text/tabwriter menghitung lebar kolom secara otomatis.

Program Utuh: Tabel, Status, Warna, Progress #

Berkas tabel-demo.go di bawah menggabungkan lima teknik sekaligus: kolom tetap, tabel tabwriter, prefix status dengan warna ANSI, representasi mentah escape sequence, dan progress bar karakter #:

go
package main

import (
	"fmt"
	"os"
	"strings"
	"text/tabwriter"
)

const (
	hijau = "\x1b[32m"
	merah = "\x1b[31m"
	reset = "\x1b[0m"
)

func main() {
	// 1. Kolom berlebar tetap dengan fmt.Printf.
	fmt.Printf("%-12s %6s %s\n", "NAMA", "STOK", "STATUS")
	fmt.Printf("%-12s %6d %s\n", "kopi", 12, "laku")
	fmt.Printf("%-12s %6d %s\n", "teh melati", 3, "hampir habis")

	// 2. Tabel rapi dengan text/tabwriter.
	w := tabwriter.NewWriter(os.Stdout, 0, 4, 2, ' ', 0)
	fmt.Fprintln(w, "SERVER\tPROSES\tSTATUS")
	fmt.Fprintln(w, "web-1\thttp\t[OK]")
	fmt.Fprintln(w, "db-primary\tpostgres\t[OK]")
	fmt.Fprintln(w, "cache\tredis\t[GAGAL]")
	w.Flush()

	// 3. Prefix status dengan warna (konvensi NO_COLOR dihormati).
	if os.Getenv("NO_COLOR") == "" {
		fmt.Printf("%s[OK]%s konfigurasi valid\n", hijau, reset)
		fmt.Printf("%s[GAGAL]%s koneksi database terputus\n", merah, reset)
	} else {
		fmt.Println("[OK] konfigurasi valid")
		fmt.Println("[GAGAL] koneksi database terputus")
	}

	// 4. Representasi mentah escape sequence dengan %q.
	fmt.Printf("repr ok    : %q\n", hijau+"[OK]"+reset)
	fmt.Printf("repr gagal : %q\n", merah+"[GAGAL]"+reset)

	// 5. Progress bar sederhana dengan karakter #.
	total := 5
	for i := 1; i <= total; i++ {
		persen := i * 100 / total
		penuh := i * 20 / total
		fmt.Printf("memproses [%-20s] %3d%%\n", strings.Repeat("#", penuh), persen)
	}
}

Jalankan program dengan NO_COLOR aktif agar baris status tampil sebagai teks polos (lingkungan eksekusi demo ini memang menetapkan NO_COLOR=1):

bash
NO_COLOR=1 go run tabel-demo.go

Output yang Dihasilkan:

text
NAMA           STOK STATUS
kopi             12 laku
teh melati        3 hampir habis
SERVER      PROSES    STATUS
web-1       http      [OK]
db-primary  postgres  [OK]
cache       redis     [GAGAL]
[OK] konfigurasi valid
[GAGAL] koneksi database terputus
repr ok    : "\x1b[32m[OK]\x1b[0m"
repr gagal : "\x1b[31m[GAGAL]\x1b[0m"
memproses [####                ]  20%
memproses [########            ]  40%
memproses [############        ]  60%
memproses [################    ]  80%
memproses [####################] 100%

Mari kita telaah baris demi baris program di atas:

  1. Baris 18-20 (%-12s dan %6d): mencetak tiga baris dengan kolom tetap; nama rata kiri selebar 12, stok rata kanan selebar 6, sisanya mengalir.
  2. Baris 23-28 (tabwriter.NewWriter): membuat writer yang menampung baris sementara; angka 2 berarti tiap kolom diberi dua spasi tambahan, dan pemisah \t menandai batas kolom. w.Flush() menghitung lebar kolom terluas lalu mencetak semuanya.
  3. Baris 31-37 (os.Getenv("NO_COLOR")): konvensi NO_COLOR menyatakan warna dimatikan bila variabel tersebut terisi; program menghormatinya dengan mencetak status polos.
  4. Baris 40-41 (%q pada string berwarna): verb %q menampilkan representasi mentah string, sehingga byte escape \x1b terlihat sebagai teks dan tidak dieksekusi terminal.
  5. Baris 44-48 (strings.Repeat("#", penuh)): menggambar bar sepanjang penuh karakter, dirata-kiri dalam 20 kolom oleh %-20s, diikuti persentase rata kanan %3d.

Warna ANSI dan Representasinya #

Warna terminal bekerja lewat escape sequence: byte escape \x1b diikuti kode warna seperti [32m untuk hijau, [31m untuk merah, dan [0m untuk mengembalikan normal. Tanpa NO_COLOR, program menulis byte-byte tersebut ke stdout dan terminal pengguna menafsirkannya sebagai perintah mewarnai:

bash
env -u NO_COLOR go run tabel-demo.go

Output yang Dihasilkan:

text
NAMA           STOK STATUS
kopi             12 laku
teh melati        3 hampir habis
SERVER      PROSES    STATUS
web-1       http      [OK]
db-primary  postgres  [OK]
cache       redis     [GAGAL]
[OK] konfigurasi valid
[GAGAL] koneksi database terputus
repr ok    : "\x1b[32m[OK]\x1b[0m"
repr gagal : "\x1b[31m[GAGAL]\x1b[0m"
memproses [####                ]  20%
memproses [########            ]  40%
memproses [############        ]  60%
memproses [################    ]  80%
memproses [####################] 100%

Baris [OK] konfigurasi valid pada blok di atas sebenarnya diawali byte \x1b[32m dan diakhiri \x1b[0m, tetapi byte escape tidak terlihat pada teks biasa. Dua baris repr di akhir blok itulah buktinya: %q mengubah byte escape menjadi teks \x1b[32m sehingga kita bisa memeriksa struktur warna tanpa terminal yang mendukung.

Prefix Status dan Progress Bar #

Prefix status memberi pemindaian cepat: baris yang diawali [OK], [GAGAL], atau [INFO] dapat dinilai tanpa membaca isinya. Teks dalam kurung siku dipilih dibanding simbol unicode seperti ✓ dan ✗ karena simbol tersebut tidak selalu tersedia pada font terminal lawas. Warna memperkuat prefix, tetapi jangan menjadikan warna satu-satunya pembawa makna — pembaca buta warna dan pipeline tanpa TTY sama-sama kehilangan informasi tersebut.

Progress bar pada program di atas mencetak satu baris penuh per tahap. CLI sungguhan memakai carriage return \r di awal baris agar setiap tahap menimpa baris sebelumnya di tempat yang sama, ditambah os.Stdout.Sync() bila buffer perlu dibilas. Pola %3d%% menjaga kolom persen tetap rata kanan sehingga animasi tidak bergeser.

Output yang rapi dan berstatus jelas kini sampai ke layar; satu pertanyaan terakhir bagi pemanggil program kita adalah apakah proses berhasil atau gagal. Jawaban standar Unix dikirim lewat exit code, yang menjadi topik chapter berikutnya.

Referensi #

Berkas Sumber

Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.

tabel-demo.go

Unduh berkas
go
package main

import (
	"fmt"
	"os"
	"strings"
	"text/tabwriter"
)

const (
	hijau = "\x1b[32m"
	merah = "\x1b[31m"
	reset = "\x1b[0m"
)

func main() {
	// 1. Kolom berlebar tetap dengan fmt.Printf.
	fmt.Printf("%-12s %6s %s\n", "NAMA", "STOK", "STATUS")
	fmt.Printf("%-12s %6d %s\n", "kopi", 12, "laku")
	fmt.Printf("%-12s %6d %s\n", "teh melati", 3, "hampir habis")

	// 2. Tabel rapi dengan text/tabwriter.
	w := tabwriter.NewWriter(os.Stdout, 0, 4, 2, ' ', 0)
	fmt.Fprintln(w, "SERVER\tPROSES\tSTATUS")
	fmt.Fprintln(w, "web-1\thttp\t[OK]")
	fmt.Fprintln(w, "db-primary\tpostgres\t[OK]")
	fmt.Fprintln(w, "cache\tredis\t[GAGAL]")
	w.Flush()

	// 3. Prefix status dengan warna (konvensi NO_COLOR dihormati).
	if os.Getenv("NO_COLOR") == "" {
		fmt.Printf("%s[OK]%s konfigurasi valid\n", hijau, reset)
		fmt.Printf("%s[GAGAL]%s koneksi database terputus\n", merah, reset)
	} else {
		fmt.Println("[OK] konfigurasi valid")
		fmt.Println("[GAGAL] koneksi database terputus")
	}

	// 4. Representasi mentah escape sequence dengan %q.
	fmt.Printf("repr ok    : %q\n", hijau+"[OK]"+reset)
	fmt.Printf("repr gagal : %q\n", merah+"[GAGAL]"+reset)

	// 5. Progress bar sederhana dengan karakter #.
	total := 5
	for i := 1; i <= total; i++ {
		persen := i * 100 / total
		penuh := i * 20 / total
		fmt.Printf("memproses [%-20s] %3d%%\n", strings.Repeat("#", penuh), persen)
	}
}