al_fuhrmann (al_fuhrmann) wrote,
al_fuhrmann
al_fuhrmann

Categories:

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

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


1. Параметры запуска приложения

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

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

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

#include <stdio.h>
#include <string.h>

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 <stdio.h>

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, как структуры. Это довольно интересная вещь, которой иногда слишком злоупотребляют, а иногда, наоборот, недооценивают.
Дальше
Tags: #include, argc, argv, c, include, main, аргументы main, командная строка
Subscribe

  • Xcircuit - 1

    Хочу рассказать о хорошем редакторе приципиальных схем. Как видно из названия, этот редактор сделан для X (только не надо путать это с десяткой), и…

  • Снайперский прицел (+бонус)

    Для поиска в интернете. Дикая вещь!! Пригодится всем, кто ищет информацию. Без всякой рекламной херни и гнилых ссылок. Однако, как говорил один мой…

  • Вдогонку к предыдущему посту

    Конечно, такой навороченный инструмент, как Meshlab, не может не содержать множество кнопочек и крутилок для поддержания нормального полета. Только…

  • Post a new comment

    Error

    Anonymous comments are disabled in this journal

    default userpic

    Your reply will be screened

    Your IP address will be recorded 

  • 0 comments