Как написать оболочку для программы

Содержание

Don’t learn to code. Code to learn!

Введение в Python

  • Python — Обзор
  • Основы синтаксиса Python
  • Операторы в Python
  • Типы данных в Python
  • Условные конструкторы в Python
  • Циклы в Python
  • Функции в Python
  • Функциональное программирование в Python
  • ООП в Python
  • Модули в Python
  • Работа с файлами в Python
  • Обработка исключительных ситуаций в Python

Рассмотрим пример создания графического интерфейса (GUI) на Python. В качестве «жертвы» напишем простенькую программу — решатель квадратных уравнений. Наше задание мы разобъем на несколько частей.

Часть первая: функция решения квадратного уравнения.

Напомним, что квадратным является уравнение вида:

Есть несколько способов решить квадратное уравнение, мы выберем решение через дискриминант.

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

Как создать графический интерфейс на Python с нуля

Если же дискриминант меньше нуля, то уравнение не имеет решений.

Превратим данные формулы в код:

def solver(a,b,c): «»» Решает квадратное уравнение и выводит отформатированный ответ «»» # находим дискриминант D = b*b — 4*a*c if D >= 0: x1 = (-b + sqrt(D)) / (2*a) x2 = (-b — sqrt(D)) / (2*a) text = «The discriminant is: %s n X1 is: %s n X2 is: %s n» % (D, x1, x2) else: text = «The discriminant is: %s n This equation has no solutions» % D return text

Чтобы все работало не забудьте импортировать функцию sqrt из модуля math.

from math import sqrt

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

Теперь пора переходить к созданию графической оболочки для нашего приложения.

Часть вторая: создаем GUI для программы

Для простоты будем создавать GUI встроенными средствами Python, поэтому импортируем все из библиотеки Tkinter:

from Tkinter import *

В Python версии 3.х название модуля следует писать с маленькой буквы — tkinter.

Далее создаем само окно и размещаем на нем необходимые виджеты:

# родительский элемент root = Tk() # устанавливаем название окна root.title(«Quadratic calculator») # устанавливаем минимальный размер окна root.minsize(325,230) # выключаем возможность изменять окно root.resizable(width=False, height=False) # создаем рабочую область frame = Frame(root) frame.grid() # поле для ввода первого аргумента уравнения (a) a = Entry(frame, width=3) a.grid(row=1,column=1,padx=(10,0)) # текст после первого аргумента a_lab = Label(frame, text=»x**2+»).grid(row=1,column=2) # поле для ввода второго аргумента уравнения (b) b = Entry(frame, width=3) b.grid(row=1,column=3) # текст после второго аргумента b_lab = Label(frame, text=»x+»).grid(row=1, column=4) # поле для ввода третьего аргумента уравнения (с) c = Entry(frame, width=3) c.grid(row=1, column=5) # текст после третьего аргумента c_lab = Label(frame, text=»= 0″).grid(row=1, column=6) # кнопка решить but = Button(frame, text=»Solve»).grid(row=1, column=7, padx=(10,0)) # место для вывода решения уравнения output = Text(frame, bg=»lightblue», font=»Arial 12″, width=35, height=10) output.grid(row=2, columnspan=8) # запускаем главное окно root.mainloop()

Если вы в точности повторили указанный код, то после запуска скрипта у вас получится примерно следующее окно:

Красивый графический интерфейс на Python | Все Фишки DearPyGui

окно калькулятора квадратных уравнений с Python и Tkinter

Отлично, программа работает. Осталось объяснить Python как связать эти две части.

Часть третья: объединяем все воедино

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

Функция вставки информации:

def inserter(value): «»» Inserts specified value into text widget «»» output.delete(«0.0″,»end») output.insert(«0.0»,value)

Функция inserter предельно проста: очищает поле для ввода и вставляет туда переданный ей аргумент value.

Напишем функцию обработки введенной информации. Назовем ее handler:

def handler(): «»» Get the content of entries and passes result to the output area «»» try: # make sure that we entered correct values a_val = float(a.get()) b_val = float(b.get()) c_val = float(c.get()) inserter(solver(a_val, b_val, c_val)) except ValueError: inserter(«Make sure you entered 3 numbers»)

