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

Итак, во-первых, терминал — это общее название для способа взаимодействия с вашим компьютером, который не требует использования мыши (графический пользовательский интерфейс или GUI). Доступ к терминалу можно получить с помощью ряда различных программ, включая программу по умолчанию, которая поставляется с вашей машиной. Первое, что встретит вас, когда вы откроете свой терминал, будь то стандартный bash, iterm, fish shell или любое количество других терминальных программ, будет подсказка. Это приглашение будет передано вам PS1, переменной среды, которая содержит значение приглашения по умолчанию. В недавнем проекте у меня была возможность поработать с @mechantkelsie, чтобы создать нашу собственную очень простую программу-оболочку, и для нашей базовой подсказки в PS1 мы использовали простой $. Неплохие деньги с нашей стороны, не так ли?

Подсказка, конечно же, требует ввода, и вы, пользователь, должны ее отправить. Очень простой первой командой для запуска будет та, которая даст вам знать, где именно вы находитесь с точки зрения файловой структуры. Если вас это интересует, вы можете использовать команду наподобие pwd (распечатать рабочий каталог), чтобы точно узнать, какой у вас текущий путь к файлу, возможно, на выходе будет что-то вроде /home/vagrant/Holberton/simple_shell. Теперь, когда вы знаете, где вы находитесь, вы можете захотеть увидеть файлы в вашей текущей папке. Для этого вы можете использовать базовую команду ls (LS) для просмотра содержимого каталога. Какие бы команды вы ни вводили в командной строке, если они действительны и у вас есть права доступа к машине, они будут выполнены для вас, а затем вам будет предложено ввести дополнительные данные.

Более подробное рассмотрение команды LS -L

Это не волшебство, и терминал не черный ящик. Вы можете заглянуть под капот, чтобы увидеть, как все работает, чтобы выполнить ls -l. Первым шагом в выполнении вашей команды будет то, что терминал примет то, что вы набрали в командной строке. Все, что вы вводите, будет принято функцией getline(). Getline предназначен для получения входных данных из файлового потока, обычно стандартного для (STDIN), а затем сохранения их в указателе строки в виде полной строки с завершающим нулем.

Хорошо, немного углубимся в сорняки, как мы можем видеть в приведенном выше прототипе, getline ожидает двойной указатель (lineptr), который указывает на массив, который может содержать символы. Мы обычно называем это буфером, который используется для хранения пользовательского ввода. Если вам это кажется полной чепухой, подумайте об этом так: буфер подобен ряду ящиков в памяти компьютера, и в каждый ящик может входить одна буква (abcd), символ (/- . , « » и т. д.), или космос.

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

С другой стороны, если у нас еще не настроен буфер, а lineptr имеет значение NULL (указывающее в никуда, поскольку буфер не был создан заранее), getline будет использовать malloc для выделения памяти для нашего пользовательского ввода. В этом случае n не требуется, так как getline выделит достаточно памяти для ввода. Последний параметр в getline — это файловый поток, который в нашем случае является STDIN для ввода из командной строки.

Итак, у нас есть подсказка, мы ввели некоторый текст, это намного проще, чем писать средний пост, но что теперь? Что ж, теперь getline передаст нашу строку с завершающим нулем функции с именем string token или strtok(). strtok() — это функция, которая разбивает нашу строку из getline на токены или небольшие отдельные аргументы, чтобы ее можно было выполнить на нашей машине.

Когда вы вводите что-то вроде ls, чтобы вывести список файлов в вашем текущем каталоге, эта единственная команда становится одним токеном в новом массиве аргументов благодаря strtok(). Если, с другой стороны, вы введете что-то вроде ls -l, тогда у нас будет два токена в нашем массиве аргументов, то есть поле для ls и поле для -l.

Теперь, имея массив токенов, мы можем начать искать нулевой аргумент, который будет именем нашей команды, чтобы мы могли выполнить нашу команду. Первым шагом будет проверка текущей среды, чтобы увидеть, является ли наш первый аргумент псевдонимом. Псевдоним — это (обычно короткое) имя, которое оболочка преобразует в другое (обычно более длинное) имя или команду. Псевдонимы создаются пользователем и обычно помещаются в файлы запуска ~/.bashrc (bash) или ~/.tcshrc (tcsh), чтобы они были доступны интерактивным подоболочкам.

Дополнительной частью этого шага является проверка любых специальных символов, таких как ", ', `, \, *, &, # и т. д. Особо следует отметить звездочку *, так как этот символ является подстановочным знаком. , что означает, что мы можем использовать его для поиска нескольких вещей, соответствующих шаблону. У меня есть предыдущий пост об использовании ls *.c здесь, и он объясняет немного больше о расширении подстановочных знаков.

Если мы не имеем дело с псевдонимом, то система переходит к проверке, является ли наш первый аргумент встроенным. Встроенная (или встроенная команда, или встроенная оболочка) — это команда или функция, которая является частью самой оболочки. Команда «встроена» в программу оболочки, поэтому для запуска команды не требуется никакой внешней программы, и нет необходимости создавать новый процесс. Забавно, но псевдоним сам по себе является встроенной функцией, как и help и echo.

Если мы зашли так далеко, то пришло время искать исполняемую версию команды, а это значит, что нам понадобится доступ к PATH. PATH — это переменная среды в Linux и других Unix-подобных операционных системах, которая сообщает оболочке, в каких каталогах искать исполняемые файлы в ответ на команды, введенные пользователем. В случае нашей команды ls -l -a мы найдем наш исполняемый файл в папке /bin, которая, к счастью, находится по пути по умолчанию. То, как система работает с путем, заключается в том, что она ищет в каждой папке файл, который соответствует нашему первому аргументу, ls.

Итак, если вы посмотрите на изображение моего пути по умолчанию выше, вы увидите, что сначала моя оболочка будет искать /usr/local/sbin/ls и не найдет его, поскольку этого файла не существует. Затем он проверит /usr/local/bin и снова не сможет найти файл. Этот процесс будет продолжаться до тех пор, пока мы не перейдем только к /bin, так как есть /bin/ls с исполняемой функцией ls, или пока он не исчерпает путь, не найдя совпадения, и, таким образом, выдаст нам ошибку: команда не найдена.

Теперь, когда у нас есть исполняемый файл, пришло время разветвить наш процесс и запустить наш исполняемый файл. Разветвление — это процесс, с помощью которого оболочка может полностью клонировать свой текущий процесс, а затем заставить этот «дочерний» процесс выполнять определенную команду или программу, в то время как «родительский» (исходный процесс) ждет или даже движется дальше и продолжает выполнение вещей одновременно. Создание дочернего процесса с помощью fork() отлично подходит для одновременного запуска множества задач, а также для доступа к execve(), функции, которая позволяет вам перехватить текущий процесс и заставить его выполнять что-то еще.

В нашем случае это будет работать примерно так: наш родительский процесс, найдя исполняемый файл /bin/ls, разветвится, чтобы дочерний процесс мог вызвать execve(), который возьмет на себя дочерний процесс и просто запустит /bin/ лс. Пока дочерний процесс выполняется, наш родитель ждет завершения дочернего процесса. Мы работаем таким образом, чтобы родительский процесс был готов вернуться к нашему приглашению PS1 и принять вашу следующую команду. Дочерний процесс, унаследовавший все от родительского процесса, вызовет execve() с нашей командой /bin/ls и любыми дополнительными аргументами, в данном случае нашим -l, а execve выполнит /bin/ls -l.

Здесь пользователь ввел ls -l, который считывается оболочкой, а затем преобразуется в ls и -l. Первый токен преобразуется в /bin/ls с помощью переменной окружения PATH и проверяется на наличие исполняемого файла. Затем дочерний процесс запускает процесс /bin/ls с переменной-аргументом -l. ls выведет список всех файлов в текущем рабочем каталоге, а -l покажет эти файлы в длинном формате. После завершения процесса наш вывод будет выглядеть примерно так:

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