Шелл своими руками

Добавил пользователь Владимир З.
Обновлено: 04.10.2024

Совет Не стоит сдавать или использовать (даже в изменённом виде) приведённый ниже код в качестве домашнего проекта в школе или вузе. Многие преподаватели знают об оригинальной статье и уличат вас в обмане.

Жизненный цикл командной оболочки

Оболочка выполняет три основные операции за время своего существования:

  1. Инициализация: на этом этапе она читает и исполняет свои файлы конфигурации. Они изменяют её поведение.
  2. Интерпретация: далее оболочка считывает команды из stdin и исполняет их.
  3. Завершение: после исполнения основных команд она исполняет команды выключения, освобождает память и завершает работу.

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

В примере выше можно увидеть функцию lsh_loop() , которая будет циклически интерпретировать команды. Реализацию рассмотрим чуть ниже.

Базовый цикл командной оболочки

В первую очередь нам нужно подумать о том, как программа должна запускаться. И здесь важно понимать, что делает оболочка во время цикла. Простой способ обработки команд состоит из трех шагов:

  1. Чтение: считывание команды со стандартных потоков.
  2. Парсинг: распознавание программы и аргументов во входной строке.
  3. Исполнение: запуск распознанной команды.

Эта идея реализована в функции lsh_loop() :

Пройдемся по коду. Первые несколько строк — это просто объявления. Цикл с постусловием более удобен для проверки состояния переменной, поскольку выполняется перед проверкой ее значения. Внутри цикла выводится приглашение ввода, вызываются функции для чтения входной строки и разбиения строки на аргументы, а затем исполняются аргументы. Далее освобождается память, выделенная под строку и аргументы. Стоит обратить внимание, что в коде используется переменная состояния, возвращаемая в lsh_execute() и определяющая, когда нужно выйти из функции.

Чтение строки

Чтение строки из стандартного потока ввода — это вроде бы просто, но в C это может вызвать много хлопот. Беда в том, что никто не знает заранее, сколько текста пользователь введет в командную оболочку. Нельзя просто выделить блок и надеяться, что пользователи не выйдут за него. Вместо этого нужно перераспределять выделенный блок памяти, если пользователи выйдут за его пределы. Это стандартное решение в C, и именно оно будет использоваться для реализации lsh_read_line() .

В первой части много объявлений. Стоит отметить, что в коде используется старый стиль C, а именно объявление переменных до основной части кода. Основная часть функции находится внутри, на первый взгляд, бесконечного цикла while(1) . В цикле символ считывается и сохраняется как int , а не char (EOF — это целое число, а не символ, поэтому для проверки используйте int ). Если это символ перевода строки или EOF, мы завершаем текущую строку и возвращаем ее. В обратном случае символ добавляется в существующую строку.

Затем мы проверяем, выходит ли следующий символ за пределы буфера. Если это так, то перераспределяем буфер (при этом проверяем его на наличие ошибок распределения) и продолжаем исполнение.

Те, кто знаком с новыми версиями стандартной библиотеки C, могут заметить, что в stdio.h есть функция getline() , которая выполняет большую часть работы, реализованной в коде выше. Эта функция была расширением GNU для библиотеки C до 2008 года, а затем была добавлена в спецификацию, поэтому большинство современных Unix-систем уже идут с ней в комплекте. С getline функция становится тривиальной:

Парсинг строки

Теперь всё, что нам нужно сделать — разбить строку на части, используя пробелы в качестве разделителей. Это значит, что мы можем использовать классическую библиотечную функцию strtok .

Реализация этой функции подозрительно похожа на lsh_read_line() , и это неспроста! Здесь используется та же стратегия, только вместо нуль-терминированного массива символов мы используем нуль-терминированный массив указателей.

Мы начинаем разбиение, вызывая strtok . Она возвращает указатель на первый кусок строки (токен). Вообще strtok() возвращает указатели на места в строке и помещает нуль-терминаторы в конце каждого токена. Эти указатели мы храним в отдельном массиве.

При необходимости мы перераспределим массив указателей. Повторяем процесс до тех пор, пока strtok не перестанет возвращать токены, и завершаем массив токенов нуль-терминатором.

Теперь у нас есть массив токенов, готовых к исполнению.

Как командные оболочки запускают процессы

Теперь мы добрались до самой сути того, что делает оболочка. Запуск процессов — это основная функция командных оболочек. Поэтому если вы создаёте оболочку, то должны точно знать, что происходит с процессами и как они запускаются. Именно поэтому сейчас мы поговорим о процессах в Unix.

В Unix есть только два способа запуска процессов. Первый (который не будем брать в счет) — это Init . Видите ли, когда загружается Unix-система, загружается её ядро. После загрузки и инициализации ядро запускает только один процесс, который называется Init . Этот процесс выполняется в течение всего времени работы компьютера, и управляет загрузкой остальных процессов, которые необходимы для его работы.

Это может показаться проблемой. Обычно, когда вы хотите запустить новый процесс, вам не нужна копия уже работающей программы — вы хотите запустить другую программу. Для этого нужно использовать системный вызов exec() . Он заменяет текущую запущенную программу совершенно новой. Это значит, что при вызове exec операционная система останавливает процесс, загружает новую программу и запускает ее на том же месте. Вызов exec() не возвращает процесс, если нет ошибки.

Благодаря этим двум системным вызовам и возможен запуск большинства программ в Unix. Сперва существующий процесс раздваивается на родительский и дочерний, а затем дочерний процесс использует exec() для замены себя новой программой. Родительский процесс может продолжать делать другие вещи, а также следить за своими дочерними элементами, используя системный вызов wait() .

Да уж, информации немало. Давайте посмотрим на код запуска программы:

Эта функция принимает список аргументов, которые мы создали ранее. Затем она разворачивает процесс и сохраняет возвращаемое значение. Как только fork() возвращает значение, мы получаем два параллельных процесса. Дочернему процессу соответствует первое условие if (где pid == 0 ).

В дочернем процессе мы хотим запустить команду, заданную пользователем. Поэтому мы используем один из вариантов системного вызова exec , execvp . Разные варианты exec делают разные вещи. Одни принимают переменное количество строковых аргументов, другие берут список строк, а третьи позволяют указать окружение, в котором выполняется процесс. Этот конкретный вариант принимает имя программы и массив (также называемый вектором, отсюда 'v' ) строковых аргументов (первым должно быть имя программы). 'p' означает, что вместо предоставления полного пути к файлу программы для запуска мы укажем только её имя, а также скажем операционной системе искать её самостоятельно.

Третье условие означает, что вызов fork() выполнен успешно. Там находится родительский процесс. Мы знаем, что потомок собирается исполнить процесс, поэтому родитель должен дождаться завершения команды. Мы используем waitpid() для ожидания изменения состояния процесса. К сожалению, у waitpid() есть много опций (например, exec() ). Процессы могут изменять свое состояние множеством способов, и не все состояния означают, что процесс завершился. Процесс может либо завершиться обычным путём (успешно либо с кодом ошибки), либо быть остановлен сигналом. Таким образом, мы используем макросы, предоставляемые waitpid() , чтобы убедиться, что процесс завершен. Затем функция возвращает 1 как сигнал вызывающей функции, что она снова может вывести приглашение ввода.

Встроенные функции оболочки

Возможно, вы заметили, что функция lsh_loop() вызывает lsh_execute() , но выше мы назвали нашу функцию lsh_launch() . Это было намеренно! Дело в том, что большинство команд, которые исполняет оболочка, являются программами — но не все. Некоторые из команд встроены прямо в оболочку.

Причина довольно проста. Если вы хотите сменить каталог, вам нужно использовать функцию chdir() . Дело в том, что текущий каталог является свойством процесса. Итак, допустим, вы написали программу cd , которая изменяет каталог. Она просто меняет свой текущий каталог и завершается, но текущий каталог родительского процесса не изменится. Вместо этого процесс оболочки должен исполнить chdir() , чтобы обновить свой текущий каталог. Затем, когда он запускает дочерние процессы, они также наследуют этот каталог.

Аналогично программа с именем exit не сможет выйти из командной оболочки, которая ее вызвала. Эта команда также должна быть встроена в оболочку. Кроме того, большинство оболочек настраиваются с помощью сценариев конфигурации, таких как ~/.bashrc . Эти сценарии используют команды, которые изменяют работу оболочки. Сами же команды могут изменить работу оболочки, если только они были реализованы внутри самой оболочки.

Соответственно, имеет смысл добавить некоторые команды в оболочку. В эту оболочку мы добавим cd , exit и help . А вот и реализация этих функций:

Код состоит из трёх частей. Первая часть содержит предваряющее объявление функций. Предваряющее объявление — это когда вы объявляете (но не определяете) что-то, чтобы можно было использовать это имя до его определения. lsh_help() — причина, по которой мы делаем это. Она использует массив встроенных функций, а сами массивы содержат lsh_help() . Самый простой способ разбить этот цикл зависимостей — это предваряющее объявление.

Следующая часть представляет собой массив имён встроенных команд, за которыми следует массив соответствующих функций. Это значит, что в будущем встроенные команды могут быть добавлены путем изменения этих массивов, а не большого оператора switch где-то в коде. Если вы смущены объявлением builtin_func , все в порядке. Это массив указателей на функции (которые принимают массив строк и возвращают int ). Любое объявление, включающее указатели на функции в C, может стать действительно сложным.

Объединение встроенных функций и процессов

Последний недостающий фрагмент головоломки заключается в реализации функции lsh_execute() , которая либо запускает либо встроенный, либо другой процесс.

Код проверяет, является ли команда встроенной. Если это так, то запускает её, а в противном случае вызывает lsh_launch() , чтобы запустить процесс.

Собираем все вместе

Вот и весь код, который входит в командную оболочку. Если вы внимательно читали статью, то должны были понять, как работает оболочка. Чтобы испробовать оболочку (на Linux), вам нужно скопировать эти сегменты кода в файл main.c и скомпилировать его. Обязательно включите только одну реализацию lsh_read_line() . Вам нужно будет включить следующие заголовочные файлы:

Чтобы скомпилировать файл, введите в терминале gcc -o main main.c , а затем ./main , чтобы запустить.

Кроме того, все исходники доступны на GitHub.

Подводя итоги

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

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

Чтобы разобраться в системных вызовах, рекомендуем обратиться к мануалу: man 3p . Если вы не знаете, какой интерфейс вам предлагают стандартная библиотека C и Unix, советуем посмотреть спецификацию POSIX, в частности раздел 13.

Всем здравствуйте!
Подскажите пожалуйста. как правильно подключать- по положению и цвету, проводки в шелле, и соответственно проводки к картриджу.
Разъем т.н. байонетный.,А головы разные..
Есть какие то разночтения, погуглил- в одном месте так, в другом иначе..
Или по схемам одно, а на картридже маркировано иначе..
Короче,есть ли в этом деле, строгие указания, для приведения к общему знаменателю. или зависит от конкретных вариантов.
Спасибо!!
С ув. Павел

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

Ну вопрос больше дилетантский, поэтому такой ответ. Еще много зависит от распайки фоно кабеля, часто соединяют минус L,R вместе ,что совсем не по фен-шую, про тестеры опытные виниловоды не говорят

Ну хорошо,я дилетант.. Даже не отрицаю.
Т.е, раз дилетант,и вопросы тоже дилетанские.. Значит -помогать, этому лоху.и делиться опытом, нет смысла..
Но если , все понимающие специалисты, пишут свои мнения, ..и они все разные. То значит, какая то проблема в этом вопросе присутствует.
Пошарив в гугле, увидел что однообразия в мнениях нет. Поэтому и обратился за компетентным и аргументированным советом..
Ну,а.. в общем, спасибо.. всем успехов и хорошей музыки..
Павел

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

(10-02-2018 12:16) ДРЕГ1 писал(а): Ну так чтобы было однообразие мнений - берете самый паршивый тестер, звоните в головке две катушки по сопротивлению и на концы цепляете провода и еще раз звоните для проверки.
На маркировку лучше не смотреть, т.к у разных производителей свои обозначения, и могут быть банальные ошибки при сборке и распайке. Ото в сети и вся проблема.

Как бы все правильно, но ведь при этом нет гарантии в том, что одна из катушек может быть включена в противофазе.

(10-02-2018 12:23) Pinaevv писал(а): Как бы все правильно, но ведь при этом нет гарантии в том, что одна из катушек может быть включена в противофазе.

Игнорируемых пользователей: 11.

Игнорируемых пользователей: 11.

Именно так и подключается, как на схеме изображено. Производитель, или маркирует буквами выводы головки, или цветовыми точками. Если смотреть на головку со стороны пинов, правый канал не всегда справа, а левый не всегда слева, но маркировка - или оттиснутые буквы или цветовые точки, всегда будет присутствовать. Вверху всегда располагаются сигнальные контакты, внизу - возвратные. Красный провод - сигнал правого канала, белый - сигнал левого канала, зелёный -возвратный правого, а синий - возвратный левого. Контакты в шелле желательно прозвонить с выводами фоно кабеля ( увязать). Если старая вертушке с родным фонокабелем, красный тюльпан -это правый канал, левый -это белый. Сигналы каждого канала прозвонить на выводах шелла, когда он стоит в байонете и соответственно туда подсоеденить красный и белый провода шелла. С возвратными контактами фонокабеля, точно так же - зелёный возврат правого канала, синий -возврат левого. Направление проводов шелла тоже имеет значение. Пины головки по диаметру не совпадают с пинами шелла. Лепестки проводов шелла имеют маркировку 1,0 и 1,2.

Timeweb - компания, которая размещает проекты клиентов в Интернете, регистрирует адреса сайтов и предоставляет аренду виртуальных и физических серверов. Разместите свой сайт в Сети - расскажите миру о себе!

Виртуальный хостинг

Быстрая загрузка вашего сайта, бесплатное доменное имя, SSL-сертификат и почта. Первоклассная круглосуточная поддержка.

Производительность и масштабируемые ресурсы для вашего проекта. Персональный сервер по цене виртуального хостинга.

Выделенные серверы

Быстрая загрузка вашего сайта, бесплатное доменное имя, SSL-сертификат и почта. Первоклассная круглосуточная поддержка.

Совет Не стоит сдавать или использовать (даже в изменённом виде) приведённый ниже код в качестве домашнего проекта в школе или вузе. Многие преподаватели знают об оригинальной статье и уличат вас в обмане.

Жизненный цикл командной оболочки

Оболочка выполняет три основные операции за время своего существования:

  1. Инициализация: на этом этапе она читает и исполняет свои файлы конфигурации. Они изменяют её поведение.
  2. Интерпретация: далее оболочка считывает команды из stdin и исполняет их.
  3. Завершение: после исполнения основных команд она исполняет команды выключения, освобождает память и завершает работу.

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

В примере выше можно увидеть функцию lsh_loop() , которая будет циклически интерпретировать команды. Реализацию рассмотрим чуть ниже.

Базовый цикл командной оболочки

В первую очередь нам нужно подумать о том, как программа должна запускаться. И здесь важно понимать, что делает оболочка во время цикла. Простой способ обработки команд состоит из трех шагов:

  1. Чтение: считывание команды со стандартных потоков.
  2. Парсинг: распознавание программы и аргументов во входной строке.
  3. Исполнение: запуск распознанной команды.

Эта идея реализована в функции lsh_loop() :

Пройдемся по коду. Первые несколько строк — это просто объявления. Цикл с постусловием более удобен для проверки состояния переменной, поскольку выполняется перед проверкой ее значения. Внутри цикла выводится приглашение ввода, вызываются функции для чтения входной строки и разбиения строки на аргументы, а затем исполняются аргументы. Далее освобождается память, выделенная под строку и аргументы. Стоит обратить внимание, что в коде используется переменная состояния, возвращаемая в lsh_execute() и определяющая, когда нужно выйти из функции.

Чтение строки

Чтение строки из стандартного потока ввода — это вроде бы просто, но в C это может вызвать много хлопот. Беда в том, что никто не знает заранее, сколько текста пользователь введет в командную оболочку. Нельзя просто выделить блок и надеяться, что пользователи не выйдут за него. Вместо этого нужно перераспределять выделенный блок памяти, если пользователи выйдут за его пределы. Это стандартное решение в C, и именно оно будет использоваться для реализации lsh_read_line() .

