BAB 43: Exec — Menjalankan Command Eksternal

Pelajari cara menjalankan perintah command line dari dalam program Go menggunakan package os/exec, termasuk penanganan output, error, dan kompatibilitas lintas sistem operasi.

Di bab sebelumnya program Go belajar menerima input dari luar melalui flag dan argumen. Tapi komunikasi antara program dan lingkungannya tidak hanya satu arah. Tool yang benar-benar berguna — skrip deploy, pipeline otomasi, wrapper CLI — sering perlu menjalankan program lain sebagai bagian dari tugasnya: memanggil git, menjalankan skrip shell, mengeksekusi binary eksternal, dan membaca hasilnya.

Go menyediakan package os/exec untuk kebutuhan ini. Dengan satu fungsi utama, exec.Command(), kamu bisa menjalankan perintah apa pun yang tersedia di sistem operasi dan mengolah outputnya langsung di dalam kode Go.

Menjalankan Command dan Membaca Output

exec.Command() menerima nama command dan argumen-argumennya sebagai parameter terpisah. Panggil .Output() untuk menjalankannya dan mendapatkan hasilnya sebagai []byte.

// runner.go
package main

import (
    "fmt"
    "os/exec"
)

func main() {
    // perintah tanpa argumen
    out, err := exec.Command("date").Output()
    if err != nil {
        fmt.Println("error:", err)
        return
    }
    fmt.Printf("tanggal sekarang:\n%s\n", string(out))

    // perintah dengan argumen
    out2, err := exec.Command("echo", "halo dari Go").Output()
    if err != nil {
        fmt.Println("error:", err)
        return
    }
    fmt.Printf("output echo:\n%s\n", string(out2))
}
tanggal sekarang:
Fri Mar 13 10:23:45 WIB 2026

output echo:
halo dari Go

Argumen perintah ditulis terpisah — bukan satu string panjang. exec.Command("git", "config", "user.name") bukan exec.Command("git config user.name"). Ini penting: parameter pertama adalah nama binary, parameter berikutnya adalah setiap argumen secara individual.

Output selalu berupa []byte. Konversi ke string dengan string(out) untuk membaca isinya, atau gunakan bytes.TrimSpace() jika ingin menghapus newline di akhir.

Penanganan Error

.Output() mengembalikan error jika command tidak ditemukan, keluar dengan kode non-zero, atau terjadi masalah I/O. Cara yang tepat adalah memeriksa error dan menampilkan pesan yang informatif.

// runner.go
package main

import (
    "fmt"
    "os/exec"
)

func jalankan(perintah string, args ...string) (string, error) {
    out, err := exec.Command(perintah, args...).Output()
    if err != nil {
        return "", fmt.Errorf("gagal menjalankan %q: %w", perintah, err)
    }
    return string(out), nil
}

func main() {
    // command yang berhasil
    hasil, err := jalankan("echo", "status: OK")
    if err != nil {
        fmt.Println(err)
    } else {
        fmt.Print(hasil)
    }

    // command yang tidak ada
    _, err = jalankan("perintah-tidak-ada")
    if err != nil {
        fmt.Println(err)
    }
}
status: OK
gagal menjalankan "perintah-tidak-ada": exec: "perintah-tidak-ada": executable file not found in $PATH

Membungkus logika eksekusi dalam fungsi helper seperti jalankan() membuat kode lebih bersih dan error lebih mudah ditelusuri.

Kompatibilitas Lintas Sistem Operasi

Beberapa command bawaan shell (seperti ls, dir, echo versi shell) tidak tersedia sebagai binary mandiri di semua sistem operasi. Di Windows, banyak command hanya tersedia melalui cmd.exe; di Linux/macOS, melalui bash atau sh. Untuk menangani ini, gunakan runtime.GOOS untuk mendeteksi OS dan menyesuaikan cara pemanggilan.

// runner.go
package main

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

func jalankanShell(perintah string) (string, error) {
    var cmd *exec.Cmd

    switch runtime.GOOS {
    case "windows":
        cmd = exec.Command("cmd", "/C", perintah)
    default:
        cmd = exec.Command("bash", "-c", perintah)
    }

    out, err := cmd.Output()
    if err != nil {
        return "", fmt.Errorf("error pada OS %s: %w", runtime.GOOS, err)
    }
    return strings.TrimSpace(string(out)), nil
}

func main() {
    fmt.Println("sistem operasi:", runtime.GOOS)

    hasil, err := jalankanShell("echo halo dari shell")
    if err != nil {
        fmt.Println(err)
        return
    }
    fmt.Println("output:", hasil)
}
sistem operasi: linux
output: halo dari shell

