Добро пожаловать! Как человек, который занимается программированием более 2 лет, меня все больше и больше привлекают низкоуровневые проекты; именно поэтому я решил сделать элементарную компьютерную оболочку полностью с нуля. Сегодня я проведу вас через это. Давайте начнем!

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

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

После этого вы должны разобрать этот ввод на несколько аргументов. Позже вы поймете, почему нам нужно это сделать.

Наконец, мы должны выполнить эти аргументы. Это немного сложнее, чем вы думаете, но эй, это то, что делает его забавным.

Теперь пришло время начать кодирование! Начнем с настройки.

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

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

Теперь, когда мы закончили с настройкой, мы создадим функцию, которая принимает пользовательский ввод. Мы собираемся создать его с нуля, а это значит, что нам придется самостоятельно управлять памятью.

Это большой кусок кода, поэтому давайте рассмотрим его вместе. Для начала создается несколько переменных. buffer - это место, где ввод будет сохранен и возвращен. bufsize — это максимальный размер, выделенный для команды (мы могли бы сделать это динамическим, но для простоты это заданный размер). line просто отслеживает, сколько символов было прочитано. Как только эти переменные инициализированы, размер 100 символов выделяется через malloc(). Если это не удается, это будет перехвачено оператором if, и программа завершится с ошибкой. В случае успеха программа соберет входные данные и вернет их как char*

Вау! Это было много. Перейдем к парсеру.

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

Хорошо, так. Эта функция принимает входные данные из предыдущей ( char* ) и предназначена для возврата аргументов в виде массива строк ( char** ). Аргументы будут разбиты пробелами (например, hello world будет разбито на ["hello", "world"] . Это работает путем отслеживания текущего слова (переменные, начинающиеся с current ) и массива слов (переменные, начинающиеся с all ). Затем ввод проходит через цикл for. По мере продвижения символ добавляется к current_token . Если места недостаточно, он выделяет больше. Затем, если встречается пробел, он очищает переменные current и добавляет текущее слово на all_tokens Вот и все!

Наконец, мы должны выполнить команду.

Теперь, когда команда разбита на несколько аргументов, мы можем их выполнить. Давайте посмотрим, как мы это делаем. Не волнуйтесь, это легко :)

Есть несколько команд, которые работают не так, как другие; cd и quit. cd означает «изменить каталог». Эта команда не работает, потому что мы разветвляем текущий процесс и запускаем команду там. Это означает, что он разветвит процесс, изменит каталог там, а затем вернется обратно. Решение этой проблемы простое — мы просто меняем его сами с помощью chdir() . Что касается quit, мы просто называем exit().

Если команда не является ни тем, ни другим, это еще проще! Мы просто разветвляем процесс с помощью fork() и выполняем их аргументы с помощью execvp(). Если это возвращает код ошибки ( -1 ), мы предупреждаем пользователя и выходим.

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