
Текст книги "Linux программирование в примерах"
Автор книги: Арнольд Роббинс
Жанры:
Программирование
,сообщить о нарушении
Текущая страница: 5 (всего у книги 55 страниц)
Функция getopt_long()
осуществляет разбор длинных опций в описанном ранее виде. Дополнительная процедура getopt_long_only()
работает идентичным образом, но она используется для программ, в которых все опции являются длинными и начинаются с единичного символа '-
'. В остальных случаях обе функции работают точно так же, как более простая функция GNU getopt()
. (Для краткости, везде, где мы говорим «getopt_long()
», можно было бы сказать «getopt_long()
и getopt_long_only()
».) Вот объявления функций из справки getopt(3) GNU/Linux:
#include
int getopt_long(int argc, char *const argv[],
const char *optstring,
const struct option *longopts, int *longindex);
int getopt_long_only(int argc, char *const argv[],
const char *optstring,
const struct option *longopts, int *longindex);
Первые три аргумента те же, что и в getopt()
. Следующая опция является указателем на массив struct option
, который мы назовем таблицей длинных опций и который вскоре опишем. Параметр longindex
, если он не установлен в NULL, указывает на переменную, в которую помешается индекс обнаруженной длинной опции в longopts
. Это полезно, например, при диагностике ошибок.
Длинные опции описываются с помощью массива структур struct option
. Структура struct option
определена в
; она выглядит следующим образом:
struct option {
const char *name;
int has_arg;
int *flag;
int val;
};
Элементы структуры следующие:
const char *name
Это имя опции без предшествующих черточек, например, «help
» или «verbose
».
int has_arg
Переменная описывает, имеет ли длинная опция аргумент, и если да, какого вида этот аргумент. Значение должно быть одно из представленных в табл. 2.1. Макроподстановки являются некоторыми символическими именами для числовых значений, приведенных в таблице. Хотя числовые значения тоже работают, макроподстановки гораздо легче читать, и вы должны их использовать вместо соответствующих чисел в любом коде, который пишете.
int *flag
Если этот указатель равен NULL, getopt_long()
возвращает значение поля val
структуры. Если он не равен NULL, переменная, на которую он указывает, заполняется значением val
, a getopt_long()
возвращает 0. Если flag
не равен NULL, но длинная опция отсутствует, указанная переменная не изменяется.
int val
Если длинная опция обнаружена, это возвращаемое значение или значение для загрузки в *flag
, если flag
не равен NULL. Обычно, если flag
не равен NULL, val
является значением true/false, вроде 1 или 0. С другой стороны, если flag
равен NULL, val
обычно содержит некоторую символьную константу. Если длинная опция соответствует короткой, эта символьная константа должна быть той же самой, которая появляется в аргументе optstring
для этой опции. (Все это станет вскоре ясно, когда мы рассмотрим несколько примеров.)
Таблица 2.1. Значения для has_arg
no_argument | 0 | Опция не принимает аргумент |
required_argument | 1 | Опции требуется аргумент |
optional_argument | 2 | Аргумент опции является необязательным |
У каждой длинной опции есть один такой элемент с соответствующими заполненными значениями. В последнем элементе массива все значения должны быть равны нулю. Нет необходимости сортировать массив: getopt_long()
осуществляет линейный поиск. Однако, сортировка его по длинным именам может упростить его чтение для программиста.
При первой встрече использование flag
и val
кажется сбивающим с толку. Давайте сделаем на время шаг назад и рассмотрим, почему это работает именно таким способом В большинстве случаев, обработка опций заключается в установке значений различных флаговых переменных при обнаружении различных символов опций, наподобие этого:
while ((с = getopt(argc, argv, ":af:hv")) != -1) {
switch (с) {
case 'a':
do_all = 1;
break;
case 'f':
myfile = optarg;
break;
case 'h':
do_help = 1;
break;
case 'v':
do_verbose = 1;
break;
... /* Здесь обработка ошибок */
}
}
Когда flag
не равен NULL, getopt_long()
устанавливает значения переменных за вас. Это снижает число операторов case
в предыдущем switch
с трех до одного. Вот пример таблицы длинных опций и код для работы с ней:
int do_all, do_help, do_verbose; /* флаговые переменные */
char *my_file;
struct option longopts[] = {
{ "all", no_argument, &do_all, 1 },
{ "file", required_argument, NULL, 'f' },
{ "help", no_argument, &do_help, 1 },
{ "verbose", no_argument, &do_verbose, 1 },
{ 0, 0, 0, 0 }
};
while ((с =
getopt_long(argc, argv, ":f:", longopts, NULL)) != -1) {
switch (c) {
case 'f':
myfile = optarg;
break;
case 0:
/* getopt_long() устанавливает значение переменной,
просто продолжить выполнение */
break;
... /* Здесь обработка ошибок */
}
}
Обратите внимание, что значение, переданное аргументу optstring
, не содержит больше 'a
', 'h
' или 'v
'. Это означает, что соответствующие короткие опции неприемлемы. Чтобы разрешить как длинные, так и короткие опции, вам придется восстановить в switch
соответствующие case
из первого примера.
На практике следует писать свои программы так, чтобы у каждой короткой опции была также соответствующая длинная опция. В этом случае проще всего установить в flag
NULL, а в val
соответствующий единичный символ.
Стандарт POSIX резервирует опцию -W
для специфических для производителя возможностей. Поэтому по определению -W
непереносимо между различными системами.
Если за W
в аргументе optstring
следует точка с запятой (обратите внимание не двоеточие), getopt_long()
рассматривает -Wlongopt
так же, как –longopt
. Соответственно в предыдущем примере измените вызов следующим образом:
while ((с =
getopt_long(argc, argv, ":f:W;", longopts, NULL)) != -1) {
С этим изменением -Wall
является тем же, что и –all
, a -Wfile=myfile
тем же, что –file=myfile
. Использование точки с запятой позволяет программе использовать при желании -W
в качестве обычной опции. (Например, GCC использует ее как нормальную опцию, тогда как gawk
использует ее для совместимости с POSIX.)
getopt_long()
Теперь должно быть ясно, что getopt_long()
предоставляет гибкий механизм для разбора опций. В табл. 2.2 приведена сводка всех возможных возвращаемых значений функции и их значение.
Таблица 2.2. Возвращаемые значения getopt_long()
0 | getopt_long() установила флаг, как указано в таблице длинных опций |
1 | optarg указывает на простой аргумент командной строки |
'?' | Недействительная опция |
' ' | Отсутствующий аргумент опции |
'x' | Символ опции 'x' |
-1 | Конец опций |
Наконец, мы улучшим предыдущий пример кода, показав оператор switch
полностью:
int do_all, do_help, do_verbose; /* флаговые переменные */
char *myfile, *user; /* файл ввода, имя пользователя */
struct option longopts[] = {
{ "all", no_argument, &do_all, 1 },
{ "file", required_argument, NULL, 'f'},
{ "help", no_argument, &do_help, 1 },
{ "verbose", no_argument, &do_verbose, 1 },
{ "user" , optional_argument, NULL, 'u'},
{ 0, 0, 0, 0 }
};
...
while((c=getopt_long(argc, argv, ":ahvf:u::W;", longopts, NULL)) != -1) {
switch (c) {
case 'a':
do_all = 1;
break;
case 'f':
myfile = optarg;
break;
case 'h':
do_help = 1;
break;
case 'u':
if (optarg != NULL)
user = optarg;
else
user = "root";
break;
case 'v':
do_verbose = 1;
break;
case 0:
/* getopt_long() установил переменную, просто продолжить */
break;
#if 0
case 1:
/*
* Используйте этот case, если getopt_long() должна
* просмотреть все аргументы. В этом случае добавьте к
* optstring ведущий * символ '-'. Действительный код,
* если он есть, работает здесь.
*/
break;
#endif
case ':': /* отсутствует аргумент опции */
fprintf(stderr, "%s: option '-%c' requires an argumentn",
argv[0], optopt);
break;
case '?':
default: /* недействительная опция */
fprintf(stderr, "%s: option '-%c' is invalid: ignoredn",
argv[0], optopt);
break;
}
}
В своих программах вы можете захотеть сделать для каждого символа опции комментарии, объясняющие их значение. Однако, если вы использовали описательные имена переменных для каждого символа опции, комментарии уже не так нужны. (Сравните do_verbose
и vflag
.)
getopt()
или getopt_long()
в программах пользователейВы можете захотеть использовать в своих программах GNU getopt()
или getopt_long()
и заставить их работать на не-Linux системах/ Это нормально; просто скопируйте исходные файлы из программы GNU или из CVS архива библиотеки С GNU (GLIBC)[30]30
См. http://sources.redhat.com
– Примеч. автора.
[Закрыть]. Исходные файлы getopt.h
, getopt.с
и getopt1.c
. Они лицензированы на условиях меньшей общедоступной лицензии (Lesser General Public License) GNU, которая позволяет включать библиотечные функции даже в патентованные программы. Вы должны включить в свою программу копию файла COPYING.LIB
наряду с файлами getopt.h
, getopt.с
и getopt1.с
.
Включите исходные файлы в свой дистрибутив и откомпилируйте их с другими исходными файлами. В исходном коде, вызывающем getopt_long()
, используйте '#include
', а не '#include "getopt.h"
'. Затем, при компилировании, добавьте к командной строке компилятора С -I
. Таким способом сначала будет найдена локальная копия заголовочного файла.
Вы можете поинтересоваться: «Вот так, я уже использую GNU/Linux. Почему я должен включать getopt_long()
в свой исполняемый модуль, увеличивая его размер, если процедура уже находится в библиотеке С?» Это хороший вопрос. Однако, здесь не о чем беспокоиться. Исходный код построен так, что если он компилируется на системе, которая использует GLIBC, откомпилированные файлы не будут содержать никакого кода! Вот подтверждение на нашей системе:
$ uname -а /* Показать имя и тип системы */
Linux example 2.4.18-14 #1 Wed Sep 4 13:35:50 EDT 2002 i686 i686 i386 GNU/Linux
$ ls -l getopt.о getopt1.о /* Показать размеры файлов */
-rw-r–r– 1 arnold devel 9836 Mar 24 13:55 getopt.о
-rw-r–r– 1 arnold devel 10324 Mar 24 13:55 getopt1.о
$ size getopt.о getopt1.о /* Показать включенные в исполняемый
модуль размеры */
text data bss dec hex filename
0 0 0 0 0 getopt.о
0 0 0 0 0 getopt1.о
Команда size
печатает размеры различных составных частей двоичного объекта или исполняемого файла. Мы объясним вывод в разделе 3.1 «Адресное пространство Linux/Unix». Что важно понять прямо сейчас, это то, что несмотря на ненулевой размер самих файлов, они не вносят никакого вклада в конечный исполняемый модуль. (Думаем, это достаточно ясно.)
Окружение представляет собой набор пар вида 'имя=значение
' для каждой программы. Эти пары называются переменными окружения. Каждое имя состоит от одной до любого числа буквенно-цифровых символов или символов подчеркивания ('_
'), но имя не может начинаться с цифры. (Это правило контролируется оболочкой; С API может помешать в окружение все, что захочет, за счет возможного запутывания последующих программ.)
Переменные окружения часто используются для управления поведением программ. Например, если в окружении существует POSIXLY_CORRECT
, многие программы запрещают расширения или историческое поведение, которые несовместимы со стандартом POSIX.
Вы можете решить использовать (и должны задокументировать) переменные окружения для управления поведением вашей программы. Например, вы можете вместо аргумента командной строки использовать для опций отладки переменную окружения. Преимуществом использования переменных окружения является то, что пользователи могут установить их в своем загрузочном файле и не беспокоиться больше постоянным набором определенных опций в командной строке.
Конечно, недостатком использования переменных окружения является то, что они могут молча изменять поведение программы. Джим Мейеринг (Jim Meyering), сопроводитель Coreutils, выразил это таким образом:
Они упрощают пользователю настройку программы без изменения способа ее вызова. Это может быть как благословением, так и проклятием. Если вы пишете сценарий, который зависит от значения определенной переменной окружения, а затем этот сценарий использует еще кто-то, у кого нет таких же установок окружения, он легко может потерпеть неудачу (или, что еще хуже, молча выдать неверные результаты).
Несколько функций позволяют получать значения переменных окружения, изменять эти значения или удалять их. Вот соответствующие объявления:
#include
char *getenv(const char *name);
/* ISO С: Получить переменную
окружения */
int setenv(const char *name, /* POSIX: Установить переменную */
const char *value, /* окружения */
int overwrite);
int putenv(char *string); /* XSI: Установить переменную
окружения, использует строку */
void unsetenv(const char *name); /* POSIX: Удалить переменную
окружения */
int clearenv(void); /* Общее: очистить все окружение */
Функция getenv()
– та, которую вы будете использовать в 99% случаев. Ее аргументом является имя переменной окружения, которую нужно искать, такое, как «НОМЕ
» или «PATH
». Если переменная существует, getenv()
возвращает указатель на строковое значение. Если нет, возвращается NULL
. Например:
char *pathval;
/* Поиск PATH; если нет, использовать значение
по умолчанию */
if ((pathval = getenv("PATH")) == NULL)
pathval = "/bin:/usr/bin:/usr/ucb";
Иногда переменная окружения существует, но с пустым значением. В этом случае возвращаемое значение не равно NULL
, но первый символ, на которую оно указывает, будет нулевым байтом, который в С является символом конца строки, ''. Ваш код должен позаботиться проверить, что возвращаемое значение не равно NULL. Если оно не
NULL
, необходимо также проверить, что строка не пустая, если вы хотите для чего-то использовать значение переменной. В любом случае, не используйте возвращенное значение слепо.
Для изменения переменной окружения или добавления к окружению еще одной используется setenv()
:
if (setenv("PATH", "/bin:/usr/bin:/usr/ucb", 1) != 0) {
/* обработать ошибку */
}
Возможно, что переменная уже существует в окружении. Если третий аргумент равен true (не ноль), новое значение затирает старое. В противном случае, предыдущее значение не меняется. Возвращаемое значение равно -1, если для новой переменной не хватило памяти, и 0 в противном случае. setenv()
для сохранения в окружении делает индивидуальные копии как имени переменной, так и нового ее значения
Более простой альтернативой setenv()
является putenv()
, которая берет одну строку «имя=значение
» и помещает ее в окружение:
if (putenv("PATH=/bin:/usr/bin:/usr/ucb") != 0) {
/* обработать ошибку */
}
putenv()
слепо заменяет любые предшествующие значения для той же переменной. А также, и это, возможно, более важно, строка, переданная putenv()
, помещается непосредственно в окружение. Это означает, что если ваш код позже изменит эту строку (например, если это был массив, а не строковая константа), окружение также будет изменено. Это, в свою очередь, означает, что вам не следует использовать в качестве параметров для putenv()
локальную переменную. По всем этим причинам setenv()
является более предпочтительной функцией.
ЗАМЕЧАНИЕ. GNU
putenv()
имеет дополнительную (документированную) особенность в своем поведении. Если строка аргумента является именем без следующего за ним символа=
, именованная переменная удаляется. Программа GNUenv
, которую мы рассмотрим далее в мой главе, полагается на такое поведение.
Функция unsetenv()
удаляет переменную из окружения:
unsetenv("PATH");
Наконец, функция clearenv()
полностью очищает окружение:
if (clearenv() != 0) {
/* обработать ошибку */
}
Эта функция не стандартизирована POSIX, хотя она доступна в GNU/Linux и нескольких коммерческих вариантах Unix. Ее следует использовать, если приложение должно быть очень безопасным и нужно построить собственное окружение с нуля. Если clearenv()
недоступна, в справке GNU/Linux для clearenv(3) рекомендуется использовать для выполнения этой задачи 'environ = NULL
'.
environ
Правильным способом работы с окружением является использование функций, описанных в предыдущем разделе. Однако, стоит взглянуть на то, как это работает «под капотом».
Внешняя переменная environ
предоставляет доступ таким же способом, как argv
предоставляет доступ к аргументам командной строки. Вы сами должны объявить переменную. Хотя она и стандартизирована POSIX, environ
намеренно не объявлена ни в одном стандартном заголовочном файле (Это, кажется, прослеживается из исторической практики.) Вот объявление:
extern char **environ; /* Смотрите, нет заголовочного файла POSIX */
Как и в argv
, завершающим элементом environ
является NULL
. Однако, здесь нет переменной «числа строк окружения», которая соответствовала бы argc
. Следующая простая программа распечатывает все окружение:
/* ch02-printenv.c – Распечатать окружение. */
#include
extern char **environ;
int main(int argc, char **argv) {
int i;
if (environ != NULL)
for (i = 0; environ[i] != NULL; i++)
printf("%sn", environ[i]);
return 0;
}
Хотя это и маловероятно, перед попыткой использовать environ
эта программа проверяет, что она не равна NULL
.
Переменные хранятся в окружении в случайном порядке. Хотя некоторые оболочки Unix хранят переменные окружения в отсортированном по именам переменных виде, это формально не требуется, и многие оболочки не сортируют их.
В качестве уловки реализации можно получить доступ к окружению, объявив третий параметр main()
:
int main(int argc, char **argv, char **envp) {
...
}
Затем можно использовать envp
также, как environ
. Хотя это иногда можно увидеть в старом коде, мы не рекомендуем такое использование; environ
является официальным, стандартным, переносимым способом получения доступа ко всему окружению, если это вам необходимо.
env
Чтобы завершить главу, рассмотрим GNU версию команды env
. Эта команда добавляет переменные к окружению в ходе выполнения одной команды. Она может использоваться также для очищения окружения в ходе этой команды или для удаления отдельных переменных окружения. Программа обеспечивает нас двойной функциональностью, поскольку проявляет возможности как getopt_long()
, так и несколько других возможностей, обсуждавшихся в этом разделе. Вот как вызывается программа:
$ env –help
Usage: env [OPTION]... [-] [NAME=VALUE]... [COMMAND [ARG]...]
/* Устанавливает соответствующее VALUE для каждого NAME и запускает COMMAND */
-i, –ignore-environment /* запустить с пустым окружением */
-u, –unset=NAME /* удалить переменную из окружения */
–help /* показать этот экран справки и выйти */
–version /* вывести информацию о версии и выйти */
/* Простое – предполагает -1. Если не указана COMMAND, отображает
имеющееся окружение.
Об ошибках сообщайте в
Вот несколько примеров вызовов команды:
$ env – myprog arg1 /* Очистить окружение, запустить программу с args */
$ env – РАТН=/bin:/usr/bin myprog arg1 /* Очистить окружение, добавить PATH, запустить программу */
$ env -u IFS PATH=/bin:/usr/bin myprog arg1 /* Сбросить IFS, добавить PATH, запустить программу */
Код начинается со стандартной формулировки авторских прав GNU и разъясняющего комментария. Мы для краткости их опустили. (Формулировка авторского права обсуждается в Приложении С «Общедоступная лицензия GNU». Показанного ранее вывода –help
достаточно для понимания того, как работает программа.) За объявленным авторским правом и комментарием следуют подключаемые заголовочные файлы и объявления. Вызов макроса 'N_("string")
' (строка 93) предназначен для использования при локализации программного обеспечения, тема, освещенная в главе 13 «Интернационализация и локализация». Пока вы можете рассматривать его, как содержащий строковую константу.
80 #include
81 #include
82 #include
83 #include
84 #include
85
86 #include "system.h"
87 #include "error.h"
88 #include "closeout.h"
89
90 /* Официальное имя этой программы (напр., нет префикса 'g'). */
91 #define PROGRAM_NAME "env"
92
93 #define AUTHORS N_ ("Richard Mlynarik and David MacKenzie")
94
95 int putenv();
96
97 extern char **environ;
98
99 /* Имя, посредством которого эта программа была запущена. */
100 char *program_name;
101
102 static struct option const longopts[] =
103 {
104 {"ignore-environment", no_argument, NULL, 'i'},
105 {"unset", required_argument, NULL, 'u'},
106 {GETOPT_HELP_OPTION_DECL},
107 {GETOPT_VERSION_OPTION_DECL},
108 {NULL, 0, NULL, 0}
109 };
GNU Coreutils содержит большое число программ, многие из которых выполняют одни и те же общие задачи (например, анализ аргументов). Для облегчения сопровождения многие типичные идиомы были определены в виде макросов. Двумя таким макросами являются GETOPT_HELP_OPTION_DECL
и GETOPT_VERSION_OPTION
(строки 106 и 107). Вскоре мы рассмотрим их определения. Первая функция, usage()
, выводит информацию об использовании и завершает программу. Макрос _("string")
(строка 115, используется также по всей программе) также предназначен для локализации, пока также считайте его содержащим строковую константу.
111 void
112 usage(int status)
113 {
114 if (status '= 0)
115 fprintf(stderr, _("Try '%s –help' for more information.n"),
116 program_name);
117 else
118 {
119 printf (_("
120 Usage: %s [OPTION]... [-] [NAME=VALUE]... [COMMAND [ARG]...]n"),
121 program_name);
122 fputs (_("
123 Set each NAME to VALUE in the environment and run COMMAND. n
124 n
125 -i, –ignore-environment start with an empty environmentn
126 -u, –unset=NAME remove variable from the environmentn
127 "), stdout);
128 fputs(HELP_OPTION_DESCRIPTION, stdout);
129 fputs(VERSION_OPTION_DESCRIPTION, stdout);
130 fputs(_("
131 n
132 A mere – implies -i. If no COMMAND, print the resulting
133 environment.n"), stdout);
134 printf(_("nReport bugs to <%s>.n"), PACKAGE_BUGREPORT);
135 }
136 exit(status);
137 }
Первая часть main()
объявляет переменные и настраивает локализацию. Функции setlocale()
, bindtextdomain()
и textdomain()
(строки 147–149) обсуждаются в главе 13 «Интернационализация и локализация». Отметим, что эта программа использует аргумент main() envp
(строка 140). Это единственная программа Coreutils, которая так делает. Наконец, вызов atexit()
в строке 151 (см. раздел 9.1.5.3. «Функции завершения») регистрирует библиотечную функцию Coreutils, которая очищает все выходные буферы и закрывает stdout
, выдавая сообщение при ошибке. Следующая часть программы обрабатывает аргументы командной строки, используя getopt_long()
.
139 int
140 main(register int argc, register char **argv, char **envp)
141 {
142 char *dummy_environ[1];
143 int optc;
144 int ignore_environment = 0;
145
146 program_name = argv[0];
147 setlocale(LC_ALL, "");
148 bindtextdomain(PACKAGE, LOCALEDIR);
149 textdomain(PACKAGE);
150
151 atexit(close_stdout);
152
153 while ((optc = getopt_long(argc, argv, "+iu:", longopts, NULL)) != -1)
154 {
155 switch (optc)
156 {
157 case 0:
158 break;
159 case 'i':
160 ignore_environment = 1;
161 break;
162 case 'u':
163 break;
164 case_GETOPT_HELP_CHAR;
165 case_GETOPT_VERSION_CHAR(PROGRAM_NAME, AUTHORS);
166 default:
167 usage(2);
168 }
169 }
170
171 if (optind != argc && !strcmp(argv[optind], "-"))
172 ignore_environment = 1;
Вот отрывок из файла src/sys2.h
в дистрибутиве Coreutils с упомянутыми ранее определениями и макросом 'case_GETOPT_xxx
', использованным выше (строки 164–165):
/* Вынесение за скобки общей части кода, обрабатывающего –help и
–version. */
/* Эти значения перечисления никак не могут конфликтовать со значениями опций,
обычно используемыми командами, включая CHAR_MAX + 1 и т.д. Избегайте
CHAR_MIN – 1, т.к. оно может равняться -1, значение завершения опций getopt.
*/
enum {
GETOPT_HELP_CHAR = (CHAR_MIN – 2),
GETOPT_VERSION_CHAR = (CHAR_MIN – 3)
};
#define GETOPT_HELP_OPTION_DECL
"help", no_argument, 0, GETOPT_HELP_CHAR
#define GETOPT_VERSION_OPTION_DECL
"version", no_argument, 0, GETOPT_VERSION_CHAR
#define case_GETOPT_HELP_CHAR
case GETOPT_HELP_CHAR:
usage(EXIT_SUCCESS);
break;
#define case_GETOPT_VERSION_CHAR(Program_name, Authors)
case GETOPT_VERSION_CHAR:
version_etc(stdout, Program_name, PACKAGE, VERSION, Authors);
exit(EXIT_SUCCESS);
break;
Результатом этого кода является печать сообщения об использовании утилиты для –help
и печать информации о версии для –version
. Обе опции завершаются успешно («Успешный» и «неудачный» статусы завершения описаны в разделе 9.1.5.1 «Определение статуса завершения процесса».) Поскольку в Coreutils входят десятки утилит, имеет смысл вынести за скобки и стандартизовать как можно больше повторяющегося кода.
Возвращаясь к env.с
:
174 environ = dummy_environ;
175 environ[0] = NULL;
176
177 if (!ignore_environment)
178 for (; *envp; envp++)
179 putenv(*envp);
180
181 optind = 0; /* Принудительная реинициализация GNU getopt. */
182 while ((optc = getopt_long(argc, argv, "+iu:", longopts, NULL)) != -1)
183 if (optc == 'u')
184 putenv(optarg); /* Требуется GNU putenv. */
185
186 if (optind !=argc && !strcmp(argv[optind], "-")) /* Пропустить опции */
187 ++optind;
188
189 while (optind < argc && strchr(argv[optind], '=')) /* Установить
переменные окружения * /
190 putenv(argv[optind++]);
191
192 /* Если программа не указана, напечатать переменные окружения и выйти. */
193 if (optind == argc)
194 {
195 while (*environ)
196 puts (*environ++);
197 exit(EXIT_SUCCESS);
198 }
Строки 174–179 переносят существующие переменные в новую копию окружения. В глобальную переменную environ
помещается указатель на пустой локальный массив. Параметр envp
поддерживает доступ к первоначальному окружению.
Строки 181–184 удаляют переменные окружения, указанные в опции -u
. Программа осуществляет это, повторно сканируя командную строку и удаляя перечисленные там имена. Удаление переменных окружения основывается на обсуждавшейся ранее особенности GNU putenv()
: при вызове с одним лишь именем переменной (без указанного значения) putenv()
удаляет ее из окружения.
После опций в командной строке помещаются новые или замещающие переменные окружения. Строки 189–190 продолжают сканирование командной строки, отыскивая установки переменных окружения в виде 'имя=значение
'.
По достижении строки 192, если в командной строке ничего не осталось, предполагается, что env
печатает новое окружение и выходит из программы. Она это и делает (строки 195–197).
Если остались аргументы, они представляют имя команды, которую нужно вызвать, и аргументы для передачи этой новой команде. Это делается с помощью системного вызова execvp()
(строка 200), который замещает текущую программу новой. (Этот вызов обсуждается в разделе 9.1.4 «Запуск новой программы: семейство exec()
»; пока не беспокойтесь о деталях.) Если этот вызов возвращается в текущую программу, он потерпел неудачу. В таком случае env
выводит сообщение об ошибке и завершает программу.
200 execvp(argv[optind], &argv[optind]);
201
202 {
203 int exit_status = (errno == ENOENT ? 127 : 126);
204 error(0, errno, "%s", argv[optind]);
205 exit(exit_status);
206 }
207 }
Значения кода завершения 126
и 127
(определяемые в строке 203) соответствуют стандарту POSIX. 127
означает, что программа, которую execvp()
попыталась запустить, не существует. (ENOENT
означает, что файл не содержит записи в каталоге.) 126
означает, что файл существует, но была какая-то другая ошибка.