Всем привет!

Всеми нами любимый 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.

Спасибо!

Комментарии (5)


  1. mentin
    25.05.2023 17:09
    +2

    Ожидал совсем другого, лучше пользоваться стандартной терминологией, где системные вызовы это https://ru.wikipedia.org/wiki/Системный_вызов. А описанное наверное можно назвать создание процессов или как-то так.


    1. uvelichitel
      25.05.2023 17:09

      Солидарен. Ожидал про пакет syscall https://pkg.go.dev/syscall


    1. dancheg Автор
      25.05.2023 17:09

      Спасибо! Поменял


  1. KrasPvP
    25.05.2023 17:09

    А что насчет перформанса? Там же используется под капотом os.StartProcess, который вызывает ForkExec, а у него, судя по issues в репо, были (есть?) проблемы.
    И тут сразу же возникает проблема с кросс-платформенностью, так как если мы вызовем условный bash, то в Windows, как минимум, его не будет.
    Лучше всего использовать библиотеки, а на крайний случай - exec.


    1. dancheg Автор
      25.05.2023 17:09
      -2

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

      Например, можно написать скрипт на питоне, использующий keras и tensorflow, и переиспользовать данную функциональность в языке go.

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