exec.CommandContext în Go

Ca exec.Command, dar procesul este oprit automat când contextul expiră sau este anulat.

Sintaxă
func CommandContext(ctx context.Context, name string, arg ...string) *Cmd

Ce face exec.CommandContext

exec.CommandContext leagă comanda de un context.Context. Dacă timpul contextului expiră sau cineva îl anulează înainte ca procesul să se termine, Go oprește procesul (implicit cu Kill). Este modul corect de a pune o limită de timp unei comenzi externe.

Din Go 1.20 poți schimba felul opririi prin câmpul cmd.Cancel (de exemplu trimiți întâi os.Interrupt) și poți da un timp de grație cu cmd.WaitDelay.

Parametri

Parametru Descriere
ctx Contextul; de obicei din context.WithTimeout.
name, arg Programul și argumentele, ca la exec.Command.

Ce returnează

Un *exec.Cmd.

Exemple

Exemplul 1: Limită de timp

package main

import (
    "context"
    "fmt"
    "os/exec"
    "time"
)

func main() {
    ctx, cancel := context.WithTimeout(context.Background(), 200*time.Millisecond)
    defer cancel()

    err := exec.CommandContext(ctx, "sleep", "5").Run()
    fmt.Println("eroare:", err)
    fmt.Println("context:", ctx.Err())
}

Rezultat

eroare: signal: killed
context: context deadline exceeded

Exemplul 2: Comanda termină la timp

package main

import (
    "context"
    "fmt"
    "os/exec"
    "time"
)

func main() {
    ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
    defer cancel()

    iesire, err := exec.CommandContext(ctx, "echo", "backup terminat").Output()
    fmt.Printf("%q %v\n", iesire, err)
}

Rezultat

"backup terminat\n" <nil>

Exemplul 3: Oprire politicoasă cu Cancel și WaitDelay

La expirare trimitem SIGINT în loc de Kill; scriptul prinde semnalul, face curățenie și iese singur.

package main

import (
    "context"
    "fmt"
    "os"
    "os/exec"
    "time"
)

func main() {
    ctx, cancel := context.WithTimeout(context.Background(), 300*time.Millisecond)
    defer cancel()

    script := "trap 'echo curăț fișierele temporare; exit 1' INT; echo lucrez; sleep 5 & wait"
    cmd := exec.CommandContext(ctx, "sh", "-c", script)
    cmd.Cancel = func() error { return cmd.Process.Signal(os.Interrupt) }
    cmd.WaitDelay = 2 * time.Second

    iesire, err := cmd.Output()
    fmt.Print(string(iesire))
    fmt.Println("eroare:", err)
}

Rezultat

lucrez
curăț fișierele temporare
eroare: exit status 1

Greșeli frecvente

  • Uiți defer cancel(). Resursele contextului rămân ocupate până la expirare.
  • Pornești cu sh -c o comandă care lansează alte procese. Kill oprește doar shell-ul, nu și copiii lui; folosește WaitDelay și, la nevoie, grupuri de procese.

Comenzi înrudite

  • exec.Command: Pregătește rularea unui program extern cu argumentele date; programul pornește abia la Run, Output sau Start.
  • exec.Cmd.Run: Pornește comanda și așteaptă să se termine; returnează eroare dacă nu a pornit sau a ieșit cu cod diferit de 0.
  • signal.NotifyContext: Returnează un context care se anulează când programul primește unul dintre semnalele date (de exemplu Ctrl+C).
  • exec.Cmd.Start: Start pornește comanda fără să aștepte; Wait așteaptă terminarea ei și eliberează resursele.