C. Глава 5. Аргументы функции main

Feb 25, 2018 18:14


"В операционной среде, обеспечивающей поддержку Си, имеется возможность передать аргументы или параметры запускаемой программе с помощью командной строки. В момент вызова main получает два аргумента. В первом, обычно называемом argc (сокращение от argument count), стоит количество аргументов, задаваемых в командной строке. Второй, argv (от argument vector), является указателем на массив символьных строк, содержащих сами аргументы. Для работы с этими строками обычно используются указатели нескольких уровней. "

(Б. Керниган, Д. Ритчи "Язык программирования C")

1. Параметры запуска приложения
Именно для этого функции main нужны аргументы. На самом деле, аргументов у main даже три, - последний указывает на блок переменных окружения. Аргументы извлекаются операционной системой из командной строки, при запуске программы, и передаются функции main. С помощью аргументов, каковые используются в форме ключей и имен файлов, мы можем управлять режимом работы программ. Они являются аналогом главного меню оконного приложения, но только для командной строки.

Здесь мы исследуем нашу собственную командную строку. Напишем простую программу, копирующую командную строку в буфер размером 256 байтов.

//main.c - аргументы main

#include
#include

char cmdline[256];

main( int argc, char *argv[] )
{
int i;
char *ptr = cmdline;

for( i = 0; i < argc; i++ )
{
strcat( ptr, argv[i] );
ptr += strlen( argv[i] );
strcat( ptr, " " );
}
puts( cmdline );
}
Лст. 5-1. Копирование командной строки в текстовый буфер.

Алгоритм здесь исходит из типа второго аргумента - указателя на указатель. Тот же результат может быть получен записью: char **argv Попробуйте сами.

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

$ m main
$ main 1 2 3 Аргумент
main 1 2 3 Аргумент
$ main 20 * ( 10 + 30 ) / 400
bash: syntax error near unexpected token `('
$
Лст. 5-2. Интерпретация метасимволов оболочкой.

Из результата можно сделать выводы, что нулевым аргументом является имя самой программы. Кроме того, мы видим, что некоторые символы приводят оболочку в негодование. Это метасимволы. В таком контексте (командной строки), они видятся не как обычные символы. Так, круглые скобки используются для запуска команд в дочерней оболочке. Если мы хотим, все-таки, затолкать их в командную строку, то надо или экранировать их слешем, или взять всю строку в кавычки:

$ main 20 \* \( 10 + 30 \) / 400
main 20 * ( 10 + 30 ) / 400
$ main '20 * ( 10 + 30 ) / 400'
main 20 * ( 10 + 30 ) / 400
$ main "20 * ( 10 + 30 ) / 400"
main 20 * ( 10 + 30 ) / 400
$
Лст. 5-3. Экранирование метасимволов.

Звездочка - это тоже метасимвол, глоб. Если не использовать экранирования звездочки слешем, то вместо нее будет подставлен перечень файлов текущего каталога. Правильно экранированные метасимволы попадают в строку как обычные символы. Кавычки вообще избавляют от необходимости что-то экранировать.

При разборе командной строки операционная система также очищает ее от лишних пробелов, которые может ввести пользователь. Это относится только к аргументам, не объединенным кавычками в строку. Пробелы в строке в кавычках будут сохранены.

$ main 1 2 3 e
main 1 2 3 e
$
Лст. 5-4. Удаление лишних пробелов.

Думаю, читателям уже все ясно. Давайте попробуем написать простую, но полезную программу, используя изученные свойства функции main. Кстати, их обеспечивает нам наша библиотека времени выполнения, ctr0.o. Будь она написана чуть проще, аргументы main были бы нам недоступны.

2. Полезная программа
Напишем простую программу detab. Хочу напомнить, если не делал этого раньше: каждый раз, когда мы выбираем имя name для новой программы, следует проверять его командой which name. Чтобы получить гарантию, что программы с таким именем нет в системе, и не смущаться ее неожиданным поведением. Наиболее частый пример: test. Можно, конечно, использовать в таком случае команду: ./test, но удобно ли это? На всякий случай, напоминаю.

Назначение detab - удалять табуляции и заменять их пробелами. Конечно, это можно сделать и в текстовом редакторе, но не каждый редактор дает возможность удобно это делать. Между прочим, и мой любимый scite. Обычно, вместо табуляции используется четыре пробела. Но некоторым удобно заменять это число на 8 или другие значения. Поэтому, нам будет очень полезен аргумент при вызове такой команды.

main( int argc, char *argv[] )
{
if( argc > 2 ) {
puts( "detab: Неверное число аргументов" );
return 1;
}
if( argc == 2 ) {
tabsize = atoi( argv[1] );
if( tabsize < 1 || tabsize > 16 ) {
puts( "detab: Ошибка в параметре" );
return 2;
}
} else tabsize = 4;

return 0;
}
Лст. 5-5. Логика разбора командной строки.

Тот факт, что получить сами аргументы из командной строки несложно, никак не отменяет возможной сложности их разбора в общем случае. Пользователь - это произвольное множество клавиш, посылаемых в программу. Мы, в любом случае, должны отреагировать на них без ошибок. Слишком простая программа, полагающаяся на "само собой разумеющийся" ввод, тупо окажется глючной. Именно так и работают "девелоперы"-гуманитарии.

В нашем случае, программа должна получить либо один аргумент, либо ни одного. Если она получает один аргумент, то это должна быть ширина табуляции, выраженная в числе пробелов. При этом, величина параметра должна оказаться в допустимых пределах. Если ни одного аргумента не было передано в командную строку, то по умолчанию вместо табуляций должно быть использовано число 4.

Докажем, что код в листинге удовлетворяет условиям. Первый оператор if отбрасывает все случаи, когда число аргументов меньше 1 и больше 2. Аргументов не может быть меньше 1 - это само имя main. Следовательно, остается проверить случай argc == 2, и, если это так, дополнительно решить целочисленное неравенство: 0 < tabsize < 17. Если a != 2, то a == 1 и тогда выполняется альтернативная ветвь, else. tabsize присваивается значение 4. После такого доказательства тестировать нечего, разве что, опечатки в коде.

$ m detab
$ detab 1 2
detab: Неверное число аргументов
$ detab 0
detab: Ошибка в параметре
$ detab 17
detab: Ошибка в параметре
$ detab 8
$
Лст. 5-6. Тест разбора командной строки.

Теперь опишем на языке C следующий алгоритм. Для каждой табуляции во входном потоке, то есть, байта \t (код 0x09), вместо нее делается вывод tabsize пробелов в выходной поток. В результате получим программу:

//detab.c - программа для удаления табуляций

#include

int tabsize, c, i;

main( int argc, char *argv[] )
{
if( argc > 2 ) {
puts( "detab: Неверное число аргументов" );
return 1;
}
if( argc == 2 ) {
tabsize = atoi( argv[1] );
if( tabsize < 1 || tabsize > 16 ) {
puts( "detab: Ошибка в параметре" );
return 2;
}
} else tabsize = 4;

while( ( c = getchar() ) != -1 )
{
if( c == '\t' )
for( i = 0; i < tabsize; i++ )
putchar( ' ' );
else putchar( c );
}
return 0;
}
Лст. 5-7. Табуляции в этом листинге удалены с помощью detab.

Программа была испытана на ее же собственном исходнике:

$ detab < detab.c | xsel -ib
$
Лст. 5-8. Использование программы с конвейером.

Исходный файл направляется во входной поток программы и передается по конвейеру в буфер обмена, откуда он попадает в листинг 5-7.

Более наглядный пример с другим файлом:



Рис. 5-1. Файл с табуляциями.

Этот файл мы переписываем в другой:

$ detab < tabbed.txt > detabbed.txt
$
Лст. 5-9. Использование программы с потоками.

И смотрим на разницу между ними:



Рис. 5-2. Замена табуляций по умолчанию.

Если нас будут интересовать другие значения отступов, например, 2 позиции, то мы выполним другую команду:

$ detab 2 < tabbed.txt > detabbed.txt
$
Лст. 5-10. Ширина отступов в два пробела.



Рис. 5-3. Короткие отступы.

Скомпилированная программа весит всего 1432 байта (это вызов специалистам по спортивному ассемблеру) и на моей машине выполняет поставленную задачу за 1 миллисекунду. Можно написать программу, создающую файл, подобный tabbed.txt, размером в 10 или 100 мегабайт и посмотреть, как быстро он будет обработан. Думаю, достаточно быстро, чтобы этим вовсе не интересоваться.

С помощью detab можно обработать целую пачку файлов, например, с ручной html разметкой. Иногда это бывает полезным, если слишком размахнулись с отступами. Конечно, мир команд Unix и скриптовые языки могут предложить мильён других решений, но, мне кажется, что это - проще и легче. Такую же программу можно собрать и в Windows, причем в таких же размерах и на языке C. Я начинал делать микробиблиотеку еще в те времена, когда был на каторге Windows 98, по причине слишком слабой машины, но потом забросил эту идею надолго.

Для аргументов командной строки используются некоторые соглашения, например, каждый параметр или ключ начинается с минуса, то есть, дефиса. Иногда один дефис предваряет целую группу возможных параметров, которые могут быть использованы в произвольном порядке. В общем и целом, каждый разработчик волен выбирать свои правила, но стандартизованные утилиты должны строго придерживаться однажды возникших правил, чтобы не возникало проблем с совместимостью. Иногда ради этого поддерживаются даже неиспользуемые параметры!

Бывает, что отлично работающие утилиты имеют ужасный набор аргументов и пользователи ругают их авторов маленьким язычком. Если вы задумали написать великую утилиту - постарайтесь, чтобы ваш набор аргументов был удобным. Иначе будете вертеться на том свете, как вентилятор.

В следующей главе мы поработаем с таким элементом языка C, как структуры. Это довольно интересная вещь, которой иногда слишком злоупотребляют, а иногда, наоборот, недооценивают.

Дальше

main, командная строка, #include, include, аргументы main, c, argv, argc

Previous post Next post
Up