Есть ли недостатки в использовании вызовов system () вместо функций вашего языка программирования?


Я программирую на C, чтобы создать некоторый API для встроенного устройства. Это встроенное устройство работает в варианте Linux. Я не очень хорошо знаком с C-я больше знаком с Shell scripting/bash.

Имея это в виду, когда речь заходит о таких вещах, как проверка, существует ли каталог, или получение использования диска, мне проще просто сделать вызов system или popen и выполнить мою команду, а затем проанализировать выходные данные. Это быстрее для меня как разработчика.

Есть ли недостатки в создании этих system и popen вызовы вместо того, чтобы выяснить, как сделать каждую из этих вещей в C, а затем использовать функции C?

4 5

4 ответа:

В конце концов, в системе Linux почти все вызывает функции C в какой-то момент.

Чтобы коснуться некоторых аргументов в комментариях, а также моих собственных мыслей:

  1. выполнение команд оболочки из программы на языке Си, особенно при использовании пользовательских аргументов, является потенциальной уязвимостью. Например, если вы позволите пользователю вызывать вашу программу с аргументом " foo; rm-rf *"; в зависимости от того, как вы вызываете оболочку, есть потенциал, который вы можете эффективно вызовите " mkdir foo; rm-rf *", если вы хотите сделать каталог предоставленным пользователем. Это может быть или не быть большой проблемой в зависимости от того, насколько Вы доверяете своим пользователям и т. д.
  2. выполнение команд оболочки приводит к потенциальным условиям гонки, которые вы не можете избежать, с которыми было бы легче справиться с помощью прямых системных вызовов, которые вы связываете вместе
  3. синтаксический анализ выходных данных команд означает, что мы имеем дело с большим количеством строковых операций в языке Си, что не очень весело.
  4. Если вы действительно предпочитаете bash, ваш лучший выбор-это, вероятно, реализовать небольшие программы, которые упаковывают дискретные части C API вашей целевой библиотеки. Это путь UNIX(tm) в любом случае.

Отредактируйте комментарий Pimgd: обратите внимание, что условия гонки-это боль в шее, и многие из них не обязательно являются проблемой для вашего конкретного случая использования, но, по крайней мере, стоит учитывать при взвешивании pro/con. классы):

  1. условия гонки типа безопасности. Если вы создадите временный файл, содержащий набор команд, а затем выполните его, существует вероятность, что между созданием и выполнением кто-то войдет и изменит файл. (На самом деле существует множество вариаций на эту тему для таких вещей, как установка/сброс ссылок sym и т. д.). Основываясь на ваших комментариях выше, это в значительной степени выходит за рамки вашего проекта, но это то, что нужно знать для других приложения.
  2. Условия многопроцессной гонки. Самый простой пример, который я могу придумать, - это то, что происходит, если два экземпляра вашей программы хотят установить файл конфигурации и работают одновременно. Есть определенные вызовы ОС, которые имеют определенные уровни гарантии атомарности, но вы теряете много этого, если вызываете серию команд оболочки для записи в файл. (обратите внимание, что даже если вы делаете это из монолитного приложения C по сравнению с серией команд оболочки, вы все равно будете нужно сделать что-то дополнительное, чтобы предотвратить перезапись изменений экземпляра 1 в экземпляре 2, но вы, по крайней мере, не столкнетесь с тем, что в конечном итоге изменения будут перемешаны. В качестве примера рассмотрим:

    FILE *fp = fopen ("config.txt", "wt");

    Fprintf(fp, "Config value 1: %s", config[0]);

    Fprintf(fp, "Config value 2: %s", config[1]);

    Fflush(fp);

    Fclose(fp);

Против

system("echo Config value 1: `df | awk '{print $1}'` > config.txt");
system("echo Config value 2: `ps | grep foo | awk '{print $2}'` >> config.txt");

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

Минусы:

  1. это небезопасно, если есть пользовательский ввод
  2. трудно разобрать выходные данные
  3. он будет полагаться на что-то внешнее, например, сценарий оболочки.
  4. тяжелый процесс-система fork () и запуск другой программы параллельно

Плюсы:

  1. легко реализовать
  2. зависит от вывода программы Разбор может быть очень простым. пример system("ls -1");

Суммировать -

Это зависит от того, что вам нужно сделать, но в общем случае там больше негатива, чем позитива.

Даже если он будет работать в Linux, вы не можете полагаться на доступность всех команд, которые вы собираетесь использовать, поэтому обратная сторона проста: Вы не можете полагаться на доступность данного инструмента.

Еще одна вещь, которую следует учитывать, - это то, что разбор выходных команд оболочки из c ужасно труден.

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

Может быть, это правильный подход для вас, более знакомых со скриптами оболочки, чтобы сделать это следующим образом:

Используйте в качестве базы сценарий оболочки, который выполняет все вычисления высокого уровня. Этот сценарий может использовать одну (или несколько) команд для обращения к API; только для предоставления этих команд, напишите небольшие программы на языке Си.

Например:

#!/bin/bash
valueA=$(APIhelper -q valueA)
valueB=$(APIhelper -q valueB)

process() { … }

result=$(process "$valueA" "$valueB")

APIhelper -s result "$result"

Эта часть должна быть понятна, если вы понимаете скрипты оболочки.

Теперь вам нужно только написать двоичный файл APIhelper (Пожалуйста, выберите более подходящее имя исходя из вашего контекста ; -).

Это может быть такая программа на языке Си:

int main(int argc, char *argv[]) {
  if (argc > 1) {
    if (strstr(argv[1], "-q") == 0) {
      if (argc > 2) {
        char *variableName = argv[2];
        someType result = makeYourApiCall(variableName);
        printf("%O\n", result);  # use a fitting format instead of %O!
      } else {
        fprintf(stderr, "-q without variable name\n");
        exit(1);
      }
    else if (strstr(argv[1], "-s") == 0) {
      …
    } else {
      fprintf(stderr, "option not understood: %s\n", argv[1]);
      exit(1);
    }
  } else {
    fprintf(stderr, "missing option\n");
    exit(1);
  }
  return 0;
}

Таким образом, только самая основная обработка API выполняется в программе C, и вы можете придерживаться использования того, что вы можете кодировать лучше всего в: скриптах. Кроме того, таким образом вы можете легко отладить свой API, потому что этот инструмент APIhelper может быть вызван из командной строки в интерактивном режиме.