Список аргументов здесь — список аргументов командной строки, передаваемых программе.
Заметьте, что в библиотеке языка Си функции spawnlp(), spawnvp() и spawnlpe() все вызывают функцию spawnvpe(), которая, в свою очередь, вызывает POSIX-функцию spawnp(). Функции spawnle(), spawnv() и spawnl() все в конечном счете вызывают функцию spawnve(), которая затем вызывает POSIX-функцию spawn(). И, наконец, POSIX-функция spawnp() вызывает POSIX-функцию spawn(). Таким образом, в основе всех возможностей семейства spawn() лежит сам вызов spawn().
Рассмотрим теперь различные варианты функций spawn() и exec() более подробно так, чтобы вы смогли получить навык свободного использования различных суффиксов. Затем мы перейдем непосредственно к рассмотрению вызова функции spawn().
Суффикс «l»Например, если я хочу вызвать команду ls с аргументами -t, -r, и -l (означает — «сортировать выходные данные по времени в обратном порядке и показывать выходные данные в длинном формате»), я мог бы определить это в программе так:
/* Вызвать ls и продолжить выполнение */
spawnl(P_WAIT, "/bin/ls", "/bin/ls", "-t", "-r", "-l",
NULL);
/* Заменить себя на ls */
execl(P_WAIT, "/bin/ls", "/bin/ls", "-t", "-r", "-l",
NULL);
Или, вариант с применением суффикса v:
char *argv[] = {
"/bin/ls",
"-t",
"-r",
"-l",
NULL
};
/* Вызвать ls и продолжить выполнение */
spawnv(P_WAIT, "/bin/ls", argv);
/* Заменить себя на ls */
execv(P_WAIT, "/bin/ls", "/bin/ls", argv);
Почему именно такой выбор? Он дан для удобства восприятия. У вас может быть синтаксический анализатор, уже встроенный в вашу программу, и может быть удобно сразу оперировать массивами строк. В этом случае я бы рекомендовал применять варианты с суффиксом «v». Или вам может понадобиться запрограммировать вызов программы, когда вам известно, где он находится и какие имеет параметры. В этом случае, зачем вам утруждать себя созданием массива строк, когда вы знаете точно, какие нужны аргументы? Просто передайте их варианту функции с суффиксом «l».
Отметим, что мы передаем реальное имя пути программы (/bin/ls), а затем имя программы еще раз в качестве первого аргумента. Это делается для поддержки программ, которые ведут себя по-разному в зависимости от того, под каким именем они были вызваны.
Например, GNU-утилиты компрессии и декомпрессии (gzip и gunzip) фактически привязаны к одному и тому же исполняемому модулю. Когда исполняемый модуль стартует, он анализирует аргумент argv[0] (передаваемый функции main()) и принимает решение, следует ли выполнять компрессию или декомпрессию.
Суффикс «е»Варианты с суффиксом «е» передают программе окружение. Окружение — это только своего рода «контекст», в котором работает программа. Например, у вас может быть программа проверки орфографии, у которой есть эталонный словарь. Вместо описания каждый раз в командной строке местоположения словаря вы могли бы сделать это в окружении:
$ export DICTIONARY=/home/rk/.dict
$ spellcheck document.1
Команда export предписывает командному интерпретатору создать новую переменную окружения (в нашем случае DICTIONARY) и присвоить ей значение (/home/rk/.dict).
Если вы когда-либо хотели бы использовать различные словари, вы были бы должны изменить среду до выполнения программы. Это просто сделать из оболочки:
$ export DICTIONARY=/home/rk1.altdict
$ spellcheck document.1
Но как сделать это из ваших собственных программ? Для того чтобы применять «e»-версии функций spawn() и exec(), вам следует определить массив строк, представляющих собой окружение:
char *env[] = {
"DICTIONARY=/home/rk/.altdict",
NULL
};
// Запуск проверки в отдельном процессе:
spawnle(P_WAIT, "/usr/bin/spellcheck",
"/usr/bin/spellcheck", "documents.1", NULL, env);
// Запуск проверки вместо себя:
execle("/usr/bin/spellcheck", "/usr/bin/spellcheck",
"document.1", NULL, env);
Суффикс «p»Версии с суффиксом «p» будут искать исполняемый модуль программы в списке каталогов, приведенном в переменной окружения PATH. Вы, вероятно, отметили, что во всех примерах местоположение исполняемых модулей строго определено — /bin/ls и /usr/bin/spellcheck. А как быть с другими исполняемыми модулями? Если вы не хотите сразу определить точный путь к нужной программе, было бы лучше сделать так, чтобы места поиска исполняемых модулей вашей программе сообщил пользователь. Стандартная системная переменная PATH для этого и предназначена. Ниже приведено ее значение для минимальной системы:
PATH=/proc/boot:/bin
Это сообщает командному интерпретатору, что когда я набираю команду, он в первую очередь должен просмотреть каталог /proc/boot/, и если не сможет найти команду там, то должна просмотреть каталог бинарных файлов /bin. Переменная PATH представляет собой разделяемый двоеточиями список каталогов для поиска команд. К переменной PATH вы можете добавлять столько элементов, сколько пожелаете, но имейте в виду, что при поиске файла будут проанализированы все элементы (в приведенной последовательности).
Если вы не знаете путь к выполнимой программе, вы можете использовать варианты с суффиксом «p».
Например:
// Использование явного пути:
execl("/bin/ls", "/bin/ls", "-l", "-t", "-r", NULL);
// Поиск пути в PATH:
execp("ls", "ls", "-l", "-t", "-r", NULL) ;
Если функция execl() не сможет найти ls в /bin, она завершится с ошибкой. Функция execlp() просмотрит все каталоги, указанные в PATH, в поисках ls, и завершится с ошибкой только в том случае, если не сможет найти ls ни в одном из этих каталогов. Это также прекрасная вещь для многоплатформенной поддержки — вашей программе не обязательно знать имена каталогов, принятых на разных машинах, она просто выполнит поиск.
А что произойдет, если сделать так?
execlp("/bin/ls", "ls", "-l", "-t", "-r", NULL);
Выполняет ли этот вызов поиск в окружении? Нет. Вы явно указали execlp() имя пути, что отменяет правило поиска в PATH. Если ls не будет найдена в /bin (а здесь это будет именно так), то никаких других попыток поиска не выполняется — эта ситуация подобна варианту с использованием функции execl()).
Опасно ли смешивать явный путь с простым именем команды (например, указывать путь как /bin/ls, а имя — как ls вместо /bin/ls)? Обычно нет, потому что:
• значительное число программ так или иначе игнорирует argv[0];
• те программы, поведение которых зависит от их имени, обычно вызывают функцию basename(), которая удаляет каталоговую часть argv[0] и возвращает только имя.
Единственная обоснованная причина использования полного имени пути в качестве первого параметра заключается в том, что программа может выводить диагностические сообщения, содержащие этот первый параметр, который немедленно укажет вам, откуда она была вызвана. Это может быть важно, если копии программы располагаются в нескольких каталогах из перечисленных в PATH.
Функции семейства spawn() имеют дополнительный параметр; во всех приведенных выше примерах я всегда указывал P_WAIT. Имеются четыре флага, которые вы можете придать функции spawn(), чтобы изменить ее поведение:
P_WAIT Вызывающий процесс (ваша программа) будет блокирован до тех пор, пока вновь созданный процесс не отработает и не завершится. P_NOWAIT Вызывающая программа не будет блокирована на время выполнения вновь созданной. Это позволяет вам запустить программу в фоновом режиме и продолжать выполнение, пока она делает свое дело. P_NOWAITO Аналогично P_NOWAIT за исключением того, что устанавливается флаг SPAWN_NOZOMBIE. Это означает, что вы не должны беспокоить себя вызовом функции
waitpid() для очистки кода завершения процесса. P_OVERLAY Этот флаг превращает вызов функции
spawn() в соответствующей вызов
exec()! Ваша программа преобразуется в указанную программу без изменения идентификатора процесса ID. Вообще-то, если вы хотите сделать именно так, то, наверное, будет более корректно использовать вызов
exec(), поскольку это избавит будущих читателей ваших исходных текстов от необходимости искать P_OVERLAY в справочном руководстве по библиотеке языка Си!
Просто spawn()Как мы упомянули выше, все функции семейства spawn(), в конечном счете, вызывают базовую функцию spawn(). Ниже приведен прототип функции spawn():