Розбираємо вихідний код GNU Coreutils утиліта yes

GNU Coreutils

GNU Core Utilites — це набір утиліт для виконання базових операцій користувача: створення директорії, виведення файлу на екран і так далі. За задумом розробників, ці утиліти мають бути доступні в будь-якій операційній системі, що ми й спостерігаємо в даний час: для Windows є Cygwin, ну а про * nix і говорити нема чого. Зберегти однаковість роботи в різних системах допомагає стандарт POSIX, який Coreutils намагаються дотримуватися. Coreutils містить такі часто використовувані утиліти, як cat, tail, echo, wc та багато інших.

Для початку виберемо найтривіальнішу програму під назвою yes. Її простота дозволить розібратися з інструментами та бібліотеками, що використовуються в Coreutils.

Утиліта yes

Як мовиться в мані, все що вміє утиліта yes - це нескінченно виводити "yn" у stdout. Якщо ми передамо yes якісь аргументи, то замість «y» yes виводитиме аргументи через пробіл. Напевно, схожу програму писав кожен, хто починав вивчати C. А значить у багатьох є можливість порівняти свій підхід з тим, як це роблять суворі бородаті дядьки з GNU. Про практичне застосування yes трохи написано у Вікіпедії.

Вихідний код

Переходимо до вихідного коду. Дістати його можна або за допомогою apt-get source і отримати версію, яка використовується у вашій системі за промовчанням, або витягти нову версію з репозиторіїв. Ми виберемо другий варіант: він зручніший і звичніший.

  1. Coreutils: git clone git://git.sv.gnu.org/coreutils
  2. Gnulib (заглянемо туди кілька разів): git clone git://git.savannah.gnu.org/gnulib.git
Вихідний код yes міститься в одному файлі coreutils/src/yes.c , його і відкриємо.

Coding style

Перше, нащо звертаєш увагу - незвичне форматування коду. Почитати про нього можна у відповідному розділі GNU Coding Standards. Наприклад, при визначенні функції тип значення, що повертається, повинен розташовуватися на окремому рядку, як і відкриваюча дужка:

Для відступів та вирівнювання використовуються тільки пробіли. Між різними рівнями вкладеності різниця у відступі становить 2 пробіли. Особливо збочену форму мають фігурні дужки при операторах:

initialize_main

Перше, що робить програма, це виклик initialize_main. Ця функція призначена у тому, щоб програма виконала свої специфічні дії над аргументами. На практиці, у Coreutils немає жодної утиліти, яка використала б цю функцію для чогось корисного. Скрізь використовується заглушка, представлена ​​у файлі coreutils/src/system.h:

Назва програми

В утилітах Coreutils розрізняють дві назви програми:

  1. Офіційна назва, яку користувач не може змінити.
  2. Реальна назва файлу, що виконується.
Офіційна назва використовується при виведенні інформації про версію програми:

Причому ця назва ніяк не залежить від імені файлу, що виконується:

Така поведінка забезпечується спеціально визначеним на початку файлу макросом PROGRAM_NAME :

Реальна назва без жодних хитрощів береться з argv[0] і використовується при виведенні помилок та підказок:

Значення argv[0] поміщається у глобальну змінну program_name за допомогою виклику функції set_program_name у другому рядку main :

Функція set_program_name надається бібліотекою Gnulib. Відповідний код знаходиться в каталозі gnulib/lib/, у файлах progname.h і progname.c. Цікаво помітити, що set_program_name не простозберігає значення argv[0] у глобальну змінну program_name , оголошену в progname.h , але виконує додаткові перетворення, пов'язані з тонкощами використання GNU Libtool, інструменту для розробки динамічних бібліотек.

Інтернаціоналізація

Coreutils використовують у всьому світі, тому у всіх утилітах передбачена можливість локалізації. До того ж ця можливість забезпечується мінімальними зусиллями завдяки використанню пакета GNU gettext. Небагатьох здивує використання саме gettext, адже цей пакет поширився далеко за межі проекту GNU. Наприклад, інтернаціоналізація у моєму улюбленому web-фреймворку Django побудована саме на gettext. Про використання gettext спільно з різними мовами та фреймворками вже писали на хабрі.

Чудовою властивістю gettext є те, що він у всіх мовах використовується приблизно однаково, і C не є винятком. Тут є стандартна магічна функція _, використання якої можна знайти у функції usage:

Визначення функції _ знаходиться у вже знайомому нам файлі system.h :

Ініціалізація механізму інтернаціоналізації в Coreutils здійснюється викликом трьох функцій в main:

  • setlocale встановлює стандартну локаль оточення як робочу для програми
  • bindtextdomain каже, де шукати файл з перекладами для конкретного домену повідомлень
  • textdomain встановлює поточний домен повідомлень

Обробка помилок

Рухаючись далі за кодом main, ми зустрічаємо такий рядок:

Інтуїтивно можна подумати, що функції close_stdout закривається стандартний потік виведення, що виключає втрату даних, якщо ми підмінили stdout яким-небудь файловим дескриптором і використовуємо буферизований висновок. Але знайтивихідний код цієї функції та зрозуміти, що ж насправді там відбувається, чи виконуються якісь додаткові дії з підчистки ресурсів, у мене не вийшло.

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

У Gnulib є спеціальна функція parse_long_options для обробки аргументів --version і --help , які будь-яка програма GNU повинна підтримувати. Знаходиться вона у файлі gnulib/lib/long-options.c та використовує getopt_long у своїй роботі.

Вихідний код yes є класним прикладом роботи з getopt. Тут одночасно відсутня зайва для навчання складність з розбором десятків аргументів і використання всіх засобів getopt. Спочатку, звичайно, виконується виклик parse_long_options. Потім перевіряється, що більше ніяких опцій-ключів не передано й інші аргументи, якщо вони є, є довільними рядками:

Наступний код можна перекласти українською так: «Якщо в списку аргументів командою рядка нічого крім ключів --version і --help не було, то ми виводитимемо „y“ у stdout»:

Запис argv[argc] не є помилкою: стандарт ANSI C вимагає, щоб елемент argv[argc] був нульовим покажчиком.

Головний цикл

От ми й дісталися до самого функціоналу програми. Ось він весь, як є: