Создание процессов в go

Всем привет!

Всеми нами любимый docker является абстракцией над операционной системой linux, kubernetes является абстракцией над docker, а openshift — это высокоуровневый дистрибутив kubernetes удобный для пользователя.

Технологии сейчас строятся на фундаменте из открытых программ, а самый очевидный путь их использования — создание подпроцессов в ваших собственных программах.

Все современные языки программирования предоставляют различные интерфейсы для данных операций, на мой взгляд одна из самых удачных реализаций представлена в языке go.

Для примера — в установщике openshift installer, данный интерфейс используется 450 раз. Стоит разобрать эту конструкцию, так как она позволяет сильно упростить многие процедуры за счет переиспользования существующих утилит, что бы не «создавать велосипеды» на ровном месте.

В данной статье я расскажу о том, как совершать системные вызовы в языке go, что бы получить весь функционал утилит линукса в своих программах.

Для примера я сделал библиотеку, предоставляющую интерфейс к пакетному менеджеру arch linux — pacman.

Проверка зависимостей

Перед тем как использовать некоторую системную утилиту необходимо проверить её наличие в среде, в которой приложение будет производить системный вызов, сделать это можно с помощью команды exec.LookPath() , данная функция возвращает путь к необходимой утилите и ошибку если программа не найдена.

Эту функцию лучше вызывать на этапе инициализации или в начале main, что бы избежать непредвиденных ошибок в рантайме.

package main  import ( "fmt" "os" "os/exec" "sync" )  // Dependecy packages. const ( pacman  = `pacman` )  func init() { _, err := exec.LookPath(pacman) if err != nil { fmt.Println("unable to find pacman in system") os.Exit(1) } }

Параметры вызова

Структура exec.Cmd предоставляет большое количество параметров, что бы лучше с ними познакомиться лучше смотреть исходники. Так как статья имеет ознакомительных характер я пройдусь только по основным:

  • Path — это путь к вызываемой программе

  • Args — аргументы используемые при вызове

  • Env — переменные окружения

  • Dir — директория в которой будет исполняться программа

  • Stdin — пользовательский ввод (аналогичным образом с терминалом)

  • Stdout, Stderr — потоки стандартного вывода и ошибок программы

Вот пример вызова знакомой нам утилиты эхо:

package main  import ( "fmt" "os" "os/exec" )  func main() { cmd := exec.Command("echo", "hello exec!") cmd.Stdin = os.Stdin cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr err := cmd.Run() fmt.Println(err) } 

Данный код создаст вызов к программе эхо, перенаправит потоки вывода на стандартные утсановленные в системе и запустит процесс.

Если мы хотим прочитать вывод программы и использовать его после, то мы можем воспользоваться буфером из библиотеки bytes и записать вывод программы в него.

package main  import ( "bytes" "fmt" "os/exec" )  func main() { cmd := exec.Command("echo", "hello exec!") var b bytes.Buffer cmd.Stdout = &b cmd.Stderr = &b cmd.Run() fmt.Println(b.String()) } 

Так же есть возможность перенаправить вывод программы сразу в несколько различных потоков, сделать это можно используя io.Multiwriter(), вот пример вывода одновременно в stdout и буфер в памяти:

package main  import ( "bytes" "fmt" "io" "os" "os/exec" )  func main() { cmd := exec.Command("echo", "hello exec!") var b bytes.Buffer cmd.Stdout = io.MultiWriter(&b, os.Stdout) cmd.Run() fmt.Println(b.String()) } 

Заключение

Интерфейсы для совершения системных вызовов есть во всех современных языках и позволяют переиспользовать функционал существующих утилит. Данной возможностью стоит пользоваться так как это позволяет экономить существенное количество времени. Наличие данных интерфейсов стоит учитывать как при написании серверного ПО, так и при создании клиентских приложений, например на electron или flutter.

Спасибо!


ссылка на оригинал статьи https://habr.com/ru/articles/737660/

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *