Разбираем исходный код GNU Coreutils: утилита yes
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 немного написано в Википедии.
Исходный код
- Coreutils: git clone git://git.sv.gnu.org/coreutils
- Gnulib (заглянем туда пару раз): git clone git://git.savannah.gnu.org/gnulib.git
Coding style
Первое, на что обращаешь внимание — непривычное форматирование кода. Почитать о нём можно в соответствующей главе GNU Coding Standards. Например, при определении функции тип возвращаемого значения должен располагаться на отдельной строке, как и открывающая скобка:
int main (int argc, char **argv)
Для отступов и выравнивания используются только пробелы. Между различными уровнями вложенности разница в отступе составляет 2 пробела. Особо извращённую форму имеют фигурные скобки при операторах:
if (x < foo (y, z)) haha = bar[4] + 5; else < while (z) < haha += foo (z, z); z--; >return ++x + bar (); >
12 строк
yes.c начинается с обязательного для всех GPL-програм комментария. Он уже успел намозолить мне глаза в других программах и необходимость его наличия была для меня загадкой. Оказывается, что текст этого комментария зафиксирован в инструкции по применению GPL. Именно в ней прописано, что все, кто желает выпускать своё ПО под GPL, должны добавлять эти 12 строк заявления о праве копирования в начало каждого файла исходного кода.
initialize_main
Первое, что делает программа, это вызов initialize_main . Эта функция предназначена для того, чтобы программа выполнила свои специфичные действия над аргументами. На практике, в Coreutils нет ни одной утилиты, которая бы использовала эту функцию для чего-то полезного. Везде используется заглушка, представленная в файле coreutils/src/system.h :
#ifndef initialize_main # define initialize_main(ac, av) #endif
Название программы
- Официальное название, которое пользователь не может изменить.
- Реальное название исполняемого файла.
user@laptop:~$ yes --version yes (GNU coreutils) 8.5 Usage: yes [STRING]. or: yes OPTION
Причём это название никак не зависит от имени исполняемого файла:
user@laptop:~$ /usr/bin/yes --version yes (GNU coreutils) 8.5 user@laptop:~$ cp /usr/bin/yes ./foo user@laptop:~$ ./foo --version yes (GNU coreutils) 8.5
Такое поведение обеспечивается специально определённым в начале файла макросом PROGRAM_NAME :
/* The official name of this program (e.g., no `g' prefix). */ #define PROGRAM_NAME "yes"
Реальное название без всяких хитростей берётся из argv[0] и используется при выводе ошибок и подсказок:
user@laptop:~$ yes --help Usage: yes [STRING]. or: yes OPTION user@laptop:~$ /usr/bin/yes --help Usage: /usr/bin/yes [STRING]. or: /usr/bin/yes OPTION
Значение argv[0] помещается в глобальную переменную program_name с помощью вызова функции set_program_name во второй строке main :
set_program_name (argv[0]);
Функция 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 :
void usage (int status)
Определение функции _ находится в уже знакомом нам файле system.h :
#define _(msgid) gettext (msgid)
Инициализация механизма интернационализации в Coreutils производится вызовом трёх функций в main :
setlocale (LC_ALL, ""); bindtextdomain (PACKAGE, LOCALEDIR); textdomain (PACKAGE);
- setlocale устанавливает стандартную локаль окружения в качестве рабочей для приложения
- bindtextdomain говорит, где искать файл с переводами для конкретного домена сообщений
- textdomain устанавливает текущий домен сообщений
Обработка ошибок
Двигаясь дальше по коду main , мы встречаем такую строку:
atexit (close_stdout);
Интуитивно можно подумать, что в функции close_stdout закрывается стандартный поток вывода, что исключает потерю данных, если мы подменили stdout каким-нибудь файловым дескриптором и используем буферизированный вывод. Но найти исходный код этой функции и понять, что же на самом деле там происходит, выполняются ли какие-нибудь дополнительные действия по подчистке ресурсов, у меня не получилось.
Аргументы командной строки
Это последний вопрос, который не касается работы самой программы. Здесь, как и в случае с интернационализацией, используется проверенное временем и пролезшее во многие проекты (например, в Python) решение — модуль getopt. Этот модуль очень прост: фактически, от разработчика требуется вызывать в цикле одну из функций getopt или getopt_long . Подробнее о getopt можно почитать в интернете, да и на хабре о нём тоже писали.
В Gnulib есть специальная функция parse_long_options для обработки аргументов —version и —help , которые любое GNU-приложение обязано поддерживать. Находится она в файле gnulib/lib/long-options.c и использует getopt_long в своей работе.
Исходный код yes является классным примером работы с getopt. Тут одновременно отсутствует излишняя для обучения сложность с разбором десятков аргументов и присутствует использование всех средств getopt. Сначала, естественно, выполняется вызов parse_long_options . Затем проверяется, что больше никаких опций-ключей не передано и остальные аргументы, если они есть, являются просто произвольными строками:
parse_long_options (argc, argv, PROGRAM_NAME, PACKAGE_NAME, Version, usage, AUTHORS, (char const *) NULL); if (getopt_long (argc, argv, "+", NULL, NULL) != -1) usage (EXIT_FAILURE);
Следующий код можно перевести на русский так: «Если в списке аргументов командой строки ничего кроме ключей —version и —help не было, то мы будем выводить „y“ в stdout»:
if (argc
Запись в argv[argc] не является ошибкой: стандарт ANSI C требует, чтобы элемент argv[argc] был нулевым указателем.
Главный цикл
Ну вот мы и добрались до самого функционала программы. Вот он весь, как есть:
while (true)
Здесь можно отметить, что все действия выполняются внутри условия if , а не в его теле. Значит, Кёрниган и Ритчи не врали, когда писали, что опытный C-программист реализует копирование строк так:
while (*dst++ = *src++) ;
Команда yes
Бесконечно выводит на экран заданную строку или y (да). Используется для автоматического подтверждения запросов других команд в командной строке.
Синтаксис
Без аргументов
Если выполнить команду yes без каких либо аргументов, то на экран бесконечно будет выводиться символ «y» (каждый символ на новой строке). Данное поведение используется для автоматического подтверждения запросов, при выполнении других команд (см. примеры ниже).
$ yes y y y y .
С указанием строки
yes строка
Если для команды yes задать строку , то на экран будет бесконечно выводиться эта строка (каждая строка на новой строке).
$ yes I am the best of the best I am the best of the best I am the best of the best I am the best of the best I am the best of the best .
Опции
У команды yes всего 2 опции:
Вывести информацию о команде yes.
Вывести версию утилиты yes.
Примеры использования команды yes
Обычно команда yes используется для того, чтобы при выполнении других команд или скриптов, которые выдают пользователю запросы, требующие ввести y (yes) или n (no), автоматически вводилось y (yes) на каждый запрос. То есть, чтобы избавить пользователя от необходимости самостоятельно отвечать y (Да) на каждый запрос.
В какой-то степени данная команда уже устарела, потому что многие команды и скрипты имеют опции -y или -f (force), выполняющие аналогичные действия (принудительный ответ y (Да) на каждый запрос).
Отвечаем Да на каждый запрос при восстановлении файловой системы
При проверке файловой системы командой fsck могут выдаваться запросы на исправление ошибок в файловой системе. Чтобы автоматически отвечать Да на каждый запрос, можно использовать следующую команду:
yes | fsck /dev/hdname
Отвечаем Да при удалении файлов
Выполним команду rm , чтобы удалить все файлы с расширением .txt
yes | rm *.txt
Данная команда эквивалентна команде:
rm -f *.txt
Создание большого файла
Команда yes может использоваться для создания больших файлов, заполненных данными. Например, это может потребоваться при тестировании программ.
Создадим файл myfile.txt и запишем в него 1000 одинаковых строк:
yes 'Some test text for the file' | head -1000 > myfile.txt
Как использовать команду yes в Linux
Фатмавати Ахмад Заенури/Shutterstock.com
Команда yes кажется слишком простой, чтобы иметь какое-либо практическое применение, но в этом руководстве мы покажем вам ее приложение и как извлечь выгоду из его положительного положительного эффекта в Linux и macOS.
Команда да
Команда yes является одной из самых простых команд в Linux и других Unix-подобных операционных системах, таких как macOS. Под простым мы подразумеваем простое в его использовании и первоначальной реализации. Исходный код исходной версии, выпущенной в System 7 Unix и созданной Кеном Томпсоном, составляет всего шесть строк кода.
Но не списывай на то, что это простая маленькая команда. Это может быть использовано несколькими интересными и полезными способами.
Что да делает?
При использовании без каких-либо параметров командной строки команда yes ведет себя так, как будто вы вводите «y» и снова нажимаете Enter, снова и снова (и снова и снова). Очень быстро. И это будет продолжаться до тех пор, пока вы не нажмете Ctrl + C, чтобы прервать его.
Фактически, yes можно использовать для многократного создания любого сообщения, которое вы выберете. Просто введите yes , пробел, строку, которую вы хотите использовать, и нажмите Enter. Это часто используется, чтобы yes генерировал выходной поток строк «yes» или «no».
да да
да все что угодно
Но что это значит?
Вывод из yes может быть передан в другие программы или сценарии.
Это звучит знакомо? Вы запускаете длинный процесс и уходите, оставляя его работать. Когда вы вернетесь на свой компьютер, процесс вообще не завершится. В ваше отсутствие он задал вам вопрос и ждет ответа «да» или «нет».
Если вы заранее знаете, что все ваши ответы будут положительными («да» или «y») или отрицательными («нет» или «n»), вы можете использовать yes , чтобы предоставить эти ответы для вы. После этого ваш длительный процесс будет завершен без присмотра, а yes предоставит ответы на любые вопросы, которые задает процесс.
Использование yes со скриптами
Посмотрите на следующий скрипт оболочки Bash. (Нам нужно представить, что это часть гораздо большего скрипта, выполнение которого займет значительное время.)
#!/Bin/Баш # . # в середине длинного скрипта # получить ответ от пользователя # . echo "Ты счастлив продолжить? [y, n]" читать ввод # мы получили входное значение? if ["$ input" == ""]; затем echo "Ничего не было введено пользователем" # это был у или да? elif [["$ input" == "y"]] || [["$ input" == "yes"]]; затем echo "Положительный ответ: $ input" # относиться ко всему остальному как к отрицательному ответу еще echo "отрицательный ответ: $ input" ц
Этот скрипт задает вопрос и ожидает ответа. Логический поток в скрипте определяется входом от пользователя.
- «Да» или «у» означает положительный ответ.
- Любой другой вклад считается отрицательным ответом.
- Нажатие Enter без ввода текста ничего не делает.
Чтобы проверить это, скопируйте скрипт в файл и сохраните его как long_script.sh . Используйте chmod , чтобы сделать его исполняемым.
chmod + x long_script.sh
Запустите скрипт с помощью следующей команды. Попробуйте ввести «yes», «y» и все остальное в качестве ввода, включая нажатие клавиши «Enter» без ввода текста.
./ long_script.sh
Чтобы yes предоставил наш ответ на вопрос сценария, направьте вывод из yes в сценарий.
да | ./long_script.sh
Некоторые сценарии более жесткие в своих требованиях и принимают только полное слово «да» как положительный ответ. Вы можете указать «yes» в качестве параметра для yes следующим образом:
да да | ./long_script.sh
Не говори да, не думая об этом
Вы должны быть уверены, что входные данные, которые вы собираетесь передать в сценарий или программу, определенно дадут вам ожидаемый результат. Чтобы принять это решение, вы должны знать вопросы и каковы ваши ответы.
Логика в сценарии, команде или программе может не соответствовать вашим ожиданиям. В нашем примере сценария вопрос мог звучать так: «Вы хотите остановиться? [y, n]. »Если бы это было так, отрицательный ответ позволил бы продолжить выполнение сценария.
Вы должны быть знакомы со сценарием, командой или программой, прежде чем беспечно направить в него yes .
Использование yes с командами
В зачаточном состоянии yes будет использоваться с другими командами Linux.С тех пор большинство этих других команд Linux имеют свой собственный способ запуска без участия человека. yes больше не требуется для этого.
Давайте возьмем в качестве примера менеджер пакетов Ubuntu apt-get . Чтобы установить приложение без необходимости нажимать «y» в середине установки, yes будет использоваться следующим образом:
да | sudo apt-get install fortune-mod
Тот же результат может быть достигнут с помощью опции -y (предположим, да) в apt-get :
sudo apt-get -y install fortune-mod
Вы увидите, что apt-get даже не спрашивает, как обычно: «Хотите продолжить? [Да/Нет] »вопрос. Предполагалось, что ответ будет «да».
На других дистрибутивах Linux ситуация такая же. В Fedora вы бы использовали команду такого типа сразу:
да | ни установить предсказатель мод
Менеджер пакетов dnf заменил yum , а dnf имеет собственную опцию -y (предположим, да).
dnf -y установить fortune-mod
То же самое относится к cp , fsck и rm . Каждая из этих команд имеет свои собственные опции -f (force) или -y (предположим, да).
Так что кажется, что yes отправлено только для работы со скриптами? Не совсем. У старой собаки еще есть несколько уловок.
Некоторые Дальнейшие да Уловки
Вы можете использовать yes с последовательностью цифр, сгенерированной seq , чтобы управлять циклом повторяющихся действий.
Этот однострочник выводит сгенерированные цифры в окно терминала и затем вызывает sleep в течение одной секунды.
Вместо того, чтобы просто отображать цифры в окне терминала, вы можете вызвать другую команду или скрипт. Эта команда или сценарий даже не должны использовать цифры, и они только там, чтобы запустить каждый цикл цикла.
yes "$ (seq 1 20)" | пока читаешь цифру; сделать эхо цифру; сон 1; Готово
Иногда полезно иметь большой файл для тестирования. Возможно, вы хотите попрактиковаться в использовании команды zip, или вы хотите иметь большой файл для тестирования загрузок FTP.
Вы можете быстро создавать большие файлы с помощью yes . Все, что вам нужно сделать, это дать ему длинную строку текста для работы и перенаправить вывод в файл. Не ошибись; эти файлы будут быстро расти. Будьте готовы нажать Ctrl + C в течение нескольких секунд.
yes длинная строка бессмысленного текста для заполнения файла> test.txt
ls -lh test.txt
wc test.txt
Сгенерированный файл занял около пяти секунд на тестовом компьютере, который использовался для исследования этой статьи. ls сообщает, что его размер составляет 557 МБ, а wc сообщает, что в нем содержится 12,4 миллиона строк.
Мы можем ограничить размер файла, включив head в нашу командную строку. Мы говорим, сколько строк включить в файл. -50 означает, что head пропустит всего 50 строк в файл test.txt .
yes длинная строка бессмысленного текста для заполнения файлов | head -50> test.txt
Как только в файле test.txt будет 50 строк, процесс остановится. Вам не нужно использовать Ctrl + C. Это происходит изящно по собственному желанию.
wc сообщает, что в файле содержится ровно 50 строк, 400 слов и его размер составляет 2350 байт.
Несмотря на то, что она по-прежнему полезна для подачи ответов в длительные сценарии (и некоторые другие приемы), команда yes не будет частью вашего ежедневного набора команд. Но когда вам это нужно, вы обнаружите, что это сама простота – и все в шести строках золотого кода.
При подготовке материала использовались источники:
https://habr.com/ru/articles/133408/
https://pingvinus.ru/note/cmd-yes