Как определить, не удалось ли оболочке выполнить команду после вызова popen? Не путать со статусом команды выхода - PullRequest
2 голосов
/ 01 февраля 2020

Недавно я начал делать некоторые тесты для моих python скриптов. И по какой-то неловкой причине модуль, который запускает скрипт python и проверяет его вывод, записан в C с добавлением некоторых других языков. Этот способ более удобен для меня на данный момент.

Одиночный тест выполняется с кодом ниже:

 FILE *fd = NULL;

 fd = popen("cmd", "r");
 if(NULL == fd){
  fprintf(stderr, "popen: failed\n");
  return 1;
 }
 fprintf(stderr, "res = %d: %s\n", errno, strerror(errno));

 int res = pclose(fd);
 fprintf(stderr, "res = %d: %s\n", res, strerror(errno));

Как видно из приведенного выше, код просто запускает скрипт с помощью popen и проверяет его состояние выхода. Но однажды я попал в ситуацию, когда popen дали неправильные аргументы. Произошло что-то подобное:

fd = popen("python@$#!", "r");

И тестовый модуль возвратился:

res = 0: Success
sh: 1: python@0!: not found
res = 32512: Success

Итак, popen успешно работает с вышеуказанной ошибкой. И только pclose вернул некоторый статус выхода. С ошибкой zero. Между прочим, оболочка также сделала свой вывод.

Вот мой вопрос. Как я могу определить, не удалось ли оболочке выполнить команду? Сбой может быть по любой причине, но главное - сценарий не запускает событие.

1 Ответ

3 голосов
/ 01 февраля 2020

Общие комментарии о том, когда использовать errno

Нет стандартной C или библиотечная функция POSIX никогда не устанавливает errno в ноль. Печать сообщения об ошибке на основе errno, когда fd не равен NULL, не подходит; номер ошибки не из popen() (или не установлен, потому что popen() не удалось). Печать res после pclose() в порядке; добавление strerror(errno) приводит к той же проблеме (информация в errno может быть совершенно неактуальной). Вы можете установить errno на ноль перед вызовом функции. Если функция возвращает индикацию ошибки, может быть уместно взглянуть на errno (посмотрите спецификацию функции - определено ли для установки errno при ошибке?). Тем не менее, errno может быть установлен ненулевым с помощью функции, даже если это удастся. Стандартные операции ввода-вывода Solaris используются для установки errno = ENOTTY, если выходной поток не был подключен к терминалу, даже если операция прошла успешно; это вероятно все еще делает. И установка Solaris errno даже на успех вполне законна; Правильно смотреть на errno, только если (1) функция сообщает о сбое и (2) задокументирована функция установки errno (POSIX или руководством системы).

См. C11 §7.5 Ошибки <errno.h> ¶3 :

Значение errno в начальном потоке равно нулю при запуске программы (начальное значение errno в других потоках является неопределенным значением) , но никогда не устанавливается в ноль какой-либо библиотечной функцией. 202) Значение errno может быть установлено в ненулевое значение при вызове библиотечной функции независимо от того, есть ли ошибка, если использование errno не задокументировано в описании функции в этом международном стандарте.

202) Таким образом, программа, использующая errno для проверки ошибок, должна установить ее на ноль перед вызовом библиотечной функции, а затем проверить это перед последующим вызовом библиотечной функции. Конечно, библиотечная функция может сохранить значение errno при вводе, а затем установить его на ноль, если исходное значение восстанавливается, если значение errno все еще равно нулю непосредственно перед возвратом.

POSIX аналогично (errno):

Многие функции предоставляют номер ошибки в errno, который имеет тип int и определяется в <errno.h>. Значение errno должно быть определено только после вызова функции, для которой оно явно указано для установки, и до тех пор, пока оно не будет изменено при следующем вызове функции или если приложение присвоит ему значение. Значение errno следует проверять только в том случае, если оно указано допустимым возвращаемым значением функции. Заявки должны получить определение errno путем включения <errno.h>. Ни одна функция в этом томе POSIX.1-2017 не должна устанавливать значение errno равное 0. Установка errno после успешного вызова функции не определена, если в описании этой функции не указано, что errno не должно изменяться.

popen() и pclose()

Спецификация POSIX для popen() не очень полезна. Есть только одно обстоятельство, при котором popen() должен потерпеть неудачу; все остальное «может потерпеть неудачу».

Однако подробности для pclose() гораздо более полезны, включая:

Если интерпретатор языка команд не может Для выполнения статуса завершения дочернего процесса, возвращаемого pclose(), должно быть, как если бы интерпретатор командного языка завершал работу, используя exit(127) или _exit(127).

и

По при успешном возврате pclose() возвращает статус завершения интерпретатора командного языка. В противном случае pclose() должен вернуть -1 и установить errno для указания ошибки.

Это означает, что pclose() возвращает значение, полученное от waitpid() - состояние выхода из команды, которая была вызывается. Обратите внимание, что он должен использовать waitpid() (или эквивалентно избирательную функцию - поиск для wait3() и wait4() в системах BSD); он не авторизован для ожидания других дочерних процессов, кроме того, который создан popen() для этого файлового потока. Существуют предписания о том, что pclose() должен быть уверен, что ребенок вышел, даже если какая-то другая функция ожидала мертвого ребенка в промежуточный период и тем самым вызвала потерю системой статуса для ребенка, созданного popen().

Если вы интерпретируете десятичное число 32512 как шестнадцатеричное, вы получите 0x7F00. И если вы использовали макросы WIFEXITED и WEXITSTATUS из <sys/wait.h> для этого, вы обнаружите, что статус выхода равен 127 (потому что 0x7F является десятичным числом 127, а статус выхода закодирован в старшие биты состояния, возвращаемые waitpid().

int res = pclose(fd);

if (WIFEXITED(res))
    printf("Command exited with status %d (0x%.4X)\n", WEXITSTATUS(res), res);
else if (WIFSIGNALED(res))
    printf("Command exited from signal %d (0x%.4X)\n", WTERMSIG(res), res);
else
    printf("Command exited with unrecognized status 0x%.4X\n", res);

И помните, что 0 - это состояние выхода, указывающее успех; все остальное обычно указывает на ошибку какого-либо рода. Вы можете дополнительно проанализировать статус выхода для поиска 127 или ретранслированных сигналов и т. д. c. Маловероятно, что вы получите статус «сигнализированный» или нераспознанный статус.

popen() сказал вам, что ребенок потерпел неудачу .

Конечно, возможно, что выполненная команда фактически вышла из себя со статусом 127, это неизбежно сбивает с толку, и единственный способ обойти это - избежать состояний выхода в диапазоне от 126 до 128 + 'максимальное число сигналов. '(что может означать 126 .. 191 при наличии 63 распознанных сигналов.) Значение 126 также используется POSIX для сообщения, когда отсутствует интерпретатор, указанный в шебанге (#!/usr/bin/interpreter) ( в отличие от выполняемой программы, недоступной). Будет ли это возвращено pclose() - это отдельное обсуждение. И передача сигналов осуществляется оболочкой, потому что нет другого (простого) способа сообщить, что ребенок умер от сигнала в противном случае.

...