C++

Аргументи командного рядка

Вивчіть аргументи командного рядка у C++ — параметри argc та argv функції main, доступ до рядкових аргументів, конвертація чисел через std::stringstream, атмосфера shell-парсингу та практичні CLI-програми.

Аргументи командного рядка

Навіщо потрібні аргументи командного рядка

Усі програми, які ми писали до цього, отримували вхідні дані одним способом: через std::cin — тобто програма запускалась, призупинялась і чекала, поки користувач введе щось з клавіатури. Цей підхід зручний для навчання, але він є непридатним для автоматизації.

Уявіть таку задачу: у вас є програма, що обробляє зображення — стискає їх і зберігає мініатюру. У каталозі лежить 500 файлів. Щоб обробити кожен через std::cin, потрібно 500 разів запустити програму вручну і ввести ім'я файлу. Але що, якщо цю програму викликає веб-сервер, скрипт або планувальник завдань? Вони не можуть «вводити» дані через клавіатуру — їм потрібно передати всю необхідну інформацію при запуску.

Саме для цього і існують аргументи командного рядка (command-line arguments) — рядки, які передаються програмі ОС у момент її запуску. Ви вже бачили цей механізм щодня:

// Компілятор g++ отримує ім'я файлу як аргумент
g++ main.cpp -o program

// git отримує команду і шлях
git add .

// Запуск власної програми з аргументами
./picture photo.jpg --width=200

Кожен з цих інструментів реалізований на C або C++, і їхній main отримує аргументи через стандартний механізм, який ми зараз вивчимо.

Передумови. Розуміння C-рядків (const char*), масивів вказівників і базової роботи з std::cin/std::cout. Аргументи командного рядка є частиною стандарту C++ і підтримуються усіма компіляторами і платформами.

Нова форма main: argc і argv

До цього ми оголошували main без параметрів:

int main()
{
    // ...
}

Щоб отримати доступ до аргументів командного рядка, використовується інша форма main з двома параметрами:

int main(int argc, char* argv[])
{
    // ...
}

Або еквівалентний запис через подвійний вказівник:

int main(int argc, char** argv)
{
    // Ідентично попередньому — обидві форми правильні
}
Обидві форми абсолютно рівнозначні. Перша — char* argv[] — є інтуїтивнішою і частіше рекомендована в підручниках. Друга — char** argv — демонструє справжню природу: вказівник на вказівник на char.

Розбір параметрів

argc (від "argument count" — кількість аргументів) — ціле число, що зберігає кількість аргументів. Завжди щонайменше 1, бо першим аргументом є ім'я самої програми.

argv (від "argument values" — значення аргументів) — масив C-рядків (char*) довжиною argc. Кожен елемент — один аргумент:

ІндексВміст argv[i]
argv[0]Шлях до виконуваного файлу ("C:\\program.exe" або "./program")
argv[1]Перший аргумент користувача
argv[2]Другий аргумент користувача
argv[argc-1]Останній аргумент
argv[argc]Гарантовано nullptr (нульовий вказівник)

Перший приклад: виводимо всі аргументи

PrintArgs.cpp
#include <iostream>

int main(int argc, char* argv[])
{
    std::cout << "Кількість аргументів: " << argc << '\n';
    std::cout << "Аргументи:\n";

    for (int i = 0; i < argc; ++i)
    {
        std::cout << "  argv[" << i << "] = " << argv[i] << '\n';
    }

    return 0;
}

Якщо скомпілювати цю програму і запустити її так:

./PrintArgs hello world 42

Вивід:

./PrintArgs hello world 42
$ ./PrintArgs hello world 42
Кількість аргументів: 4
Аргументи:
argv[0] = ./PrintArgs
argv[1] = hello
argv[2] = world
argv[3] = 42

Розбір. Навіть якщо ми передали 3 аргументи (hello, world, 42), argc = 4, бо argv[0] — сама програма. Аргументи завжди є рядками — зверніть: 42 виведено як рядок "42", а не як числове значення.

Рядок 8. for (int i = 0; i < argc; ++i) — ітерація по всіх аргументах. Якщо хочете пропустити argv[0] (ім'я програми) і обробляти лише аргументи від користувача — починайте з i = 1.


Перевірка наявності аргументів

Завжди перевіряйте argc перед доступом до argv[1] та далі. Якщо користувач не передав жодного аргументу — argv[1] просто не існує, і звертання до нього — невизначена поведінка:

CheckArgs.cpp
#include <iostream>
#include <cstdlib> // для exit()

int main(int argc, char* argv[])
{
    // Перевіряємо: чи передав користувач хоча б один аргумент?
    // argc == 1 означає: є лише argv[0] (ім'я програми), більше нічого
    if (argc < 2)
    {
        // argv[0] — ім'я програми: виводимо правильне «використання»
        std::cout << "Використання: " << argv[0] << " <ім'я файлу>\n";
        std::cout << "Приклад:      " << argv[0] << " photo.jpg\n";

        // exit(1) — завершуємо програму з кодом помилки 1
        // За конвенцією: 0 = успіх, не 0 = помилка
        exit(1);
    }

    // Тепер safe: argv[1] гарантовано існує
    std::cout << "Обробляємо файл: " << argv[1] << '\n';
    // ... подальша логіка роботи з файлом

    return 0;
}
./CheckArgs (без аргументів)
$ ./CheckArgs
Використання: ./CheckArgs <ім'я файлу>
Приклад: ./CheckArgs photo.jpg
./CheckArgs photo.jpg
$ ./CheckArgs photo.jpg
Обробляємо файл: photo.jpg

Рядок 8. if (argc < 2) — якщо менше двох аргументів (тобто немає argv[1]), виводимо підказку і завершуємо. Це стандартна конвенція: будь-яка CLI-програма повинна пояснювати користувачу своє «правильне» використання, якщо аргументів бракує.

Рядок 14. exit(1) — функція зі <cstdlib>, що завершує програму негайно, не повертаючись з main. Аргумент — код виходу: 0 означає успіх, будь-яке ненульове — помилку. Ця конвенція є стандартною в Unix/Linux/Windows і дозволяє скриптам та автоматизаційним інструментам перевіряти, чи завершилась програма успішно.


Конвертація аргументів у числа

Усі аргументи командного рядка приходять як рядки (const char*), навіть якщо користувач ввів число. Щоб використати їх як числа — потрібно явно конвертувати.

Найпростіший спосіб у C++ — через std::stringstream:

NumericArg.cpp
#include <iostream>
#include <sstream>  // для std::stringstream
#include <cstdlib>  // для exit()

int main(int argc, char* argv[])
{
    if (argc < 2)
    {
        std::cout << "Використання: " << argv[0] << " <число>\n";
        exit(1);
    }

    // argv[1] — рядок, наприклад "42" або "hello"
    // Конвертуємо його в int через std::stringstream
    std::stringstream converter(argv[1]); // ініціалізуємо потік рядком

    int number;

    // Оператор >> намагається зчитати int з рядка
    // Якщо рядок не є числом — повертає false (потік у стані помилки)
    if (!(converter >> number))
    {
        std::cout << "Помилка: \"" << argv[1] << "\" — не є цілим числом.\n";
        exit(1);
    }

    std::cout << "Отримано число: " << number << '\n';
    std::cout << "Подвоєне:       " << number * 2 << '\n';
    std::cout << "Квадрат:        " << number * number << '\n';

    return 0;
}
./NumericArg 15
$ ./NumericArg 15
Отримано число: 15
Подвоєне: 30
Квадрат: 225
./NumericArg hello
$ ./NumericArg hello
Помилка: "hello" — не є цілим числом.

Рядок 15. std::stringstream converter(argv[1]) — створюємо «рядковий потік», ініціалізований рядком-аргументом. std::stringstream поводиться як std::cin, але зчитує дані не з клавіатури, а з рядка в пам'яті. Включити потрібно <sstream>.

Рядок 21. if (!(converter >> number)) — оператор >> намагається зчитати int з потоку. Якщо це неможливо (рядок не є числом) — потік переходить у стан помилки і >> повертає false-подібне значення. Оператор ! інвертує результат: якщо конвертація не вдалась → заходимо в if.

Практичний приклад: конвертація кількох аргументів

MultiNumeric.cpp
#include <iostream>
#include <sstream>
#include <cstdlib>

int strToInt(const char* str)
{
    std::stringstream ss(str);
    int result;
    if (!(ss >> result))
        return 0; // значення за замовчуванням при помилці
    return result;
}

int main(int argc, char* argv[])
{
    if (argc < 3)
    {
        std::cout << "Використання: " << argv[0] << " <width> <height>\n";
        exit(1);
    }

    int width  = strToInt(argv[1]);
    int height = strToInt(argv[2]);

    if (width <= 0 || height <= 0)
    {
        std::cout << "Помилка: розміри мають бути додатніми числами.\n";
        exit(1);
    }

    std::cout << "Розмір:  " << width << " x " << height << '\n';
    std::cout << "Площа:   " << width * height << '\n';
    std::cout << "Периметр:" << 2 * (width + height) << '\n';

    return 0;
}
./MultiNumeric 16 9
$ ./MultiNumeric 16 9
Розмір: 16 x 9
Площа: 144
Периметр:50

Рядки 5–11. Допоміжна функція strToInt — приймає C-рядок і повертає int. Можна розміщувати логіку конвертації прямо в main, але виносити її у функцію є гарною практикою: повторне використання і зрозумілість.


Парсинг аргументів: як shell розбиває рядок

Коли ви вводите команду в терміналі, операційна система (точніше — shell: bash, cmd, PowerShell) відповідає за розбиття рядка на окремі аргументи. Важливо розуміти правила, щоб ваша програма отримувала саме те, що ви задумали.

Роздільник — пробіл

./program Hello world

argc = 3: argv[1] = "Hello", argv[2] = "world". Кожне слово — окремий аргумент.

Рядки у подвійних лапках — один аргумент

./program "Hello world"

argc = 2: argv[1] = "Hello world". Лапки говорять shell: все всередині — один аргумент, навіть якщо є пробіли.

Порожній рядок

./program ""

argc = 2: argv[1] = "" (порожній рядок). Порожній аргумент — теж аргумент.

Спеціальні символи

./program file*.txt    // shell розкриє * у список файлів!
./program 'file*.txt'  // одинарні лапки у bash: передати буквально
ParsingDemo.cpp
#include <iostream>

int main(int argc, char* argv[])
{
    std::cout << "argc = " << argc << '\n';

    for (int i = 0; i < argc; ++i)
        std::cout << "argv[" << i << "] = \"" << argv[i] << "\"\n";

    return 0;
}
$ ./ParsingDemo one two three
argc = 4
argv[0] = "./ParsingDemo"
argv[1] = "one"
argv[2] = "two"
argv[3] = "three"

Повний практичний приклад: калькулятор з CLI

Побудуємо повноцінну маленьку програму-калькулятор, що приймає вираз <число> <оператор> <число> через аргументи командного рядка:

Calculator.cpp
#include <iostream>
#include <sstream>
#include <cstdlib>

int strToInt(const char* str, bool& success)
{
    std::stringstream ss(str);
    int result;
    success = static_cast<bool>(ss >> result);
    return result;
}

int main(int argc, char* argv[])
{
    // Перевірка: потрібно рівно 3 аргументи (число оператор число)
    if (argc != 4)
    {
        std::cout << "Використання: " << argv[0] << " <число> <оператор> <число>\n";
        std::cout << "Оператори:    + - * /\n";
        std::cout << "Приклад:      " << argv[0] << " 10 + 5\n";
        exit(1);
    }

    // Конвертуємо перше і третє аргументи в числа
    bool ok1, ok2;
    int left  = strToInt(argv[1], ok1);
    int right = strToInt(argv[3], ok2);

    if (!ok1 || !ok2)
    {
        std::cout << "Помилка: очікуються цілі числа.\n";
        exit(1);
    }

    // argv[2] — оператор (рядок, але нас цікавить перший символ)
    char op = argv[2][0];

    if (op == '+')
        std::cout << left << " + " << right << " = " << (left + right) << '\n';
    else if (op == '-')
        std::cout << left << " - " << right << " = " << (left - right) << '\n';
    else if (op == '*')
        std::cout << left << " * " << right << " = " << (left * right) << '\n';
    else if (op == '/')
    {
        if (right == 0)
        {
            std::cout << "Помилка: ділення на нуль!\n";
            exit(1);
        }
        std::cout << left << " / " << right << " = " << (left / right) << '\n';
    }
    else
    {
        std::cout << "Помилка: невідомий оператор '" << op << "'.\n";
        std::cout << "Допустимі: + - * /\n";
        exit(1);
    }

    return 0;
}
./Calculator демонстрація
$ ./Calculator 10 + 5
10 + 5 = 15
$ ./Calculator 100 / 7
100 / 7 = 14
$ ./Calculator 5 * 8
5 * 8 = 40
$ ./Calculator 10 / 0
Помилка: ділення на нуль!
$ ./Calculator
Використання: ./Calculator <число> <оператор> <число>
Оператори: + - * /

Рядок 16. if (argc != 4) — строга перевірка: саме 4 аргументи (argv[0] + 3 від користувача). Якщо менше або більше — помилка.

Рядок 38. char op = argv[2][0]argv[2] є рядком char*. Нас цікавить перший символ — оператор. argv[2][0] — читання нульового елементу рядку. Якщо користувач передав "+", то argv[2][0] == '+'.

Рядки 5–10. strToInt приймає додатковий параметр bool& success — прапорець успіху конвертації. Це дозволяє функції повідомляти, чи вдалась конвертація, без повернення спеціального «службового» значення на кшталт -1.


Зв'язок із вказівниками: char** argv зсередини

Оскільки ця стаття є частиною модуля про вказівники — розглянемо argv через призму того, що ми вивчили.

argv — це масив вказівників на рядки. Кожен елемент argv[i] є char* — вказівником на перший символ i-го рядка. Останній елемент argv[argc] завжди є nullptr.

ArgvPointers.cpp
#include <iostream>

int main(int argc, char* argv[])
{
    // argv[i] — вказівник на char (перший символ рядка)
    // argv[i][j] — j-й символ i-го аргументу

    for (int i = 0; i < argc; ++i)
    {
        char* arg = argv[i]; // вказівник на початок рядка

        std::cout << "argv[" << i << "]: ";

        // Ітеруємо по символах рядка вручну — через арифметику вказівників
        int j = 0;
        while (arg[j] != '\0')
        {
            std::cout << arg[j];
            ++j;
        }
        std::cout << " (довжина: " << j << ")\n";
    }

    // argv[argc] — гарантовано nullptr (NULL-термінатор масиву вказівників)
    std::cout << "argv[argc] == nullptr: "
              << (argv[argc] == nullptr ? "так" : "ні") << '\n';

    return 0;
}
./ArgvPointers Hello C++
$ ./ArgvPointers Hello C++
argv[0]: ./ArgvPointers (довжина: 14)
argv[1]: Hello (довжина: 5)
argv[2]: C++ (довжина: 3)
argv[argc] == nullptr: так
Loading diagram...
graph TD
    ArgV["argv (char**)"]
    ArgV --> A0["argv[0] → './ArgvPointers\0'"]
    ArgV --> A1["argv[1] → 'H','e','l','l','o','\0'"]
    ArgV --> A2["argv[2] → 'C','+','+','\0'"]
    ArgV --> AN["argv[3] = nullptr"]

argv — це вказівник на вказівники. argv[i] — вказівник на C-рядок (масив символів). argv[i][j] — j-й символ i-го аргументу. argv[argc] — гарантований nullptr, що дозволяє також ітерувати через while (*argv) замість лічильника argc.


Практичні завдання

Завдання

Рівень 1 — Базовий

Завдання 1. Напишіть програму EchoArgs, що виводить всі аргументи окрім argv[0] (ім'я програми), кожен на новому рядку. Якщо аргументів немає — вивести "Жодних аргументів не передано.".

Завдання 2. Що виведе команда? Поясніть значення argc і кожного argv[i]:

./MyProgram "one two" three "" four

Завдання 3. Напишіть програму CountChars, що приймає один рядковий аргумент і виводить кількість символів у ньому (не через strlen, а через власний цикл з вказівником або індексом).

Рівень 2 — Логіка

Завдання 4. Напишіть програму SumArgs, що приймає довільну кількість числових аргументів і виводить їхню суму. Приклад: ./SumArgs 10 20 30 40Sum: 100. Якщо якийсь аргумент не є числом — вивести помилку і завершити програму з кодом 1.

Завдання 5. Напишіть програму Repeat, що приймає два аргументи: рядок і ціле число. Виводить рядок стільки разів, скільки вказує число: ./Repeat hello 3 → виводить hello тричі кожен з нового рядка.

Завдання 6. Напишіть програму MaxOfArgs, що знаходить максимум серед переданих чисел: ./MaxOfArgs 7 2 15 3 11Max: 15. Якщо аргументів немає — вивести підказку.

Рівень 3 — Архітектура

Завдання 7. Реалізуйте просту утиліту FileInfo, що приймає один або кілька «імен файлів» як аргументи і для кожного виводить:

  • Ім'я файлу
  • Розширення (частина після останньої крапки)
  • Прапорець: «має розширення» або «без розширення»

Наприклад: ./FileInfo photo.jpg document.pdf readme

photo.jpg     → розширення: jpg
document.pdf  → розширення: pdf
readme        → без розширення

(Не потрібно відкривати файли — лише аналізувати рядки-аргументи.)


Підсумок

Синтаксис main

int main(int argc, char* argv[]). argc ≥ 1 завжди. argv[0] — ім'я програми. argv[argc]nullptr.

Доступ до аргументів

Перевіряйте argc перед доступом. argv[i] — C-рядок. argv[i][j] — символ. Ітерувати від i=1 для аргументів користувача.

Числова конвертація

std::stringstream ss(argv[i]); int n; ss >> n; — найпростіший спосіб. Перевіряйте успішність через if (!(ss >> n)).

Shell-парсинг

Пробіл = роздільник. Подвійні лапки = один аргумент. argc залежить від shell, not від вашої програми.

Завжди exit(1) при помилці

Код виходу 0 = успіх, не 0 = помилка. Це дозволяє скриптам і автоматизації перевіряти результат.

argv у пам'яті

argvchar**, масив вказівників на C-рядки. Кожен argv[i] — вказівник на перший символ. Гарантований nullptr в кінці.