В зависимости от данных введенных в поля для ввода передает функции inserter либо результат решения уравнения, либо сообщение о неверно введенных данных.

Чтобы все работало, следует изменить строку создания виджета Button следующим образом:

but = Button(frame, text=»Solve», command=handler).grid(row=1, column=7, padx=(10,0))

Теперь можно спокойно пользоваться нашей программой:

Пример программы с GUI на Python и Tkinter #1 Пример программы с GUI на Python и Tkinter #2
Дискриминант больше нуля Дискриминант равен нулю
Пример программы с GUI на Python и Tkinter #3 Пример программы с GUI на Python и Tkinter #4
Дискриминант меньше нуля. Решений нет Введены не все аргументы

Часть четвертая: необязательная

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

def clear(event): «»» Clears entry form «»» caller = event.widget caller.delete(«0», «end»)

Таким образом мы очищаем виджет, вызвавший данную функцию. Чтобы все работало, добавьте следующие строки после создания виджетов, но до размещения. Например, после строки a = Entry(. , но до строки a.grid(.

a.bind(«», clear) b.bind(«», clear) c.bind(«», clear)

Готово. Программа работает, Вы великолепны!

Исходный код калькулятора квадратных уравнений с GUI на GitHub

  • Пятнашки на Python
  • Паттерны проектирования в Python
  • Множествeнное наследование в Python
  • Абстрактные классы в Python
  • Сапер на Python

Источник: www.pythonicway.com

Давайте напишем командную оболочку Linux

Всем привет! Хочу поделиться своим опытом написания собственной командной оболочки Linux используя Posix API, усаживайтесь поудобнее.

Что должна уметь наша командная оболочка

  1. Запуск процессов в foreground и background режиме
  2. Завершение background процессов
  3. Поддержка перемещения по директориям

Как устроена работа командной оболочки

  1. Считывание строки из стандартного потока ввода
  2. Разбиение строки на токены
  3. Создание дочернего процесса с помощью системного вызова fork
  4. Замена дочернего процесса на необходимый с помощью системного вызова exec
  5. Ожидание завершения дочернего процесса (в случае foreground процесса)

Немного про системный вызов fork()

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

#include #include #include int main() < pid_t pid = fork(); if (pid == 0) < printf(«I’m child process!n»); >else < printf(«I’m parent process!n»); wait(NULL); >return 0; >
Что выведет данная программа:

I’m parent process!
I’m child process!

Что же произошло? Системный вызов fork создал клон процесса, т. е. теперь мы имеем родительский и дочерний процесс.

Чтобы отличить дочерний процесс от родительского в коде достаточно сделать проверку. Если результат функции fork равен 0 — мы имеем дело с дочерним процессом, если нет — с родительским. Это не означает, что в операционной системе id дочернего процесса равен 0 .

Читайте также:
Планшет в машину программы

Причем, порядок выполнения дочернего и родительского процесса ничем не задекларирован. Все будет зависеть от планировщика операционной системы. Поэтому в конце блока родительского процесса добавлена строчка wait(NULL) , которая дожидается окончания дочернего процесса.

Подробнее про exec()

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

Системный вызов exec заменяет текущий процесс сторонним. Естественно, сторонний процесс задается через параметры функции.

#include #include #include #include int main() < pid_t pid = fork(); if (pid == 0) < execlp(«ls», «ls», «-l», NULL); exit(1); >else < waitpid(pid, NULL, 0); >return 0; >
Что выведет данная программа

total 16
-rwxr-xr-x 1 runner runner 8456 Jan 13 07:33 main
-rw-r—r— 1 runner runner 267 Jan 13 07:33 main.c

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

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

Перейдем к полноценной реализации

Часть 1. Чтение строки с консоли

Изначально нам надо уметь считывать строку из командной строки. Думаю, с этим не возникнет сложностей.

char* readline() < char* line = NULL; size_t size = 0; ssize_t str_len; // Reading line from stdin if ((str_len = getline(size, stdin)) == -1) < // Logging all errors except Ctrl-D — terminal shutdown if (errno != 0) < printf(«[ERROR] Couldn’t read from stdinn»); >free(line); printf(«n»); return NULL; > // Remove useless n symbol if exists if (line[str_len — 1] == ‘n’) < line[str_len — 1] = ‘’; >return line; >

В данной функции происходит чтение строки с применением функции getline . После чего, если в конце строки имеется символ переноса строки, удаляем его.

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

Часть 2. Разбиение строки на токены

В данной части будет представлена реализация разбиения строки на массив токенов.

#define DEFAULT_BUFF_SIZE 16 #define TOKENS_DELIMITERS » t»

Определения начальной длины массива и разделителей строки.

char** split(char* line) < size_t position = 0; size_t buff_size = DEFAULT_BUFF_SIZE; char* token; // Allocate memory for tokens array char** tokens = (char**)malloc(sizeof(char*) * buff_size); if (tokens == NULL) < printf(«[ERROR] Couldn’t allocate buffer for splitting!n»); return NULL; >// Tokenize process token = strtok(line, TOKENS_DELIMITERS); while (token != NULL) < // Emplace token to array tokens[position++] = token; // If array free space ended — increase array if (position >= buff_size) < buff_size *= 2; tokens = (char**)realloc(tokens, buff_size * sizeof(char*)); if (tokens == NULL) < printf(«[ERROR] Couldn’t reallocate buffer for tokens!n»); return NULL; >> // Getting next token token = strtok(NULL, TOKENS_DELIMITERS); > // Place NULL to the end of tokens array tokens[position] = NULL; return tokens; >

Код выглядит довольно громоздким, однако в нем нет ничего сложного.

Очередной токен получается с использованием функции strtok . После чего данный токен копируется в массив токенов. Если в массиве токенов не достаточно места, массив увеличивается в 2 раза.

Завершается всё добавлением завершающего токена равного NULL , т. к. функция exec() ожидает наличие данного завершающего токена.

Часть 3. Выполнение процессов

Структура хранения списка запущенных процессов

Напишем определения структур для foreground и background процессов, fg_task и bg_task . А также определение структуры для хранения всех процессов tasks .

// Struct of background task struct bg_task_t < pid_t pid; // Process id bool finished; // Process state char* timestamp; // Process state char* cmd; // Command cmd >; typedef struct bg_task_t bg_task; // Struct of foreground task struct fg_task_t < pid_t pid; // Process id bool finished; // Process state >; typedef struct fg_task_t fg_task; // Struct of all tasks struct tasks_t < fg_task foreground; // Process id of foreground bg_task bg_task* background; // Background task list size_t cursor; // Cursor of background tasks size_t capacity; // Background array capacity >; typedef struct tasks_t tasks;

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

// Global variable for storing active tasks tasks t = < .foreground = < .pid = -1, .finished = true >, .background = NULL, .cursor = 0, .capacity = 0 >;

Вспомогательные функции добавления процессов

Установка foreground процесса выглядит банально и не нуждается в комментировании.

void set_foreground(pid_t pid)

Добавление background процесса выглядит посложнее.

int add_background(pid_t pid, char* name) < // Temp background task variable bg_task* bt; // If end of free space in background array — increase size if (t.cursor >= t.capacity) < t.capacity = t.capacity * 2 + 1; t.background = (bg_task*)realloc(t.background, sizeof(bg_task) * t.capacity); if (t.background == NULL) < printf(«[ERROR] Couldn’t reallocate buffer for background tasks!n»); return -1; >> // Print info about process start printf(«[%zu] started.n», t.cursor); // Save task in temp variable bt = // Save process info in array bt->pid = pid; bt->finished = false; time_t timestamp = time(NULL); bt->timestamp = ctime( bt->cmd = strdup(name); // Move cursor right t.cursor += 1; return 0; >

На деле же все проще. Смотрим есть ли место в массиве для хранения информации о процессе. Если его недостаточно — увеличиваем длину массива в 2 раза. После чего происходит добавление структуры bg_task в массив и последующее заполнение полей структуры информацией о процессе.

Данная функция возвращает -1 в случае неудачи.

Вспомогательные функции для завершения процессов

Добавим функцию экстренного завершения foreground процесса. Данная функция с помощью системного вызова kill с параметром SIGTERM завершает процесс по id процесса.

void kill_foreground() < if (t.foreground.pid != -1) < // Kill process kill(t.foreground.pid, SIGTERM); // Set finished flag t.foreground.finished = true; printf(«n»); >>

Также добавим функцию для завершения background процесса.

int term(char** args) < char* idx_str; // Cursor in index arg int proc_idx = 0; // Converted to int index arg if (args[1] == NULL) < printf(«[ERROR] No process index to stop!n»); >else < // Set cursor in index arg idx_str = args[1]; // Convert string index arg to int while (*idx_str >= ‘0’ *idx_str // Kill process if process index not bad // and target process not finished if (*idx_str != ‘’ || proc_idx >= t.cursor) < printf(«[ERROR] Incorrect background process index!n»); >else if (!t.background[proc_idx].finished) < kill(t.background[proc_idx].pid, SIGTERM); >> return CONTINUE; >

Данная функция принимает в себя массив токенов вида «, NULL> . После чего преобразует токен индекса background задачи в число. Убивает background задачу посредством системного вызова kill .

Непосредственно запуск процессов

Для удобства введем функцию is_background , определяющую является ли задача фоновым процессом. Данная функция просто проверяет наличие // Current position in array int last_arg = 0; // Finding last arg in array while (args[last_arg + 1] != NULL) < last_arg += 1; >// Checking if task is background` if (strcmp(args[last_arg], «) == 0) < // Remove ‘ // Return true return 1; >// Return false if: ‘ >

Читайте также:
Как удалить в программе 1с помеченные объекты

Введем функцию launch которая будет запускать background процесс если в конце присутствует токен pid_t pid; // Fork process id int background; // Is background task // Checking if task is background background = is_background(args); // Create child process pid = fork(); // If created failure log error if (pid < 0) < printf(«[ERROR] Couldn’t create child process!n»); >// Child process else if (pid == 0) < // Try launch task if (execvp(args[0], args) == -1) < printf(«[ERROR] Couldn’t execute unknown command!n»); >exit(1); > // Parent process else < if (background) < // Try add background task to array if (add_background(pid, args[0]) == -1) < // Kill all processes and free // memory before exit quit(); >> else < // Set foreground task to store set_foreground(pid); // Wait while process not ended if (waitpid(pid, NULL, 0) == -1) < // Logging error if process tracked with error // Except when interrupted by a signal if (errno != EINTR) < printf(«[ERROR] Couldn’t track the completion of the process!n»); >> > > return CONTINUE; >

То, что происходит в этой функции уже должно быть все понятно.

  1. Создается дубликат процесса с помощью системного вызова fork
  2. Заменяем дочерний процесс на требуемый с помощью системного вызова exec
  3. Определяем является ли процесс фоновым
  4. Если процесс фоновый — просто добавляем его в список bacground задач
  5. Если процесс не фоновый — дожидаемся окончания выполнения процесса

В функции присутствует неизвестная функция quit . Ее мы разберем в следующем блоке.

Вспомогательные функции для командной оболочки.

Введем функцию execute , которая в зависимости от первого токена выбирает нужное действие.

int execute(char** args) < if (args[0] == NULL) < return CONTINUE; >else if (strcmp(args[0], «cd») == 0) < return cd(args); >else if (strcmp(args[0], «help») == 0) < return help(); >else if (strcmp(args[0], «quit») == 0) < return quit(); >else if (strcmp(args[0], «bg») == 0) < return bg(); >else if (strcmp(args[0], «term») == 0) < return term(args); >else < return launch(args); >>

Данная функция пропускает действие, если первый токен NULL . Смена директории, если первый токен cd . Вывод справки об использовании, если первый токен help . Завершение работы командной оболочки, если первый токен quit . Вывод списка background задач, если первый токен bg . Завершение процесса по индексу, если первый токен term .

Во всех других случаях запускается процесс.

Реализация вспомогательных функций

#define CONTINUE 1 #define EXIT 0

Значение CONTINUE означает дальнейшее исполнение главного цикла командной оболочки. Значение EXIT прерывает выполнение главного цикла программы.

int cd(char** args) < if (args[1] == NULL) < printf(«[ERROR] Expected argument for «cd» command!n»); >else if (chdir(args[1]) != 0) < printf(«[ERROR] Couldn’t change directory to «%s»!n», args[1]); >return CONTINUE; >
int help() < printf( «Simple shell by Denis Glazkov. nn» «Just type program names and arguments, and hit enter. n» «Run tasks in background using ‘ «Built in functions: n» » cd — Changes current working directory n» » term — Kill background process by index n» » help — Prints info about shell n» » bg — Prints list of background tasks n» » quit — Terminates shell and all active tasksnn» «Use the man command for information on other programs. n» ); return CONTINUE; >
int quit() < // Temp background task variable bg_task* bt; // Disable logging on child killed signal(SIGCHLD, SIG_IGN); // Kill foreground process if (!t.foreground.finished) < kill_foreground(); >// Kill all active background tasks for (size_t i = 0; i < t.cursor; i++) < // Place background task to temp variable bt = // Kill process if active if (!bt->finished) < kill(bt->pid, SIGTERM); > // Free memory for command name free(bt->cmd); > return EXIT; >

Функция quit отключает все callback функции по событию SIGCHLD — т. е. функции, выполняющиеся когда дочерний элемент был завершен. После этого завершает все активные процессы.

#define PRIMARY_COLOR «33[92m» #define SECONDARY_COLOR «33[90m» #define RESET_COLOR «33[0m»

Основные цвета командной оболочки.

int bg() < // Temp background task variable bg_task* bt; for (size_t i = 0; i < t.cursor; i++) < // Store background task in temp variable bt = // Print info about task printf( «[%zu]%s cmd: %s%s;%s pid: %s%d; %s» «state: %s%s;%s timestamp: %s%s», i, SECONDARY_COLOR, RESET_COLOR, bt->cmd, SECONDARY_COLOR, RESET_COLOR, bt->pid, SECONDARY_COLOR, RESET_COLOR, bt->finished ? «finished» : «active», SECONDARY_COLOR, RESET_COLOR, bt->timestamp ); > return CONTINUE; >

Часть 4. Главный цикл командной оболочки

#include #include #include «include/shell.h» int main() < char* line; // User input char** args; // Tokens in user input int status; // Status of execution // Add signal for killing foreground child on ctrl-c signal(SIGINT, kill_foreground); // Add signal for handling end of child processes signal(SIGCHLD, mark_ended_task); // Shell is running while // status == CONTINUE do < // Printing left shell info display(); // Reading user input line = readline(); if (line == NULL) < exit(1); >// Parse line to tokens args = split(line); if (args == NULL) < free(line); exit(2); >// Try execute command status = execute(args); // Free allocated memory free(line); free(args); > while (status); return 0; >

Здесь и происходит вся магия. Взгляните на следующие строки. С помощью функции signal задаются callback функции на заданные события.

// Add signal for killing foreground child on ctrl-c signal(SIGINT, kill_foreground); // Add signal for handling end of child processes signal(SIGCHLD, mark_ended_task);

Событие SIGINT — срабатывает при нажатии комбинации ctrl-C , которое в дефолтном поведении завершает работу программы. В нашем же случае мы переназначаем его на завершение foreground процесса.

Событие SIGCHLD — срабатывает при завершении дочернего процесса созданyого с помощью системного вызова fork . В нашем случае мы переопределяем его на пометку фоновой задачи как выполненной с помощью функции mark_ended_task .

void mark_ended_task() < // Temp background task variable bg_task* bt; // Get process id of ended process pid_t pid = waitpid(-1, NULL, 0); // Handle foreground process if (pid == t.foreground.pid) < t.foreground.finished = true; >// Handle background process else < // Search and remove process form background tasks array for (size_t i = 0; i < t.cursor; i++) < // Place task to temp variable bt = if (bt->pid == pid) < // Print info about process end printf(«[%zu] finished.n», i); // Set new state for background process bt->finished = 1; break; > > > >

Все что описано в главном цикле командной оболочки можно описать словами:

  1. Вывод информации о пользователе и текущей директории с помощью функции display
  2. Чтение строки из стандартного потока ввода
  3. Разбиение строки на токены
  4. Выполнение ранее описанной функции execute , которая в зависимости от массива токенов выполняет нужное нам действие.
Читайте также:
В каких программах делают аниме

Нам осталось реализовать одну оставшуюся функцию display . Которая получает информацию о текущей директории с помощью функции getcwd и имя пользователя с помощью функции getpwuid .

void display() < // Try get and print username with color uid_t uid = geteuid(); struct passwd *pw = getpwuid(uid); if (pw != NULL) < printf(«%s%s%s:», PRIMARY_COLOR, pw->pw_name, RESET_COLOR); > // Try get and print current directory with color char cwd[MAX_DIRECTORY_PATH]; if (getcwd(cwd, MAX_DIRECTORY_PATH) != NULL) < printf(«%s%s%s», SECONDARY_COLOR, cwd, RESET_COLOR); >// Print end of shell info printf(«# «); >

Часть 5. Итоговый результат

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

С исходным кодом проекта вы можете ознакомиться по данной ссылке.

Источник: habr.com

«Шелл» на С: пишем командную оболочку для Unix

Многие считают, что сделать программу, которой будут пользоваться миллионы, очень трудно. Однако за любым, даже самым сложным, продуктом всегда стоит простая идея. Одним из них является командная оболочка, или «шелл». В этой статье мы расскажем, как написать упрощенную командную оболочку Unix на C.

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

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

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

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

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

int main(int argc, char **argv) < // Загрузка файлов конфигурации при их наличии. // Запуск цикла команд. lsh_loop(); // Выключение / очистка памяти. return EXIT_SUCCESS; >

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

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

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

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

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

void lsh_loop(void) < char *line; char **args; int status; do < printf(«>»); line = lsh_read_line(); args = lsh_split_line(line); status = lsh_execute(args); free(line); free(args); > while (status); >

Пройдемся по коду. Первые несколько строк — это просто объявления. Цикл с постусловием более удобен для проверки состояния переменной, поскольку выполняется перед проверкой ее значения.

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

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

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

#define LSH_RL_BUFSIZE 1024 char *lsh_read_line(void) < int bufsize = LSH_RL_BUFSIZE; int position = 0; char *buffer = malloc(sizeof(char) * bufsize); int c; if (!buffer) < fprintf(stderr, «lsh: ошибка выделения памятиn»); exit(EXIT_FAILURE); >while (1) < // Читаем символ c = getchar(); // При встрече с EOF заменяем его нуль-терминатором и возвращаем буфер if (c == EOF || c == ‘n’) < buffer[position] = ‘’; return buffer; >else < buffer[position] = c; >position++; // Если мы превысили буфер, перераспределяем блок памяти if (position >= bufsize) < bufsize += LSH_RL_BUFSIZE; buffer = realloc(buffer, bufsize); if (!buffer) < fprintf(stderr, «lsh: ошибка выделения памятиn»); exit(EXIT_FAILURE); >> > >

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

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

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

char *lsh_read_line(void) < char *line = NULL; ssize_t bufsize = 0; // getline сама выделит память getline(bufsize, stdin); return line; >

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

Теперь нам нужно распарсить входную строку в список аргументов. Мы сделаем небольшое упрощение и запретим пользователю использовать кавычки и обратную косую черту в аргументах командной строки. Вместо этого для разделения аргументов мы просто будем использовать пробелы. Таким образом команда echo «вот сообщение» будет вызывать команду echo не с одним аргументом «вот сообщение» , а с двумя: «вот» и «сообщение» .

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

#define LSH_TOK_BUFSIZE 64 #define LSH_TOK_DELIM » trna» char **lsh_split_line(char *line) < int bufsize = LSH_TOK_BUFSIZE, position = 0; char **tokens = malloc(bufsize * sizeof(char*)); char *token; if (!tokens) < fprintf(stderr, «lsh: ошибка выделения памятиn»); exit(EXIT_FAILURE); >token = strtok(line, LSH_TOK_DELIM); while (token != NULL) < tokens[position] = token; position++; if (position >= bufsize) < bufsize += LSH_TOK_BUFSIZE; tokens = realloc(tokens, bufsize * sizeof(char*)); if (!tokens) < fprintf(stderr, «lsh: ошибка выделения памятиn»); exit(EXIT_FAILURE); >> token = strtok(NULL, LSH_TOK_DELIM); > tokens[position] = NULL; return tokens; >

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

Рейтинг
( Пока оценок нет )
Загрузка ...
EFT-Soft.ru