runtime.GOOS mengembalikan string seperti "linux", "darwin" (macOS), atau "windows". Dengan bash -c atau cmd /C, kamu bisa menjalankan ekspresi shell lengkap termasuk pipe (|), redirect (>), dan command bawaan shell.

Jangan pernah menyisipkan input dari pengguna langsung ke string perintah yang diteruskan ke bash -c atau cmd /C. Ini membuka celah untuk command injection. Jika input pengguna perlu digunakan sebagai argumen, gunakan exec.Command("binary", arg1, arg2) dengan argumen terpisah — bukan satu string gabungan.

Studi Kasus: Tool Status Sistem

Menggabungkan flag dari bab sebelumnya dengan eksekusi command untuk membangun tool sederhana yang mengumpulkan informasi sistem.

// runner.go
package main

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

func jalankanShell(perintah string) string {
    var cmd *exec.Cmd
    if runtime.GOOS == "windows" {
        cmd = exec.Command("cmd", "/C", perintah)
    } else {
        cmd = exec.Command("bash", "-c", perintah)
    }
    out, err := cmd.Output()
    if err != nil {
        return fmt.Sprintf("[error: %v]", err)
    }
    return strings.TrimSpace(string(out))
}

func main() {
    tampilGit := flag.Bool("git", false, "tampilkan info git")
    tampilDisk := flag.Bool("disk", false, "tampilkan penggunaan disk")
    flag.Parse()

    fmt.Println("=== Status Sistem ===")
    fmt.Println("OS       :", runtime.GOOS)
    fmt.Println("Hostname :", jalankanShell("hostname"))

    if runtime.GOOS != "windows" {
        fmt.Println("Uptime   :", jalankanShell("uptime -p"))
    }

    if *tampilGit {
        fmt.Println()
        fmt.Println("=== Info Git ===")
        fmt.Println("User     :", jalankanShell("git config user.name"))
        fmt.Println("Email    :", jalankanShell("git config user.email"))
        fmt.Println("Branch   :", jalankanShell("git branch --show-current"))
    }

    if *tampilDisk && runtime.GOOS != "windows" {
        fmt.Println()
        fmt.Println("=== Penggunaan Disk ===")
        fmt.Println(jalankanShell("df -h /"))
    }
}
go run runner.go -git -disk
=== Status Sistem ===
OS       : linux
Hostname : dev-server-01
Uptime   : up 3 days, 14 hours, 22 minutes

=== Info Git ===
User     : Reza Operator
Email    : reza@ops.internal
Branch   : main

=== Penggunaan Disk ===
Filesystem      Size  Used Avail Use% Mounted on
/dev/sda1        50G   18G   30G  38% /

Program ini menggunakan flag dari Bab 42 untuk mengontrol bagian mana yang ditampilkan, dan os/exec untuk mengumpulkan data aktual dari sistem.

Untuk command yang outputnya besar atau butuh streaming secara real-time (bukan dikumpulkan semua dulu), gunakan cmd.Stdout = os.Stdout sebelum memanggil cmd.Run(). Ini langsung mengarahkan output command ke stdout program Go tanpa buffering.

Latihan

Latihan 1 — Wrapper git: Buat program yang menerima flag -repo (path ke direktori, default .) dan menampilkan: nama branch aktif, commit terakhir (hash pendek dan pesannya), serta jumlah file yang berubah. Semua informasi dikumpulkan dengan menjalankan command git yang sesuai.

Latihan 2 — Checksum file: Buat program yang menerima argumen posisional berupa path file dan menjalankan sha256sum (Linux/macOS) atau certutil -hashfile (Windows) untuk menghitung checksum-nya. Gunakan runtime.GOOS untuk memilih command yang tepat.

Latihan 3 — Pipeline command: Gunakan jalankanShell() untuk menjalankan pipeline seperti ls -la | grep ".go" | wc -l dan cetak hasilnya. Bandingkan dengan cara melakukan hal yang sama murni menggunakan Go (tanpa exec). Kapan masing-masing pendekatan lebih masuk akal?


Dengan os/exec, program Go bisa bertindak sebagai orkestrator — memanggil tool lain, mengumpulkan outputnya, dan mengambil keputusan berdasarkan hasilnya. Ini adalah pola yang digunakan oleh sebagian besar tool DevOps dan skrip otomasi: bukan menulis ulang semua fungsionalitas dari nol, tapi mengkombinasikan program-program yang sudah ada dengan logika Go yang bersih dan terstruktur.

Referensi

  1. 1Package os/exec — Go Standard Library Documentation
  2. 2runtime.GOOS — Go Standard Library Documentation
  3. 3Spawning Processes — Go by Example