В первой части много объявлений. Стоит отметить, что в коде используется старый стиль C, а именно объявление переменных до основной части кода. Основная часть функции находится внутри, на первый взгляд, бесконечного цикла while(1) . В цикле символ считывается и сохраняется как int , а не char (EOF — это целое число, а не символ, поэтому для проверки используйте int ). Если это символ перевода строки или EOF, мы завершаем текущую строку и возвращаем ее. В обратном случае символ добавляется в существующую строку.

Затем мы проверяем, выходит ли следующий символ за пределы буфера. Если это так, то перераспределяем буфер (при этом проверяем его на наличие ошибок распределения) и продолжаем исполнение.

Те, кто знаком с новыми версиями стандартной библиотеки C, могут заметить, что в stdio.h есть функция getline() , которая выполняет большую часть работы, реализованной в коде выше. Эта функция была расширением GNU для библиотеки C до 2008 года, а затем была добавлена в спецификацию, поэтому большинство современных Unix-систем уже идут с ней в комплекте. С getline функция становится тривиальной:

Парсинг строки

Теперь всё, что нам нужно сделать — разбить строку на части, используя пробелы в качестве разделителей. Это значит, что мы можем использовать классическую библиотечную функцию strtok .

Реализация этой функции подозрительно похожа на lsh_read_line() , и это неспроста! Здесь используется та же стратегия, только вместо нуль-терминированного массива символов мы используем нуль-терминированный массив указателей.

Мы начинаем разбиение, вызывая strtok . Она возвращает указатель на первый кусок строки (токен). Вообще strtok() возвращает указатели на места в строке и помещает нуль-терминаторы в конце каждого токена. Эти указатели мы храним в отдельном массиве.

При необходимости мы перераспределим массив указателей. Повторяем процесс до тех пор, пока strtok не перестанет возвращать токены, и завершаем массив токенов нуль-терминатором.

Теперь у нас есть массив токенов, готовых к исполнению.

Как командные оболочки запускают процессы

Теперь мы добрались до самой сути того, что делает оболочка. Запуск процессов — это основная функция командных оболочек. Поэтому если вы создаёте оболочку, то должны точно знать, что происходит с процессами и как они запускаются. Именно поэтому сейчас мы поговорим о процессах в Unix.

В Unix есть только два способа запуска процессов. Первый (который не будем брать в счет) — это Init . Видите ли, когда загружается Unix-система, загружается её ядро. После загрузки и инициализации ядро запускает только один процесс, который называется Init . Этот процесс выполняется в течение всего времени работы компьютера, и управляет загрузкой остальных процессов, которые необходимы для его работы.

Это может показаться проблемой. Обычно, когда вы хотите запустить новый процесс, вам не нужна копия уже работающей программы — вы хотите запустить другую программу. Для этого нужно использовать системный вызов exec() . Он заменяет текущую запущенную программу совершенно новой. Это значит, что при вызове exec операционная система останавливает процесс, загружает новую программу и запускает ее на том же месте. Вызов exec() не возвращает процесс, если нет ошибки.

Благодаря этим двум системным вызовам и возможен запуск большинства программ в Unix. Сперва существующий процесс раздваивается на родительский и дочерний, а затем дочерний процесс использует exec() для замены себя новой программой. Родительский процесс может продолжать делать другие вещи, а также следить за своими дочерними элементами, используя системный вызов wait() .

Да уж, информации немало. Давайте посмотрим на код запуска программы:

Эта функция принимает список аргументов, которые мы создали ранее. Затем она разворачивает процесс и сохраняет возвращаемое значение. Как только fork() возвращает значение, мы получаем два параллельных процесса. Дочернему процессу соответствует первое условие if (где pid == 0 ).

В дочернем процессе мы хотим запустить команду, заданную пользователем. Поэтому мы используем один из вариантов системного вызова exec , execvp . Разные варианты exec делают разные вещи. Одни принимают переменное количество строковых аргументов, другие берут список строк, а третьи позволяют указать окружение, в котором выполняется процесс. Этот конкретный вариант принимает имя программы и массив (также называемый вектором, отсюда 'v' ) строковых аргументов (первым должно быть имя программы). 'p' означает, что вместо предоставления полного пути к файлу программы для запуска мы укажем только её имя, а также скажем операционной системе искать её самостоятельно.

Третье условие означает, что вызов fork() выполнен успешно. Там находится родительский процесс. Мы знаем, что потомок собирается исполнить процесс, поэтому родитель должен дождаться завершения команды. Мы используем waitpid() для ожидания изменения состояния процесса. К сожалению, у waitpid() есть много опций (например, exec() ). Процессы могут изменять свое состояние множеством способов, и не все состояния означают, что процесс завершился. Процесс может либо завершиться обычным путём (успешно либо с кодом ошибки), либо быть остановлен сигналом. Таким образом, мы используем макросы, предоставляемые waitpid() , чтобы убедиться, что процесс завершен. Затем функция возвращает 1 как сигнал вызывающей функции, что она снова может вывести приглашение ввода.

Встроенные функции оболочки

Возможно, вы заметили, что функция lsh_loop() вызывает lsh_execute() , но выше мы назвали нашу функцию lsh_launch() . Это было намеренно! Дело в том, что большинство команд, которые исполняет оболочка, являются программами — но не все. Некоторые из команд встроены прямо в оболочку.

Причина довольно проста. Если вы хотите сменить каталог, вам нужно использовать функцию chdir() . Дело в том, что текущий каталог является свойством процесса. Итак, допустим, вы написали программу cd , которая изменяет каталог. Она просто меняет свой текущий каталог и завершается, но текущий каталог родительского процесса не изменится. Вместо этого процесс оболочки должен исполнить chdir() , чтобы обновить свой текущий каталог. Затем, когда он запускает дочерние процессы, они также наследуют этот каталог.

Аналогично программа с именем exit не сможет выйти из командной оболочки, которая ее вызвала. Эта команда также должна быть встроена в оболочку. Кроме того, большинство оболочек настраиваются с помощью сценариев конфигурации, таких как ~/.bashrc . Эти сценарии используют команды, которые изменяют работу оболочки. Сами же команды могут изменить работу оболочки, если только они были реализованы внутри самой оболочки.

Соответственно, имеет смысл добавить некоторые команды в оболочку. В эту оболочку мы добавим cd , exit и help . А вот и реализация этих функций:

Код состоит из трёх частей. Первая часть содержит предваряющее объявление функций. Предваряющее объявление — это когда вы объявляете (но не определяете) что-то, чтобы можно было использовать это имя до его определения. lsh_help() — причина, по которой мы делаем это. Она использует массив встроенных функций, а сами массивы содержат lsh_help() . Самый простой способ разбить этот цикл зависимостей — это предваряющее объявление.

Следующая часть представляет собой массив имён встроенных команд, за которыми следует массив соответствующих функций. Это значит, что в будущем встроенные команды могут быть добавлены путем изменения этих массивов, а не большого оператора switch где-то в коде. Если вы смущены объявлением builtin_func , все в порядке. Это массив указателей на функции (которые принимают массив строк и возвращают int ). Любое объявление, включающее указатели на функции в C, может стать действительно сложным.

Объединение встроенных функций и процессов

Последний недостающий фрагмент головоломки заключается в реализации функции lsh_execute() , которая либо запускает либо встроенный, либо другой процесс.

Код проверяет, является ли команда встроенной. Если это так, то запускает её, а в противном случае вызывает lsh_launch() , чтобы запустить процесс.

Собираем все вместе

Вот и весь код, который входит в командную оболочку. Если вы внимательно читали статью, то должны были понять, как работает оболочка. Чтобы испробовать оболочку (на Linux), вам нужно скопировать эти сегменты кода в файл main.c и скомпилировать его. Обязательно включите только одну реализацию lsh_read_line() . Вам нужно будет включить следующие заголовочные файлы:

Чтобы скомпилировать файл, введите в терминале gcc -o main main.c , а затем ./main , чтобы запустить.

Кроме того, все исходники доступны на GitHub.

Подводя итоги

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

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

Чтобы разобраться в системных вызовах, рекомендуем обратиться к мануалу: man 3p . Если вы не знаете, какой интерфейс вам предлагают стандартная библиотека C и Unix, советуем посмотреть спецификацию POSIX, в частности раздел 13.

Читайте также: