Описание функций и типов пакета os/exec языка программирования Go
В отличие от вызова библиотеки «system» из C и других языков, пакет os/exec намеренно не вызывает системную оболочку и не расширяет никакие шаблоны glob, а также не обрабатывает другие расширения, конвейеры или перенаправления, которые обычно выполняются оболочками. Пакет ведет себя больше как семейство функций «exec» в C. Чтобы расширить шаблоны glob, либо вызовите оболочку напрямую, позаботившись об экранировании опасных входных данных, либо используйте функцию Glob из пакета path/filepath. Для расширения переменных окружения используйте ExpandEnv из пакета os.
Обратите внимание, что примеры в этом пакете предполагают использование системы Unix. Они могут не работать в Windows и не работают в Go Playground, используемом golang.org и godoc.org.
Исполняемые файлы в текущем каталоге
Функции Command
и LookPath
ищут программу в каталогах, перечисленных в текущем пути, следуя соглашениям операционной системы хоста. На протяжении десятилетий операционные системы включали текущий каталог в этот поиск, иногда неявно, а иногда явно настраивая его таким образом по умолчанию. Современная практика такова, что включение текущего каталога обычно является неожиданным и часто приводит к проблемам безопасности.
Чтобы избежать этих проблем безопасности, начиная с Go 1.19, этот пакет не будет разрешать программу, используя явную или неявную запись пути относительно текущего каталога. То есть, если вы запустите LookPath(“go”), он не вернет ./go в Unix и .\go.exe в Windows, независимо от того, как настроен путь. Вместо этого, если обычные алгоритмы пути приведут к такому ответу, эти функции возвращают ошибку err, удовлетворяющую errors.Is(err, ErrDot).
Рассмотрим, например, эти два фрагмента программы:
path, err := exec.LookPath("prog")
if err != nil {
log.Fatal(err)
}
use(path)
cmd := exec.Command("prog")
if err := cmd.Run(); err != nil {
log.Fatal(err)
}
Они не найдут и не запустят ./prog или .\prog.exe, независимо от того, как настроен текущий путь.
Код, который всегда хочет запускать программу из текущего каталога, можно переписать так, чтобы вместо “prog” он говорил “./prog”.
Код, который настаивает на включении результатов из записей относительных путей, может вместо этого отменить ошибку с помощью проверки errors.Is:
path, err := exec.LookPath("prog")
if errors.Is(err, exec.ErrDot) {
err = nil
}
if err != nil {
log.Fatal(err)
}
use(path)
cmd := exec.Command("prog")
if errors.Is(cmd.Err, exec.ErrDot) {
cmd.Err = nil
}
if err := cmd.Run(); err != nil {
log.Fatal(err)
}
Установка переменной окружения GODEBUG=execerrdot=0 полностью отключает генерацию ErrDot, временно восстанавливая поведение, существовавшее до Go 1.19, для программ, которые не могут применить более целевые исправления. В будущих версиях Go поддержка этой переменной может быть удалена.
Прежде чем добавлять такие переопределения, убедитесь, что вы понимаете последствия этого для безопасности. Дополнительные сведения см. на сайте https://go.dev/blog/path-security.
Переменные
var ErrDot = errors.New("cannot run executable found relative to current directory")
ErrDot указывает, что поиск пути привел к появлению исполняемого файла в текущем каталоге из-за ‘.’ в пути, либо неявно, либо явно. Подробности см. в документации к пакету.
Обратите внимание, что функции этого пакета не возвращают ErrDot напрямую. Для проверки того, связана ли возвращаемая ошибка err с этим условием, в коде следует использовать errors.Is(err, ErrDot), а не err == ErrDot.
var ErrNotFound = errors.New("исполняемый файл не найден в $PATH")
ErrNotFound - это ошибка, возникающая, если при поиске по пути не удалось найти исполняемый файл.
var ErrWaitDelay = errors.New("exec: WaitDelay expired before I/O complete")
ErrWaitDelay возвращается Cmd.Wait, если процесс завершается с успешным кодом состояния, но его выходные трубы не закрыты до истечения WaitDelay команды.
Функции
func LookPath
func LookPath(file string) (string, error)
LookPath ищет исполняемый файл с именем file в каталогах, указанных переменной окружения PATH. Если файл содержит косую черту, то поиск ведется напрямую и PATH не используется. В противном случае, при успехе, результатом будет абсолютный путь.
В старых версиях Go LookPath мог возвращать путь относительно текущего каталога. Начиная с Go 1.19, LookPath будет возвращать этот путь вместе с ошибкой, удовлетворяющей errors.Is(err, ErrDot). Более подробную информацию см. в документации к пакету.
Пример
package main
import (
"fmt"
"log"
"os/exec"
)
func main() {
path, err := exec.LookPath("fortune")
if err != nil {
log.Fatal("installing fortune is in your future")
}
fmt.Printf("fortune is available at %s\n", path)
}
Типы
type Cmd
type Cmd struct {
// Path — путь к выполняемой команде.
//
// Это единственное поле, которое должно иметь значение, отличное от нуля.
// Если Path является относительным, оно оценивается относительно
// Dir.
Path string
// Args содержит аргументы командной строки, включая команду как Args[0].
// Если поле Args пустое или равно nil, Run использует {Path}.
//
// В типичном случае Path и Args задаются вызовом Command.
Args []string
// Env задает среду процесса.
// Каждая запись имеет вид «ключ=значение».
// Если Env равно nil, новый процесс использует среду текущего процесса.
//
// Если Env содержит дубликаты ключей среды, используется только последнее
// значение в срезе для каждого дубликата ключа.
// В качестве особого случая в Windows SYSTEMROOT всегда добавляется, если
// отсутствует и явно не установлен в пустую строку.
//
// См. также поле Dir, которое может устанавливать PWD в среде.
Env []string
// Dir указывает рабочий каталог команды.
// Если Dir является пустой строкой, Run запускает команду в
// текущем каталоге вызывающего процесса.
//
// В системах Unix значение Dir также определяет
// переменную окружения PWD дочернего процесса, если не указано иное
//. Процесс Unix представляет свой рабочий каталог
// не по имени, а как неявную ссылку на узел в
// дереве файлов. Таким образом, если дочерний процесс получает свой рабочий
// каталог путем вызова функции, такой как getcwd в C, которая
// вычисляет каноническое имя, проходя по дереву файлов, он
// не восстановит исходное значение Dir, если это значение
// было псевдонимом, включающим символьные ссылки. Однако, если
// дочерний процесс вызывает [os.Getwd] в Go или
// get_current_dir_name из GNU C, и значение PWD является псевдонимом для
// текущего каталога, эти функции вернут
// значение PWD, которое совпадает со значением Dir.
Dir string
// Stdin указывает стандартный ввод процесса.
//
// Если Stdin равно nil, процесс читает из нулевого устройства (os.DevNull).
//
// Если Stdin является *os.File, стандартный ввод процесса подключается
// непосредственно к этому файлу.
//
// В противном случае во время выполнения команды отдельная
// goroutine считывает данные из Stdin и передает их команде
// через канал. В этом случае Wait не завершается, пока goroutine
// не прекратит копирование, либо потому что достиг конца Stdin
// (EOF или ошибка чтения), либо потому, что запись в канал вернула ошибку,
// либо потому, что было установлено ненулевое значение WaitDelay, которое истекло.
Stdin io.Reader
// Stdout и Stderr указывают стандартный вывод и ошибки процесса.
//
// Если любой из них равен nil, Run подключает соответствующий файловый дескриптор
// к нулевому устройству (os.DevNull).
//
// Если любой из них является *os.File, соответствующий вывод из процесса
// подключается непосредственно к этому файлу.
//
// В противном случае во время выполнения команды отдельная goroutine
// считывает данные из процесса через канал и доставляет их в
// соответствующий Writer. В этом случае Wait не завершается, пока
// goroutine не достигнет EOF, не столкнется с ошибкой или не истечет ненулевое значение WaitDelay
//.
//
// Если Stdout и Stderr являются одним и тем же writer и имеют тип, который можно
// сравнить с помощью ==, то одновременно Write будет вызывать не более одного goroutine.
Stdout io.Writer
Stderr io.Writer
// ExtraFiles указывает дополнительные открытые файлы, которые будут унаследованы
// новый процесс. Он не включает стандартный ввод, стандартный вывод или
// стандартную ошибку. Если не равен nil, запись i становится файловым дескриптором 3+i.
//
// ExtraFiles не поддерживается в Windows.
ExtraFiles []*os.File
// SysProcAttr содержит дополнительные атрибуты, специфичные для операционной системы.
// Run передает его os.StartProcess как поле Sys os.ProcAttr.
SysProcAttr *syscall.SysProcAttr
// Process — это базовый процесс после запуска.
Process *os.Process
// ProcessState содержит информацию о завершенном процессе.
// Если процесс был запущен успешно, Wait или Run
// заполнят его ProcessState по завершении команды.
ProcessState *os.ProcessState
Err error // Ошибка LookPath, если есть.
// Если Cancel не равен nil, команда должна быть создана с помощью
// CommandContext, и Cancel будет вызван, когда
// Context команды будет выполнен. По умолчанию CommandContext устанавливает Cancel в
// вызывает метод Kill в процессе команды.
//
// Обычно пользовательский Cancel посылает сигнал процессу команды
//, но вместо этого он может предпринять другие действия для инициирования отмены,
// такие как закрытие канала stdin или stdout или отправка запроса на завершение работы
// сетевого сокета.
//
// Если команда завершается с успешным статусом после вызова Cancel
//, а Cancel не возвращает ошибку, эквивалентную
// os.ErrProcessDone, то Wait и подобные методы будут возвращать не нулевую
// ошибку: либо ошибку, оборачивающую ошибку, возвращенную Cancel,
// либо ошибку из Context.
// (Если команда завершается с неуспешным статусом или Cancel
// возвращает ошибку, которая оборачивает os.ErrProcessDone, Wait и подобные методы
// продолжают возвращать обычный статус завершения команды.)
//
// Если Cancel установлен в nil, ничего не произойдет сразу после завершения
// Context команды, но WaitDelay, отличное от нуля, все равно будет действовать. Это может
// быть полезно, например, для обхода тупиковых ситуаций в командах, которые не
// поддерживают сигналы завершения, но должны всегда завершаться быстро.
//
// Cancel не будет вызван, если Start возвращает ошибку, отличную от nil.
Cancel func() error
// Если WaitDelay отлично от нуля, оно ограничивает время ожидания двух источников
// непредвиденной задержки в Wait: дочернего процесса, который не завершается после
// отмены связанного Context, и дочернего процесса, который завершается, но оставляет
// свои каналы ввода-вывода незакрытыми.
//
// Таймер WaitDelay запускается, когда связанный контекст завершается или
// вызов Wait обнаруживает, что дочерний процесс завершился, в зависимости от того, что произойдет
// первым. По истечении задержки команда завершает дочерний процесс
// и/или его каналы ввода-вывода.
//
// Если дочерний процесс не завершился — возможно, потому что он проигнорировал или
// не получил сигнал о завершении от функции Cancel, или потому что не была
// установлена функция Cancel — то он будет завершен с помощью os.Process.Kill.
//
// Затем, если каналы ввода-вывода, связывающиеся с дочерним процессом, все еще открыты,
// эти каналы закрываются, чтобы разблокировать все goroutines, которые в данный момент заблокированы
// вызовами Read или Write.
//
// Если трубы закрыты из-за WaitDelay, вызов Cancel не произошел,
// и команда завершилась с успешным статусом, Wait и
// подобные методы вернут ErrWaitDelay вместо nil.
//
// Если WaitDelay равен нулю (по умолчанию), трубы ввода-вывода будут читаться до EOF,
// что может не произойти, пока осиротевшие подпроцессы команды
// также не закроют свои дескрипторы для каналов.
WaitDelay time.Duration
// содержит отфильтрованные или неэкспортированные поля
}
Cmd представляет внешнюю команду, которая готовится или запускается.
Cmd не может быть повторно использован после вызова методов Cmd.Run, Cmd.Output или Cmd.CombinedOutput.
func Command
func Command(name string, arg ...string) *Cmd
Command возвращает структуру Cmd для выполнения именованной программы с заданными аргументами.
В возвращаемой структуре задаются только Path и Args.
Если имя не содержит разделителей путей, Command использует LookPath для преобразования имени в полный путь, если это возможно. В противном случае она использует имя непосредственно в качестве Path.
Возвращаемое поле Args Cmd строится из имени команды, за которым следуют элементы arg, поэтому arg не должно включать само имя команды. Например, Command(“echo”, “hello”). Args[0] - это всегда имя, а не возможно разрешенный Path.
В Windows процессы получают всю командную строку как единую строку и выполняют собственный разбор. Command объединяет и заключает Args в кавычки в строку командной строки с помощью алгоритма, совместимого с приложениями, использующими CommandLineToArgvW (это наиболее распространенный способ). Заметными исключениями являются msiexec.exe и cmd.exe (и, соответственно, все пакетные файлы), которые имеют другой алгоритм снятия кавычек. В этих и других подобных случаях вы можете выполнить котирование самостоятельно и указать полную командную строку в SysProcAttr.CmdLine, оставив Args пустым.
Пример
package main
import (
"fmt"
"log"
"os/exec"
"strings"
)
func main() {
cmd := exec.Command("tr", "a-z", "A-Z")
cmd.Stdin = strings.NewReader("some input")
var out strings.Builder
cmd.Stdout = &out
err := cmd.Run()
if err != nil {
log.Fatal(err)
}
fmt.Printf("in all caps: %q\n", out.String())
}
Пример с окружением среды
package main
import (
"log"
"os"
"os/exec"
)
func main() {
cmd := exec.Command("prog")
cmd.Env = append(os.Environ(),
"FOO=duplicate_value", // ignored
"FOO=actual_value", // this value is used
)
if err := cmd.Run(); err != nil {
log.Fatal(err)
}
}
func CommandContext
func CommandContext(ctx context.Context, name string, arg ...string) *Cmd
CommandContext похож на Command, но включает в себя контекст.
Предоставленный контекст используется для прерывания процесса (путем вызова cmd.Cancel или os.Process.Kill), если контекст завершается до того, как команда завершит свою работу.
CommandContext устанавливает функцию Cancel команды для вызова метода Kill в ее Process и оставляет WaitDelay не установленным. Вызывающая сторона может изменить поведение отмены, изменив эти поля перед запуском команды.
Пример
package main
import (
"context"
"os/exec"
"time"
)
func main() {
ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
defer cancel()
if err := exec.CommandContext(ctx, "sleep", "5").Run(); err != nil {
// This will fail after 100 milliseconds. The 5 second sleep
// will be interrupted.
}
}
func (*Cmd) CombinedOutput
func (c *Cmd) CombinedOutput() ([]byte, error)
CombinedOutput запускает команду и возвращает ее объединенный стандартный вывод и стандартную ошибку.
Пример
package main
import (
"fmt"
"log"
"os/exec"
)
func main() {
cmd := exec.Command("sh", "-c", "echo stdout; echo 1>&2 stderr")
stdoutStderr, err := cmd.CombinedOutput()
if err != nil {
log.Fatal(err)
}
fmt.Printf("%s\n", stdoutStderr)
}
func (*Cmd) Environ
func (c *Cmd) Environ() []string
Environ возвращает копию среды, в которой будет запущена команда, в том виде, в котором она настроена в данный момент.
Пример
package main
import (
"fmt"
"log"
"os/exec"
)
func main() {
cmd := exec.Command("pwd")
// Set Dir before calling cmd.Environ so that it will include an
// updated PWD variable (on platforms where that is used).
cmd.Dir = ".."
cmd.Env = append(cmd.Environ(), "POSIXLY_CORRECT=1")
out, err := cmd.Output()
if err != nil {
log.Fatal(err)
}
fmt.Printf("%s\n", out)
}
func (*Cmd) Output
func (c *Cmd) Output() ([]byte, error)
Output запускает команду и возвращает ее стандартный вывод. Любая возвращаемая ошибка обычно будет иметь тип *ExitError. Если c.Stderr было nil, а возвращаемая ошибка имеет тип *ExitError, Output заполняет поле Stderr возвращаемой ошибки.
Пример
package main
import (
"fmt"
"log"
"os/exec"
)
func main() {
out, err := exec.Command("date").Output()
if err != nil {
log.Fatal(err)
}
fmt.Printf("The date is %s\n", out)
}
func (*Cmd) Run
func (c *Cmd) Run() error
Run запускает указанную команду и ждет ее завершения.
Возвращаемая ошибка равна nil, если команда запущена, не возникло проблем с копированием stdin, stdout и stderr, и она завершилась с нулевым статусом выхода.
Если команда запущена, но не завершилась успешно, ошибка будет типа *ExitError. В других ситуациях могут возвращаться ошибки других типов.
Если вызывающая goroutine заблокировала поток операционной системы с помощью runtime.LockOSThread и изменила любое наследуемое состояние потока на уровне ОС (например, пространства имен Linux или Plan 9), новый процесс унаследует состояние потока вызывающего.
Пример
package main
import (
"log"
"os/exec"
)
func main() {
cmd := exec.Command("sleep", "1")
log.Printf("Running command and waiting for it to finish...")
err := cmd.Run()
log.Printf("Command finished with error: %v", err)
}
func (*Cmd) Start
func (c *Cmd) Start() error
Start запускает указанную команду, но не ждет ее завершения.
Если Start возвращается успешно, поле c.Process будет установлено.
После успешного вызова Start необходимо вызвать метод Cmd.Wait, чтобы освободить связанные системные ресурсы.
Пример
package main
import (
"log"
"os/exec"
)
func main() {
cmd := exec.Command("sleep", "5")
err := cmd.Start()
if err != nil {
log.Fatal(err)
}
log.Printf("Waiting for command to finish...")
err = cmd.Wait()
log.Printf("Command finished with error: %v", err)
}
func (*Cmd) StderrPipe
func (c *Cmd) StderrPipe() (io.ReadCloser, error)
StderrPipe возвращает канал, который будет подключен к стандартной ошибке команды при ее запуске.
Cmd.Wait закроет канал после завершения команды, поэтому большинству вызывающих не нужно закрывать канал самостоятельно. Таким образом, неправильно вызывать Wait до завершения всех операций чтения из канала. По той же причине неправильно использовать Cmd.Run при использовании StderrPipe. Идиоматическое использование см. в примере StdoutPipe.
Пример
package main
import (
"fmt"
"io"
"log"
"os/exec"
)
func main() {
cmd := exec.Command("sh", "-c", "echo stdout; echo 1>&2 stderr")
stderr, err := cmd.StderrPipe()
if err != nil {
log.Fatal(err)
}
if err := cmd.Start(); err != nil {
log.Fatal(err)
}
slurp, _ := io.ReadAll(stderr)
fmt.Printf("%s\n", slurp)
if err := cmd.Wait(); err != nil {
log.Fatal(err)
}
}
func (*Cmd) StdinPipe
func (c *Cmd) StdinPipe() (io.WriteCloser, error)
StdinPipe возвращает канал, который будет подключен к стандартному входу команды при ее запуске. Канал будет автоматически закрыт после того, как Cmd.Wait увидит выход команды. Вызывающему достаточно вызвать Close, чтобы заставить канал закрыться раньше. Например, если запускаемая команда не завершится до закрытия стандартного ввода, вызывающий должен закрыть канал.
Пример
package main
import (
"fmt"
"io"
"log"
"os/exec"
)
func main() {
cmd := exec.Command("cat")
stdin, err := cmd.StdinPipe()
if err != nil {
log.Fatal(err)
}
go func() {
defer stdin.Close()
io.WriteString(stdin, "values written to stdin are passed to cmd's standard input")
}()
out, err := cmd.CombinedOutput()
if err != nil {
log.Fatal(err)
}
fmt.Printf("%s\n", out)
}
func (*Cmd) StdoutPipe
func (c *Cmd) StdoutPipe() (io.ReadCloser, error)
StdoutPipe возвращает канал, который будет подключен к стандартному выводу команды при ее запуске.
Cmd.Wait закроет канал после завершения команды, поэтому большинству вызывающих не нужно закрывать канал самостоятельно. Таким образом, неправильно вызывать Wait до завершения всех операций чтения из канала. По той же причине неправильно вызывать Cmd.Run при использовании StdoutPipe. См. пример для идиоматического использования.
Пример
package main
import (
"encoding/json"
"fmt"
"log"
"os/exec"
)
func main() {
cmd := exec.Command("echo", "-n", `{"Name": "Bob", "Age": 32}`)
stdout, err := cmd.StdoutPipe()
if err != nil {
log.Fatal(err)
}
if err := cmd.Start(); err != nil {
log.Fatal(err)
}
var person struct {
Name string
Age int
}
if err := json.NewDecoder(stdout).Decode(&person); err != nil {
log.Fatal(err)
}
if err := cmd.Wait(); err != nil {
log.Fatal(err)
}
fmt.Printf("%s is %d years old\n", person.Name, person.Age)
func (*Cmd) String
func (c *Cmd) String() string
String возвращает удобочитаемое описание c. Предназначено только для отладки. В частности, не подходит для использования в качестве ввода в оболочку. Вывод String может различаться в разных версиях Go.
func (*Cmd) Wait
func (c *Cmd) Wait() error
Wait ожидает завершения команды и завершения копирования в stdin или копирования из stdout или stderr.
Команда должна быть запущена с помощью Cmd.Start.
Возвращаемая ошибка равна nil, если команда запущена, не имеет проблем с копированием stdin, stdout и stderr и завершается с нулевым статусом выхода.
Если команда не запускается или не завершается успешно, ошибка имеет тип *ExitError. Другие типы ошибок могут возвращаться при проблемах с вводом-выводом.
Если c.Stdin, c.Stdout или c.Stderr не являются *os.File, Wait также ожидает завершения соответствующего цикла ввода-вывода в процесс или из процесса.
Wait освобождает все ресурсы, связанные с Cmd.
type Error
type Error struct {
// Name — имя файла, для которого произошла ошибка.
Name string
// Err — базовая ошибка.
Err error
}
Error возвращается LookPath, когда он не может классифицировать файл как исполняемый.
func (*Error) Error
func (e *Error) Error() string
func (*Error) Unwrap
func (e *Error) Unwrap() error
type ExitError
type ExitError struct {
*os.ProcessState
// Stderr содержит подмножество стандартного вывода ошибок из
// метода Cmd.Output, если стандартные ошибки не были
// собраны иным способом.
//
// Если вывод ошибок длинный, Stderr может содержать только префикс
// и суффикс вывода, а середина будет заменена
// текстом о количестве пропущенных байтов.
//
// Stderr предоставляется для отладки, для включения в сообщения об ошибках.
// Пользователи с другими потребностями должны перенаправить Cmd.Stderr по мере необходимости.
Stderr []byte
}
ExitError сообщает о неудачном завершении команды.
func (*ExitError) Error
func (e *ExitError) Error() string