Функция main(): разбор параметров командной строки
При запуске программы мы, как правило, передаем ей информацию в командной строке. Например, можно написать
prog -d -o of lie dataO
Фактические параметры являются аргументами функции main() и могут быть получены из массива C-строк с именем argv; мы покажем, как их использовать.
Во всех предыдущих примерах определение main() содержало пустой список:
int main() { ... }
Развернутая сигнатура main() позволяет получить доступ к параметрам, которые были заданы пользователем в командной строке:
int main( int argc, char *argv[] ){...}
argc содержит их количество, а argv – C-строки, представляющие собой отдельные значения (в командной строке они разделяются пробелами). Скажем, при запуске команды
prog -d -o ofile data0
argc получает значение 5, а argv включает следующие строки:
argv[ 0 ] = "prog";
argv[ 1 ] = "-d";
argv[ 2 ] = "-o";
argv[ 3 ] = "ofile";
argv[ 4 ] = "dataO";
В argv[0] всегда входит имя команды (программы). Элементы с индексами от 1 до argc-1 служат параметрами.
Посмотрим, как можно извлечь и использовать значения, помещенные в argv. Пусть программа из нашего примера вызывается таким образом:
prog [-d] [-h] [-v]
[-o output_file] [-l limit_value]
file_name
[ file_name [file_name [ ... ]]]
Параметры в квадратных скобках являются необязательными. Вот, например, запуск программы с их минимальным количеством – одним лишь именем файла:
prog chap1.doc
Но можно запускать и так:
prog -l 1024 -o chap1-2.out chapl.doc chap2.doc
prog d chap3.doc
prog -l 512 -d chap4.doc
При разборе параметров командной строки выполняются следующие основные шаги:
1. По очереди извлечь каждый параметр из argv. Мы используем для этого цикл for с начальным индексом 1 (пропуская, таким образом, имя программы):
for ( int ix = 1; ix < argc; ++ix ) {
char *pchar = argv[ ix ];
// ...
}
2. Определить тип параметра. Если строка начинается с дефиса (-), это одна из опций { h, d, v, l, o}. В противном случае это может быть либо значение, ассоциированное с опцией (максимальный размер для -l, имя выходного файла для -o), либо имя входного файла. Чтобы определить, начинается ли строка с дефиса, используем инструкцию switch:
switch ( pchar[ 0 ] ) {
case '-': {
// -h, -d, -v, -l, -o
}
default: {
// обработаем максимальный размер для опции -1
// имя выходного файла для -o
// имена входных файлов ...
}
}
Реализуем обработку двух случаев пункта 2.
Если строка начинается с дефиса, мы используем switch по следующему символу для определения конкретной опции. Вот общая схема этой части программы:
case '-': {
switch( pchar[ 1 ] )
{
case 'd':
// обработка опции debug
break;
case 'v':
// обработка опции version
break;
case 'h':
// обработка опции help
break;
case 'o':
// приготовимся обработать выходной файл
break;
case 'l':
// приготовимся обработать макс.размер
break;
default:
// неопознанная опция:
// сообщить об ошибке и завершить выполнение
}
}
Опция -d задает необходимость отладки. Ее обработка заключается в присваивании переменной с объявлением
bool debug_on = false;
значения true:
case 'd':
debug_on = true;
break;
В нашу программу может входить код следующего вида:
if ( debug_on )
display_state_elements( obj );
Опция -v выводит номер версии программы и завершает исполнение:
case 'v':
cout << program_name << "::"
<< program_version << endl;
return 0;
Опция -h запрашивает информацию о синтаксисе запуска и завершает исполнение. Вывод сообщения и выход из программы выполняется функцией usage():
case 'h':
// break не нужен: usage() вызывает exit()
usage();
Опция -o сигнализирует о том, что следующая строка содержит имя выходного файла. Аналогично опция -l говорит, что за ней указан максимальный размер. Как нам обработать эти ситуации?
Если в строке параметра нет дефиса, возможны три варианта: параметр содержит имя выходного файла, максимальный размер или имя входного файла. Чтобы различать эти случаи, присвоим true переменным, отражающим внутреннее состояние:
// если ofi1e_on==true,
// следующий параметр - имя выходного файла
bool ofi1e_on = false;
// если ofi1e_on==true,
// следующий параметр - максимальный размер
bool limit_on = false;
Вот обработка опций -l и -o в нашей инструкции switch:
case 'l':
limit_on = true;
break;
case 'o':
ofile_on = true;
break;
Встретив строку, не начинающуюся с дефиса, мы с помощью переменных состояния можем узнать ее содержание:
// обработаем максимальный размер для опции -1
// имя выходного файла для -o
// имена входных файлов ...
default: {
// ofile_on включена, если -o встречалась
if ( ofile_on ) {
// обработаем имя выходного файла
// выключим ofile_on
}
else if ( limit_on ) { // если -l встречалась
// обработаем максимальный размер
// выключим limit_on
} else {
// обработаем имя входного файла
}
}
Если аргумент является именем выходного файла, сохраним это имя и выключим ofile_on:
if ( ofile_on ) {
ofile_on = false;
ofile = pchar;
}
Если аргумент задает максимальный размер, мы должны преобразовать строку встроенного типа в представляемое ею число. Сделаем это с помощью стандартной функции atoi(), которая принимает строку в качестве аргумента и возвращает int (также существует функция atof(), возвращающая double). Для использования atoi() включим заголовочный файл ctype.h. Нужно проверить, что значение максимального размера неотрицательно и выключить limit_on:
// int limit;
else
if ( limit_on ) {
limit_on = false;
limit = atoi( pchar );
if ( limit < 0 ) {
cerr << program_name << "::"
<< program_version << " : error: "
<< "negative value for limit.\n\n";
usage( -2 );
}
}
Если обе переменных состояния равны false, у нас есть имя входного файла. Сохраним его в векторе строк:
else
file_names.push_back( string( pchar ));
При обработке параметров командной строки важен способ реакции на неверные опции. Мы решили, что задание отрицательной величины в качестве максимального размера будет фатальной ошибкой. Это приемлемо или нет в зависимости от ситуации. Также можно распознать эту ситуацию как ошибочную, выдать предупреждение и использовать ноль или какое-либо другое значение по умолчанию.
Слабость нашей реализации становится понятной, если пользователь небрежно относится к пробелам, разделяющим параметры. Скажем, ни одна из следующих двух строк не будет обработана:
prog - d dataOl
prog -oout_file dataOl
(Оба случая мы оставим для упражнений в конце раздела.)
Вот полный текст нашей программы. (Мы добавили инструкции печати для трассировки выполнения.)
#include <iostream>
#include <string>
#include <vector>
#include <ctype.h>
const char *const program_name = "comline";
const char *const program_version = "version 0.01 (08/07/97)";
inline void usage( int exit_value = 0 )
{
// печатает отформатированное сообщение о порядке вызова
// и завершает программу с кодом exit_value ...
cerr << "порядок вызова:\n"
<< program_name << " "
<< "[-d] [-h] [-v] \n\t"
<< "[-o output_file] [-l limit] \n\t"
<< "file_name\n\t[file_name [file_name [ ... ]]]\n\n"
<< "где [] указывает на необязательность опции:\n\n\t"
<< "-h: справка.\n\t\t"
<< "печать этого сообщения и выход\n\n\t"
<< "-v: версия.\n\t\t"
<< "печать информации о версии программы и выход\n\n\t"
<< "-d: отладка.\n\t\t включает отладочную печать\n\n\t"
<< "-l limit\n\t\t"
<< "limit должен быть неотрицательным целым числом\n\n\t"
<< "-o ofile\n\t\t"
<< "файл, в который выводится результат\n\t\t"
<< "по умолчанию результат записывается на стандартный вывод\n\n"
<< "file_name\n\t\t"
<< "имя подлежащего обработке файла\n\t\t"
<< "должно быть задано хотя бы одно имя --\n\t\t"
<< "но максимальное число не ограничено\n\n"
<< "примеры:\n\t\t"
<< "$command chapter7.doc\n\t\t"
<< "$command -d -l 1024 -o test_7_8 "
<< "chapter7.doc chapter8.doc\n\n";
exit( exit_value );
}
int main( int argc, char* argv[] )
{
bool debug_on = false;
bool ofile_on = false;
bool limit_on = false;
int limit = -1;
string ofile;
vector<string> file_names;
cout << "демонстрация обработки параметров в командной строке:\n"
<< "argc: " << argc << endl;
for ( int ix = 1; ix < argc; ++ix )
{
cout << "argv[ " << ix << " ]: "
<< argv[ ix ] << endl;
char *pchar = argv[ ix ];
switch ( pchar[ 0 ] )
{
case '-':
{
cout << "встретился \'-\'\n";
switch( pchar[ 1 ] )
{
case 'd':
cout << "встретилась -d: "
<< "отладочная печать включена\n";
debug_on = true;
break;
case 'v':
cout << "встретилась -v: "
<< "выводится информация о версии\n";
cout << program_name
<< " :: "
<< program_version
<< endl;
return 0;
case 'h':
cout << "встретилась -h: "
<< "справка\n";
// break не нужен: usage() завершает программу
usage();
case 'o':
cout << "встретилась -o: выходной файл\n";
ofile_on = true;
break;
case 'l':
cout << "встретилась -l: "
<< "ограничение ресурса\n";
limit_on = true;
break;
default:
cerr << program_name
<< " : ошибка : "
<< "неопознанная опция: - "
<< pchar << "\n\n";
// break не нужен: usage() завершает программу
usage( -1 );
}
break;
}
default: // либо имя файла
cout << "default: параметр без дефиса: "
<< pchar << endl;
if ( ofile_on ) {
ofile_on = false;
ofile = pchar;
}
else
if ( limit_on ) {
limit_on = false;
limit = atoi( pchar );
if ( limit < 0 ) {
cerr << program_name
<< " : ошибка : "
<< "отрицательное значение limit.\n\n";
usage( -2 );
}
}
else file_names.push_back( string( pchar ));
break;
}
}
if ( file_names.empty() ) {
cerr << program_name
<< " : ошибка : "
<< " не задан ни один входной файл.\n\n";
usage( -3 );
}
if ( limit != -1 )
cout << "Заданное пользователем значение limit: "
<< limit << endl;
if ( ! ofile.empty() )
cout << "Заданный пользователем выходной файл: "
<< ofile << endl;
cout << (file_names.size() == 1 ? "Файл, " : "Файлы, ")
<< "подлежащий(е) обработке:\n";
for ( int inx = 0; inx < file_names.size(); ++inx )
cout << "\t" << file_names[ inx ] << endl;
}
a.out -d -l 1024 -o test_7_8 chapter7.doc chapters.doc
Вот трассировка обработки параметров командной строки:
демонстрация обработки параметров в командной строке:
argc: 8
argv[ 1 ]: -d
встретился '-'
встретилась -d: отладочная печать включена
argv[ 2 ]: -l
встретился '-'
встретилась -l: ограничение ресурса
argv[ 3 ]: 1024
default: параметр без дефиса: 1024
argv[ 4 ]: -o
встретился '-'
встретилась -o: выходной файл
argv[ 5 ]: test_7_8
default: параметр без дефиса: test_7_8
argv[ 6 ]: chapter7.doc
default: параметр без дефиса: chapter7.doc
argv[ 7 ]: chapter8.doc
default: параметр без дефиса: chapter8.doc
Заданное пользователем значение limit: 1024
Заданный пользователем выходной файл: test_7_8
Файлы, подлежащий(е) обработке:
chapter7.doc
chapter8.doc