Книга: Linux программирование в примерах

2.4.3. GNU env

2.4.3. GNU 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, отображает
   имеющееся окружение.
Об ошибках сообщайте в <[email protected]>. */

Вот несколько примеров вызовов команды:

$ 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 <config.h>
81  #include <stdio.h>
82  #include <getopt.h>
83  #include <sys/types.h>
84  #include <getopt.h>
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 означает, что файл существует, но была какая-то другая ошибка.

Оглавление книги


Генерация: 1.243. Запросов К БД/Cache: 3 / 0
поделиться
Вверх Вниз