大家好!我是大聪明-PLUS!
我最近决定通过编写项目来提升我的 C 语言技能。首先想到的就是命令行解释器,或者简称为 shell。我还会谈到 Make 构建系统以及如何正确地编写和记录 C 代码。
在第一部分中,我们定义了基本代码结构,介绍了读取输出和创建进程。在这一部分,我们的目标是从 alpha 版本过渡到 beta 版本------也就是说,实现其他重要功能,例如基本的插件支持、自动补全、语法高亮、环境变量以及新的内置实用程序。
没错,我们要把自行车改装成轻便摩托车!我已经总结了上一篇文章的发现,并尝试解决所有问题和疑虑。让我们继续深入探索 Linux 开发!
事实上,编译器的选择本身取决于给定的条件和限制。
在第一部分中,我介绍了 C 语言的历史、基本原理,以及命令解释器的基本结构。在这一部分,我们将对程序进行重大修改。我很感兴趣:我们将添加自动补全、语法高亮、基本实用程序,并改进代码的结构和清晰度。
那么,我们开始吧!
❯ 编译过程
为了理解二进制格式的工作原理,我们来编译一个 C 程序。这将帮助我们了解程序在运行之前所经历的步骤。
二进制文件是通过编译过程创建的,编译过程是将人类可读的源代码(例如 C/C++)翻译成处理器可以执行的机器代码。
❯ 预处理阶段
我们来探讨一下预处理阶段。编译过程首先要处理几个需要编译的文件。
使用 `#include` 指令在程序中包含的头文件会被递归预处理并包含在输出文件中。然而,每个头文件在预处理过程中可能会被多次打开,因此通常需要使用特殊的预处理器指令来防止循环依赖。
预处理器是一个宏处理器,它将你的程序转换为后续编译所需的格式。在这个阶段,它使用预处理器指令。例如,预处理器会向代码中添加头文件(#include),删除注释,将宏(#define)替换为它们的值,并根据条件(#if、#ifdef 和 #ifndef)选择所需的代码片段。
程序可能只有一个源文件,但大型程序通常由多个文件组成(这是一种良好的编程实践,可以避免主文件被不必要的代码淹没)。C和 C++ 源文件可以包含宏(`#define` 指令)和 `#include` 指令。后者用于包含源文件所依赖的库和头文件(扩展名为 `.h`)。在预处理阶段,所有 `#define` 和 `#include` 指令都会被展开,最终只剩下纯 C 代码供编译。
#include <stdio.h> `
`#define GREETING "Hello, %s" `
`#define MESSAGE "Dacongming-plus" `
`int` `main`() {
`printf`(`GREETING`, `MESSAGE`);
`return` `0`;
}
`
我们很快就会看到编译过程其他阶段的情况,但现在我们只关注预处理阶段的输出。默认情况下,GCC 会同时运行所有编译阶段,但也有一些标志可以让我们在不同阶段停止编译。要在预处理模式下停止编译,我们需要输入命令gcc -E -P <example.с> -o <example_processed.ii>。
我们来分析一下这条命令。-E 标志强制在预处理完成后停止,-P 标志强制省略调试信息。-o 标志指定输出的写入位置。下面是一个预处理阶段的示例文件,为了简洁起见已做了修改。
typedef` `long` `unsigned` `int` `size_t`;
`typedef` `__builtin_va_list` `__gnuc_va_list`;
`typedef` `unsigned` `char` `__u_char`;
`typedef` `unsigned` `short` `int` `__u_short`;
`typedef` `unsigned` `int` `__u_int`;
`typedef` `unsigned` `long` `int` `__u_long`;
`/* ... */`
`extern` `int` `__uflow` (`FILE` `*`);
`extern` `int` `__overflow` (`FILE` `*`, `int`);
`int` `main`() {
`printf`(`"Hello, %s"`, `"Habr"`);
`return` `0`;
}
`
stdio.h 头文件及其所有类型定义、全局变量和函数原型都会被完整地复制到主文件中。由于每个 #include 指令都会执行此操作,因此包含多个库或头文件会导致输出非常长。此外,预处理器还会展开所有使用 #define 关键字定义的宏。在本例中,这意味着 printf 函数的两个参数(GREETING 和 MESSAGE)都会被求值并替换为相应的字符串。
❯ 编译阶段
所以,在得到 C 代码之后,我们需要将其编译。但不是直接编译成二进制可执行文件,而是编译成汇编代码。预处理完成后,源文件就可以进行编译了。在编译阶段,预处理后的代码会被翻译成汇编语言(汇编代码是机器代码的人类可读表示)。大多数编译器在这个阶段都会进行不同程度的优化,优化程度由编译标志控制;以 gcc 为例,这些标志从 -O0 到 -O3。
为什么编译阶段生成的是汇编代码而不是机器代码?对于单一语言(例如 C 语言)而言,这种做法似乎毫无意义,但考虑到其他编程语言,就显得合情合理了。最流行的编译型语言包括 C、C++、Common Lisp、Go 和 Haskell。为每种语言编写一个生成机器代码的编译器将是一项极其耗时耗力的工作。相比之下,生成汇编代码并在流程的最后阶段使用同一个汇编器进行处理则更为简便。
因此,编译阶段的结果是生成了仍然可读且保留所有符号信息的汇编代码。如前所述,gcc 通常会自动执行所有编译阶段,因此要查看编译阶段生成的汇编代码,需要让 gcc 在此阶段结束后停止并将汇编文件保存到磁盘。这可以通过 `-S` 标志来实现(.s 扩展名通常用于汇编语言文件,尽管简单的 .asm 扩展名也经常使用)。我们还向 gcc 传递了 `-masm=intel` 标志,该标志强制使用 Intel 语法而不是 AT&T 语法进行汇编。AT&T 语法不如 Intel 语法流行且可读性差。以下是编译命令gcc -S -masm=intel <example.c> -o <example_asm>.s:
下面粘贴了我们代码汇编文件中的一小段摘录:
` .file "hello.c"
.intel_syntax noprefix
.text
.Ltext0:
.file 0 "/home/argentum/Coding/ELF-x86-research" "src/hello.c"
.section .rodata.str1.1,"aMS",@progbits,1
.LC0:
.string "Habr"
.LC1:
.string "Hello, %s"
.section .text.startup,"ax",@progbits
.p2align 4,,10
.p2align 3
.globl main
.type main, @function
`
顺便一提,代码优化过程中你的源代码可能会略有变化。编译器知道加法比乘法更高效,所以如果你需要计算 2 乘以 2,它会直接将 2 和 2 相加,而不是进行乘法运算。
❯ 组装阶段
由于 x86 处理器以二进制代码执行指令,因此需要使用汇编器将汇编代码翻译成机器代码。汇编器将汇编代码转换为机器代码,并将其存储在目标文件中。
汇编阶段结束时,我们终于得到了真正的、新鲜出炉的机器代码!此阶段以编译过程中生成的汇编文件作为输入,并输出一组目标文件,有时也称为模块。目标文件包含机器指令,原则上可以由处理器执行。但是,正如我们很快就会了解到的,在生成可运行的二进制文件之前,还有一些工作要做。通常,一个源文件对应一个汇编文件,一个汇编文件对应一个目标文件。要生成目标文件,需要向 gcc 传递 `-c` 标志。
目标文件是由汇编器创建的中间文件,其中存储了一段机器代码。这段尚未与其他机器代码片段链接起来形成最终可执行程序的机器代码称为目标代码。
这样就可以将此目标代码保存到静态库中,从而避免再次编译此代码。
要验证生成的目标文件是否确实是目标文件,可以使用文件实用程序:
`file <example_obj.o>
<example_obj.o>: ELF `64-bit` LSB relocatable, x86-64, version `1` (SYSV), with debug_info, not stripped
`
第一部分告诉我们该文件符合可执行二进制文件格式 (ELF) 规范。更准确地说,这是一个 64 位 ELF 文件,LSB 表示在内存中存储数字时,最低有效字节 (LSB) 优先。但这里最重要的部分是"可重定位"这个词。
可重定位文件不绑定到任何特定的内存地址;它们可以被重定位,而不会违反代码中的任何假设。在文件输出中看到"可重定位"一词表明我们正在讨论的是目标文件,而不是可执行文件(尽管也存在位置无关(可重定位)文件,但文件会将它们报告为共享对象,而不是可重定位文件。它们可以通过入口点地址的存在与常规共享库区分开来)。目标文件是独立编译的,因此在处理一个文件时,汇编器可能不知道其他目标文件中引用了哪些地址。这正是目标文件必须是可重定位的原因;这样我们就可以按任意顺序链接它们,并生成一个完整的可执行二进制文件。如果目标文件不可重定位,这将是不可能的。但在这个阶段,一切还没有结束,因为可能存在许多目标文件,它们都需要使用链接器合并成一个单独的可执行文件。因此,我们进入下一个阶段。
❯ 布局阶段
链接是编译过程的最后阶段。在这个阶段,所有目标文件被合并成一个可执行的二进制文件。在现代系统中,链接阶段有时会包含一个称为链接时优化(LTO)的额外步骤。
不出所料,执行链接操作的程序被称为链接器。链接器通常独立于编译器,编译器负责执行之前的所有步骤。链接器将所有目标文件和静态库合并成一个可执行文件,然后我们就可以运行该文件了。要理解链接是如何进行的,我们需要了解符号表。
符号表是由编译器创建并存储在目标文件内部的一种数据结构。符号表存储变量、函数、类、对象等的名称,其中每个标识符(符号)都与其类型和作用域相关联。符号表还存储对其他目标文件中数据和过程的引用地址。
链接器利用符号表及其存储的引用,能够建立跨多个目标文件的数据连接,并最终生成一个可执行文件。
正如我们之前提到的,目标文件是可重定位的,因为它们彼此独立编译,编译器无法预先确定目标文件在内存中的起始地址。此外,目标文件可能包含对其他目标文件或外部库中的函数和变量的引用。在链接之前,代码和数据的具体地址尚不清楚,因此目标文件只包含可重定位符号,这些符号决定了最终如何解析对函数和变量的引用。在链接过程中,依赖于可重定位符号的引用称为符号链接。如果目标文件使用绝对地址引用自身的某个函数或变量,那么这种引用也是符号链接。
链接器的任务是将程序的所有目标文件合并成一个可执行文件,该文件通常从特定的内存地址加载。现在可执行文件的模块已经确定,链接器可以解析大多数符号链接。但是,根据库的类型,对库的引用可能仍然无法解析。
统计库(在 Linux 系统中,它们通常带有 .a 扩展名)包含在可执行二进制文件中,因此可以完全解析对它们的引用。然而,还有动态(共享)库,它们由系统上运行的所有程序共享。换句话说,动态库不会被复制到每个使用它的二进制文件中,而是只加载到内存中一次,所有需要它的二进制文件都使用这个共享副本。在链接阶段,动态库的地址尚未确定,因此无法解析对它们的引用。因此,链接器即使在最终的可执行文件中也会留下指向这些库的符号链接,这些引用只有在二进制文件加载到内存中执行时才会解析。大多数编译器,包括 gcc,都会在编译过程结束时自动调用链接器。因此,要创建一个完整的二进制可执行文件,只需调用 gcc 而无需任何特殊标志即可。
gcc` <example.c> `-o` <example_bin>
file <example_bin>
<example_bin>: ELF `64-bit` LSB pie executable, x86-64, version `1` (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]`=`1189a5182dc9274591321961fea250aa18893450, `for` GNU/Linux `3`.2.0, with debug_info, not stripped
`
文件实用程序报告我们有一个 ELF 64 位 LSB PIE 可执行文件。缩写 PIE 已添加,代表位置无关可执行文件 (Position Independent Executable)。当然,您也可以拥有不带 PIE 的文件。该文件现在是可执行文件,而不是像汇编阶段之后那样是可重定位的。同样重要的是,该文件是动态链接的,这意味着它使用了未包含在自身中但与其他系统程序共享的库。最后,`interpreter /lib64/ld-linux-x86-64.so.2` 告诉我们,当可执行文件加载到内存中时,将使用哪个动态链接器来最终解析动态库依赖关系。运行该二进制文件,您会发现它的行为符合预期。一个可以正常运行的二进制文件!对于一个小程序来说,这真是一段漫长而复杂的旅程,对吧?
准备工作
在开始编写代码之前,我们需要准备开发环境。这意味着要设置构建系统、安装额外的脚本和库。我们先从库开始------我决定使用isocline而不是 GNU readline 。这个库提供了许多现成的基础功能:语法高亮、自动补全和历史记录。我一直在寻找 readline 的替代品,最终找到了这个库。根据它的描述,它由 8000 行纯 C 代码组成!
您可以按如下方式将其编译成 .a 文件:
git` clone https://github.com/daanx/isocline
`mkdir` `-p` build/release
`cd` build/release
cmake ../..
cmake `--build` .
`mv` libisocline.a ../../../
`
现在我们来处理 Makefile------构建系统:
`
BINARY_NAME = shegang
SRC_DIR = src
BIN_DIR = bin
INCLUDE_DIR = include
CTYPES_LIB_DIR = ctypes_lib/bin
CC = clang
CFLAGS = -Wall -Wextra -Werror=implicit-function-declaration -Wpedantic -O2 -g -pipe -fno-fat-lto-objects -fPIC -mtune=native -march=native
CC_SO_FLAGS = -Wall -Wextra -shared -fPIC -O2 -std=c11
LDFLAGS = -L$(BIN_DIR)
INCLUDE_PATHS = -I$(INCLUDE_DIR)
SRC_FILES = $(shell find $(SRC_DIR) -type f -name "*.c")
OBJ_FILES = $(patsubst $(SRC_DIR)/%.c, $(BIN_DIR)/%.o, $(SRC_FILES))
OBJ_SO_FILES = $(patsubst $(SRC_DIR)/%.c, $(CTYPES_LIB_DIR)/%.o, $(SRC_FILES))
LIBRARY = $(CTYPES_LIB_DIR)/libshegang.so
LIB_ISOCLINE = libisocline.a
SUDO = sudo
DEL_FILE = rm -f
CHK_DIR_EXISTS = test -d
MKDIR = mkdir -p
COPY = cp -f
COPY_FILE = cp -f
COPY_DIR = cp -f -R
INSTALL_FILE = install -m 644 -p
INSTALL_PROGRAM = install -m 755 -p
INSTALL_DIR = cp -f -R
DEL_FILE = rm -f
SYMLINK = ln -f -s
DEL_DIR = rmdir
MOVE = mv -f
TAR = tar -cf
COMPRESS = gzip -9f
LIBS_DIRS = -I./include/
SED = sed
STRIP = strip
all: $(BIN_DIR)/$(BINARY_NAME)
build: $(BIN_DIR)/$(BINARY_NAME)
example_plugin:
@echo "Compile example plugin"
@$(CC) $(CC_SO_FLAGS) plugins/pipegang_plugin.c -o pipegang_plugin.so
install: $(BIN_DIR)/$(BINARY_NAME)
$(SUDO) $(INSTALL_PROGRAM) $(BIN_DIR)/$(BINARY_NAME) /usr/local/bin/
remove:
$(SUDO) $(DEL_FILE) /usr/local/bin/$(BINARY_NAME)
ctypes: $(LIBRARY)
@$(DEL_FILE) $(CTYPES_LIB_DIR)/*.o
$(LIBRARY): $(OBJ_SO_FILES)
@mkdir -p $(CTYPES_LIB_DIR)
@$(CC) $(INCLUDE_PATHS) $(CC_SO_FLAGS) -o $@ $^
$(CTYPES_LIB_DIR)/%.o: $(SRC_DIR)/%.c
@mkdir -p $(dir $@)
@$(CC) $(INCLUDE_PATHS) $(CC_SO_FLAGS) -c -o $@ $<
$(BIN_DIR)/$(BINARY_NAME): $(OBJ_FILES)
@echo "CC | $@"
@mkdir -p $(BIN_DIR)
@$(CC) $(LDFLAGS) $(INCLUDE_PATHS) -o $@ $(OBJ_FILES) $(LIB_ISOCLINE)
$(BIN_DIR)/%.o: $(SRC_DIR)/%.c
@echo "CC | $@"
@mkdir -p $(dir $@)
@$(CC) $(CFLAGS) $(INCLUDE_PATHS) -c $< -o $@ $(LIB_ISOCLINE)
# Очистка
clean:
@echo "Clean..."
@rm -rf $(BIN_DIR)
@rm -rf $(CTYPES_LIB_DIR)/*
reinstall: clean all
.PHONY: all build example_plugin ctypes clean reinstall
`
我们新增了一个标签------编译基础插件。在本节中,我们将创建一个简单的插件加载和创建系统。
最后一步是构建项目结构和架构:
`.
├── build_scripts
│ ├── build_make.sh
│ ├── create_package.sh
│ ├── uninstall.sh
│ └── update.sh
├── CHANGELOG.md
├── config/
├── .sgaliases
└── .shegangrc
├── ctypes_lib
│ ├── bin
│ ├── README.md
│ └── shegang.py
├── docs
│ ├── en
│ │ ├── article.md
│ │ ├── changes.md
│ │ ├── index.md
│ │ └── project_architecture.md
│ ├── preview.gif
│ └── ru
│ ├── article.md
│ ├── article-part2.md
│ ├── index.md
│ └── project_architecture.md
├── include
│ ├── autocomplete.h
│ ├── builtin.h
│ ├── colorschemes.h
│ ├── colors.h
│ ├── config.h
│ ├── environment.h
│ ├── executor.h
│ ├── fuzzy.h
│ ├── isocline.h
│ ├── plugins_manager.h
│ ├── shell_api.h
│ ├── tasks_processing.h
│ └── userinput.h
├── libisocline.a
├── LICENSE
├── Makefile
├── pipegang_plugin.so
├── plugins
│ └── pipegang_plugin.c
├── README.md
├── shegang_en.man
└── src
├── config
│ └── config.c
├── core
│ └── userinput.c
├── execution
│ ├── builtin
│ │ ├── base.c
│ │ ├── gapf.c
│ │ ├── lsblk.c
│ │ ├── ls.c
│ │ └── shegang_config.c
│ ├── executor.c
│ └── tasks_processing.c
├── features
│ ├── autocomplete.c
│ ├── colors.c
│ ├── colorschemes.c
│ ├── environment.c
│ └── fuzzysearch.c
├── plugins
│ └── plugins_manager.c
└── shegang.c
`
如您所见,项目树已经相当庞大。让我们从上到下依次介绍:
-
build_make.shbuild_scripts --- 用于构建( )、创建程序存档(create_package.sh)、删除(uninstall.sh)和更新( )的辅助 bash 脚本update.sh。 -
config --- 包含 shell 配置示例的目录:
# .sgaliases`
`ls=`sgls
`# .shegangrc`
`USERNAME_COLOR=`YELLOW
`PWD_COLOR=`GREEN
`TIME_COLOR=`MAGENTA
`COLORSCHEME=`gruvbox
`
-
ctypes_lib --- 与文章第一部分相同,目前还没有任何变化。
-
docs 是一个包含大量文档的目录。顺便一提,您可以在此目录中查看这些文章的文本版本。
-
include 目录存放头文件:builtin.h(包含 src/builtin 目录下的所有 C 文件)、shell_api.h(包含宏定义,未来可能包含常用函数)、isocline.h。其余目录对应源代码文件。
-
libisocline.a - 等倾库。
-
许可证 --- 许可证。
-
Makefile 是一个构建文件。
-
pipegang_plugin.so 是插件文件(它以共享对象的形式连接到我们的 shell)。
-
插件 --- 插件的源代码。
-
shegang_en.man --- man 工具的文件。
-
src 是源代码目录。config 子目录包含用于读取配置的 config.c 文件;core 子目录包含用于读取用户输入和其他基本功能的 userinput.c 文件;execution 子目录最为庞大,包含用于执行命令的 executor.c 文件、用于启动进程的 tasks_processing.c 文件、包含内置实用程序(例如 ls (sgls)、lsblk (sglsblk)、shegang_config、gapf(类似于 cat))的 builtin 子目录,以及包含基本命令的 base.c 文件;features 目录包含一些重要功能,例如自动补全和高亮显示 (autocomplete.c)、用于彩色输出的 colors.c、用于语法高亮配色方案的 colorschemes.c、用于处理环境变量的 environment 以及用于模糊命令查找(如果用户输入了不存在的命令)的 fuzzysearch;plugins 目录包含插件管理器,以及主 shegang.c 文件本身。
那么,让我们从最简单的文件开始include/shell_api.h------这是一个包含宏的头文件:
#ifndef SHELL_API_H`
`#define SHELL_API_H`
`#define CONFIG_FILE "/.shegangrc" `
`#define CONFIG_ALIASES "/.sgaliases" `
`#define DEFAULT_USERNAME_COLOR "\033[0;33m" `
`#define DEFAULT_PWD_COLOR "\033[0;32m" `
`#define DEFAULT_CURR_TIME_COLOR "\033[0;35m" `
`#define MAX_LINE_LENGTH 256`
`#define MAX_ALIASES 128`
`#define DEFAULT_BUFFER_SIZE 256`
`#define TOKENS_DELIMETERS " \t" `
`#define MAX_FILE_NAME_LENGTH 1024 `
`#define MAX_GRID_COLUMNS 3 `
`#define INIT_CAPACITY 1024 `
`#define MAX_PROGRAMS 4096 `
`#define BIN_DIR "/bin" `
`#define RESET "\x1b[0m"`
`#define BOLD "\x1b[1m"`
`#define DIM "\x1b[2m"`
`#define ITALIC "\x1b[3m"`
`#define UNDERLINE "\x1b[4m"`
`#define BLINK "\x1b[5m"`
`#define REVERSE "\x1b[7m"`
`#define HIDDEN "\x1b[8m"`
`#define BLACK "\x1b[30m"`
`#define RED "\x1b[31m"`
`#define GREEN "\x1b[32m"`
`#define YELLOW "\x1b[33m"`
`#define BLUE "\x1b[34m"`
`#define MAGENTA "\x1b[35m"`
`#define CYAN "\x1b[36m"`
`#define WHITE "\x1b[37m"`
`#define GRAY "\x1b[90m"`
`#define DEBUG -1`
`#define INFO 0`
`#define WARNING 1`
`#define ERROR 2`
`#define MAX_DIRECTORY_PATH 2048 `
`#define MAX_SCHEME_NAME_LENGTH 64 `
`#define MAX_COLOR_NAME_LENGTH 64 `
`#define MAX_COLOR_HEX_LENGTH 8 `
`#endif // SHELL_API_H`
`
下一步,与上一篇文章一样,是模块部分colors.c:
#include <pwd.h>`
`#include <stdio.h>`
`#include <string.h>`
`#include <stdlib.h>`
`#include <unistd.h>`
`#include <wait.h>`
`#include <string.h>`
`#include <time.h>`
`#include <wait.h>`
`#include "shell_api.h"`
`extern` `char*` `username_color`;
`extern` `char*` `pwd_color`;
`extern` `char*` `curr_time_color`;
`int` `global_status_code` `=` `0`;
`char*` `get_color_by_name`(`const` `char*` `color_name`) {
`struct` {`char` `*text`, `*code`; } `colors`[] `=` {
{.`text="RESET"` , .`code` `=` `RESET`},
{.`text="BLACK"` , .`code` `=` `BLACK`},
{.`text="RED"` , .`code` `=` `RED`},
{.`text="GREEN"` , .`code` `=` `GREEN`},
{.`text="YELLOW"` , .`code` `=` `YELLOW`},
{.`text="BLUE"` , .`code` `=` `BLUE`},
{.`text="MAGENTA"`, .`code` `=` `MAGENTA`},
{.`text="CYAN"` , .`code` `=` `CYAN`},
{.`text="WHITE"` , .`code` `=` `WHITE`},
{.`text="GRAY"` , .`code` `=` `GRAY`}
};
`const` `int` `len` `=` `sizeof`(`colors`) `/` `sizeof`(`colors`[`0`]);
`for` (`int` `i` `=` `0`; `i` `<` `len`; `++i`) {
`if` (`strcmp`(`color_name`, `colors`[`i`].`text`) `==` `0`) {
`return` `colors`[`i`].`code`;
}
}
`return` `NULL`;
}
`void` `println`(`const` `char*` `message`) {
`printf`(`"%s\n"`, `message`);
}
`void` `println_colored`(`const` `char*` `message`, `char*` `message_color`) {
`printf`(`"%s%s%s\n"`, `message_color`, `message`, `RESET`);
}
`void` `print_colored`(`const` `char*` `message`, `char*` `message_color`) {
`printf`(`"%s%s%s"`, `message_color`, `message`, `RESET`);
}
`void` `print_message`(`const` `char*` `message`, `int` `message_type`) {
`const` `char*` `color`;
`const` `char*` `format`;
`const` `char*` `msgtype_string`;
`switch` (`message_type`) {
`case` `DEBUG`:
`color` `=` `CYAN`;
`format` `=` `BOLD`;
`msgtype_string` `=` `"[DEBUG]"`;
`break`;
`case` `INFO`:
`color` `=` `GREEN`;
`format` `=` `BOLD`;
`msgtype_string` `=` `"[INFO]"`;
`break`;
`case` `WARNING`:
`color` `=` `YELLOW`;
`format` `=` `DIM`;
`msgtype_string` `=` `"[WARNING]"`;
`break`;
`case` `ERROR`:
`color` `=` `RED`;
`format` `=` `BOLD`;
`msgtype_string` `=` `"[ERROR]"`;
`break`;
`default`:
`color` `=` `WHITE`;
`format` `=` `RESET`;
`msgtype_string` `=` `"[DEFAULT]"`;
`break`;
}
`if` (`message_type` `==` `ERROR`) {
`fprintf`(`stderr`, `"%s%s%s%s%s %s\n"`, `RESET`, `color`, `format`, `msgtype_string`, `RESET`, `message`);
} `else` {
`printf`(`"%s%s%s%s %s%s\n"`, `RESET`, `color`, `format`, `msgtype_string`, `RESET`, `message`);
}
`printf`(`RESET`);
}
`void` `display_ps`(`int` `status`) {
`pid_t` `uid` `=` `geteuid`();
`struct` `passwd` `*pw` `=` `getpwuid`(`uid`);
`char` `cwd`[`MAX_DIRECTORY_PATH`];
`time_t` `rawtime`;
`struct` `tm` `*` `timeinfo`;
`time`(`&rawtime`);
`timeinfo` `=` `localtime`(`&rawtime`);
`printf`(`"%s┬─%s%s[%s"`, `DIM`, `RESET`, `GRAY`, `RESET`);
`if` (`pw` `!=` `NULL`) {
`printf`(`"%s%s%s:"`, `username_color`, `pw->pw_name`, `RESET`);
`if` (`getcwd`(`cwd`, `MAX_DIRECTORY_PATH`) `!=` `NULL`) {
`printf`(`"%s%s%s]%s"`, `pwd_color`, `cwd`, `GRAY`, `RESET`);
}
`printf`(`"%s─%s"`, `DIM`, `RESET`);
`printf`(`"%s[%s%s%d:%d:%d%s%s] %s"`, `GRAY`, `RESET`, `curr_time_color`, `timeinfo->tm_hour`, `timeinfo->tm_min`, `timeinfo->tm_sec`, `RESET`, `GRAY`, `RESET`); `// время`
`if` (`global_status_code` `!=` `0`) {
`printf`(`"%s%d ✘%s"`, `RED`, `global_status_code`, `RESET`);
} `else` `if` (`global_status_code` `==` `0` `&&` `status` `!=` `1`) {
`printf`(`"%s%d ✘%s"`, `RED`, `status`, `RESET`);
}
`global_status_code` `=` `0`;
}
`
下一步是读取用户输入------即文件core/userinput.c。该文件还包含消息标记化函数:
#include <pwd.h>`
`#include <stdio.h>`
`#include <stdlib.h>`
`#include <string.h>`
`#include <unistd.h>`
`#include "isocline.h"`
`#include "colors.h"`
`#include "autocomplete.h"`
`#include "shell_api.h"`
`char*` `r_line`(`char*` `filename`, `int` `line_n`) {
`FILE*` `file` `=` `fopen`(`filename`, `"r"`);
`char` `line`[`256`];
`char` `*data` `=` `malloc`(`sizeof`(`char`) `*` `256`);
`int` `i` `=` `0`;
`while` (`fgets`(`line`, `sizeof`(`line`), `file`)) {
`i++`;
`if` (`i` `==` `line_n`)
`strcpy`(`data`, `line`);
}
`fclose`(`file`);
`return` `data`;
}
`char*` `get_history_file_path`(`void`) {
`char*` `home_dir` `=` `getenv`(`"HOME"`);
`if` (`home_dir` `==` `NULL`) {
`return` `NULL`;
}
`char*` `history_file_path` `=` (`char*`)`malloc`(`1024`);
`if` (`history_file_path` `==` `NULL`) {
`return` `NULL`;
}
`snprintf`(`history_file_path`, `1024`, `"%s/.sghistory"`, `home_dir`);
`return` `history_file_path`;
}
`void` `setup_isocline`(`void`) {
`ic_style_def`(`"kbd"`, `"white underline"`);
`ic_style_def`(`"ic-prompt"`, `"gray"`);
`printf`(`"%s\n"`, `get_history_file_path`());
`ic_set_history`(`get_history_file_path`(), `1024`);
`ic_set_default_completer`(`&completer`, `NULL`);
`ic_set_default_highlighter`(`highlighter`, `NULL`);
`ic_enable_auto_tab`(`true`);
`ic_set_hint_delay`(`100`); `// `
`ic_enable_brace_matching`(`true`); `// `
`ic_enable_brace_insertion`(`true`); `// `
`ic_enable_inline_help`(`true`); `// `
}
`/**`
`*/`
`char*` `read_user_input`(`void`) {
`printf`(`"\n"`);
`char*` `input_buffer` `=` `ic_readline`(`"╰─"`);
`// If input buffer is not plain string`
`if` (`input_buffer` `==` `NULL`) {
`print_message`(`"Failed to read user input"`, `WARNING`);
`return` `NULL`;
}
`// if input buffer is not empty`
`if` (`*input_buffer` `!=` `'\0'`) {
`ic_history_add`(`input_buffer`);
}
`return` `input_buffer`;
}
`/**`
`* @brief `
`*`
`* @param `
`* @param `
`*`
`* @return `
`*/`
`char**` `split_by_delims`(`char*` `line`, `const` `char*` `delims`, `int` `*num_tokens`) {
`char` `**tokens` `=` `NULL`;
`char` `*token`;
`int` `i` `=` `0`;
`char` `*line_copy` `=` `malloc`(`strlen`(`line`) `+` `1`);
`strcpy`(`line_copy`, `line`);
`token` `=` `strtok`(`line_copy`, `delims`);
`while` (`token` `!=` `NULL`) {
`tokens` `=` `realloc`(`tokens`, (`i` `+` `1`) `*` `sizeof`(`char` `*`));
`tokens`[`i`] `=` `malloc`(`strlen`(`token`) `+` `1`);
`strcpy`(`tokens`[`i`], `token`);
`i++`;
`token` `=` `strtok`(`NULL`, `delims`);
}
`*num_tokens` `=` `i`;
`free`(`line_copy`);
`return` `tokens`;
}
`/**`
`* @brief `
`*`
`* @param `
`*`
`* @return `
`*/`
`char**` `split_into_tokens`(`char*` `line`) {
`size_t` `position` `=` `0`;
`size_t` `buffer_size` `=` `DEFAULT_BUFFER_SIZE`;
`char*` `token`;
`char**` `tokens` `=` (`char**`)`malloc`(`sizeof`(`char*`) `*` `buffer_size`);
`// `
`if` (`tokens` `==` `NULL`) {
`print_message`(`"Couldn't allocate buffer for splitting"`, `ERROR`);
`return` `NULL`;
}
`// `
`token` `=` `strtok`(`line`, `TOKENS_DELIMETERS`);
`while` (`token` `!=` `NULL`) {
`tokens`[`position++`] `=` `token`;
`if` (`position` `>=` `buffer_size`) {
`buffer_size` `*=` `2`;
`tokens` `=` (`char**`)`realloc`(`tokens`, `buffer_size` `*` `sizeof`(`char*`));
`if` (`tokens` `==` `NULL`) {
`print_message`(`"Couldn't reallocate buffer for tokens"`, `ERROR`);
`return` `NULL`;
}
}
`token` `=` `strtok`(`NULL`, `TOKENS_DELIMETERS`);
}
`tokens`[`position`] `=` `NULL`;
`return` `tokens`;
}
`
现在我们需要创建一个用于自动补全和语法高亮显示的模块------autocomplete.c:
#include <dirent.h>`
`#include <errno.h>`
`#include <stdio.h>`
`#include <stdlib.h>`
`#include <string.h>`
`#include <unistd.h>`
`#include <sys/types.h>`
`#include <sys/stat.h>`
`#include <fcntl.h>`
`#include "isocline.h"`
`#include "colorschemes.h" // `
`#include "tasks_processing.h" // `
`#include "shell_api.h"`
`extern` `ColorScheme*` `current_theme`; `// `
`// `
`const` `char*` `builtin_utils`[] `=` {`"shegang_config"`, `"sgls"`, `"sglsblk"`, `"help"`, `"exit"`,
`"term"`, `"reload"`, `"history"`, `"gapf"`, `"quit"`, `"bg"`, `"term"`,
`"environment"`, `"sghint"`, `"echon"`, `"sghint"`, `NULL`};
`/**`
`* @brief `
`*`
`* @return `
`*/`
`char` `**get_sys_binaries`(`void`) {
`DIR` `*dir`;
`struct` `dirent` `*entry`;
`char` `**progs` `=` `malloc`(`INIT_CAPACITY` `*` `sizeof`(`char` `*`));
`size_t` `count` `=` `0`, `capacity` `=` `INIT_CAPACITY`;
`// `
`dir` `=` `opendir`(`BIN_DIR`);
`if` (`dir` `==` `NULL`) {
`fprintf`(`stderr`, `"Ошибка открытия каталога %s: %s\n"`, `BIN_DIR`, `strerror`(`errno`));
`return` `NULL`;
}
`// `
`while` ((`entry` `=` `readdir`(`dir`)) `!=` `NULL`) {
`if` (`entry->d_name`[`0`] `==` `'.'`)
`continue`;
`char` `path`[`128`];
`snprintf`(`path`, `128`, `BIN_DIR` `"/%s"`, `entry->d_name`);
`struct` `stat` `st`;
`if` (`stat`(`path`, `&st`) `==` `0` `&&` (`st`.`st_mode` `&` `S_IXUSR`)) {
`if` (`count` `>=` `capacity`) { `// `
`capacity` `*=` `2`;
`progs` `=` `realloc`(`progs`, `capacity` `*` `sizeof`(`char` `*`));
`if` (`progs` `==` `NULL`) {
`closedir`(`dir`);
`return` `NULL`;
}
}
`progs`[`count++`] `=` `strdup`(`entry->d_name`);
}
}
`// `
`for` (`int` `i` `=` `0`; `i` `<` `sizeof`(`builtin_utils`) `/` `sizeof`(`builtin_utils`[`0`]); `i++`) {
`if` (`builtin_utils`[`i`] `==` `0`)
`break`;
`progs`[`count++`] `=` `strdup`(`builtin_utils`[`i`]);
}
`// `
`progs` `=` `realloc`(`progs`, (`count` `+` `1`) `*` `sizeof`(`char` `*`));
`progs`[`count`] `=` `NULL`;
`if` (`closedir`(`dir`) `!=` `0`) {
`fprintf`(`stderr`, `" %s: %s\n"`, `BIN_DIR`, `strerror`(`errno`));
}
`return` `progs`;
}
`/**`
`* @brief `
`*`
`* @param `
`* @param[in] `
`*/`
`void` `word_completer`(`ic_completion_env_t*` `cenv`, `const` `char*` `word`) {
`ic_add_completions`(`cenv`, `word`, `get_sys_binaries`()); `// `
`if` (`strcmp`(`word`, `"s"`) `==` `0`) { `// `
`ic_add_completion`(`cenv`, `"sgls"`);
`ic_add_completion`(`cenv`, `"sghint"`);
`ic_add_completion`(`cenv`, `"sglsblk"`);
`ic_add_completion`(`cenv`, `"shegang_config"`);
`ic_add_completion`(`cenv`, `"su"`);
`ic_add_completion`(`cenv`, `"sudo"`);
} `else` `if` (`strcmp`(`word`, `"sghint"`) `==` `0` `||` `strcmp`(`word`, `"sghint "`) `==` `0`) {
`// `
`char*` `command_completion` `=` (`char*`)`malloc`(`1024`);
`// `
`for` (`int` `i` `=` `0`; `i` `<` `sizeof`(`builtin_utils`) `/` `sizeof`(`builtin_utils`[`0`]); `i++`) {
`if` (`builtin_utils`[`i`] `==` `NULL`) {
`break`;
}
`snprintf`(`command_completion`, `1024`, `"sghint %s"`, `builtin_utils`[`i`]);
`ic_add_completion`(`cenv`, `command_completion`);
}
} `else` `if` (`strcmp`(`word`, `"shegang"`) `==` `0`) {
`ic_add_completion`(`cenv`, `"shegang_config"`);
} `else` `if` (`strcmp`(`word`, `"t"`) `==` `0`) {
`ic_add_completion`(`cenv`, `"top"`);
`ic_add_completion`(`cenv`, `"time"`);
`ic_add_completion`(`cenv`, `"touch"`);
`ic_add_completion`(`cenv`, `"tar"`);
`ic_add_completion`(`cenv`, `"touch"`);
`ic_add_completion`(`cenv`, `"tail"`);
`ic_add_completion`(`cenv`, `"term"`);
} `else` `if` (`strcmp`(`word`, `"e"`) `==` `0`) {
`ic_add_completion`(`cenv`, `"environment"`);
}
}
`/**`
`* @brief `
`*`
`* @param `
`* @param[in] `
`*/`
`void` `completer`(`ic_completion_env_t*` `cenv`, `const` `char*` `input`) {
`ic_complete_filename`(`cenv`, `input`, `0`, `".;/usr/local;c:\\Program Files"`, `NULL`); `// `
`ic_complete_word`(`cenv`, `input`, `&word_completer`, `NULL`); `// и автодополнение по фразам`
}
`/**`
`* @brief Подсветка синтаксиса`
`*`
`* @param henv highlight env`
`* @param[in] input Ввод`
`* @param arg Аргумент`
`*/`
`void` `highlighter`(`ic_highlight_env_t*` `henv`, `const` `char*` `input`, `void*` `arg`) {
`long` `len` `=` (`long`)`strlen`(`input`);
`for` (`long` `i` `=` `0`; `i` `<` `len`;) {
`const` `char*` `keywords`[] `=` {`"then"`, `"if"`, `"fi"`, `"else"`, `"do"`, `"while"`, `"function"`, `"return"`, `"not"`, `"null"`,
`"false"`, `"true"`, `"and"`, `"or"`, `NULL`}; `// `
`const` `char*` `commands`[] `=` {`"bash"`, `"diff"`, `"cat"`, `"chown"`, `"chmod"`, `"chgrp"`, `"cp"`, `"dd"`, `"du"`, `"df"`, `"ln"`,
`"ls"`, `"mkdir"`, `"mkfifo"`, `"mv"`, `"rm"`, `"head"`, `"tail"`, `"md5sum"`, `"basename"`,
`"chroot"`, `"date"`, `"echo"`, `"env"`, `"nice"`, `"nohup"`, `"sleep"`, `"printf"`,
`"find"`, `"xargs"`, `"awk"`, `"grep"`, `"grub"`, `"gzip"`, `"unzip"`, `"zip"`, `"tar"`,
`"ssh"`, `"telnet"`, `"time"`, `"make"`, `"gcc"`, `"clang"`, `"gdb"`, `"rmdir"`, `"mkdir"`,
`"cd"`, `NULL`}; `// `
`const` `char*` `operators`[] `=` {`"=="`, `">="`, `"<="`, `"!="`, `">"`, `"<"`, `"+"`, `"-"`, `"*"`, `"/"`, `"&&"`, `"||"`, `NULL`}; `// `
`long` `tlen`; `// `
`if` ((`tlen` `=` `ic_match_any_token`(`input`, `i`, `&ic_char_is_idletter`, `keywords`)) `>` `0`) {
`ic_highlight`(`henv`, `i`, `tlen`, `get_color`(`current_theme`, `"keywords"`).`hex`); `// `
`i` `+=` `tlen`;
} `else` `if` ((`tlen` `=` `ic_match_any_token`(`input`, `i`, `&ic_char_is_idletter`, `commands`)) `>` `0`) {
`ic_highlight`(`henv`, `i`, `tlen`, `get_color`(`current_theme`, `"commands"`).`hex`); `// `
`i` `+=` `tlen`;
} `else` `if` ((`tlen` `=` `ic_match_any_token`(`input`, `i`, `&ic_char_is_idletter`, `builtin_utils`)) `>` `0`) {
`ic_highlight`(`henv`, `i`, `tlen`, `get_color`(`current_theme`, `"programs"`).`hex`); `// `
`i` `+=` `tlen`;`если это цифра`
`ic_highlight`(`henv`, `i`, `tlen`, `get_color`(`current_theme`, `"digits"`).`hex`); `// `
`i` `+=` `tlen`;
} `else` `if` ((`tlen` `=` `ic_is_token`(`input`, `i`, `&ic_char_is_separator`)) `>` `0`) { `// `
`ic_highlight`(`henv`, `i`, `tlen`, `get_color`(`current_theme`, `"light_gray"`).`hex`); `// `
`i` `+=` `tlen`;
} `else` `if` (`ic_starts_with`(`input` `+` `i`, `"#"`)) { `// `
`tlen` `=` `1`;
`while` (`i` `+` `tlen` `<` `len` `&&` `input`[`i` `+` `tlen`] `!=` `'\n'`) {
`tlen++`;
}
`ic_highlight`(`henv`, `i`, `tlen`, `get_color`(`current_theme`, `"dark_gray"`).`hex`); `// `
`i` `+=` `tlen`;
} `else` `if` (`ic_starts_with`(`input` `+` `i`, `"//"`)) { `// `
`tlen` `=` `2`;
`while` (`i` `+` `tlen` `<` `len` `&&` `input`[`i` `+` `tlen`] `!=` `'\n'`) {
`tlen++`;
}
`ic_highlight`(`henv`, `i`, `tlen`, `get_color`(`current_theme`, `"dark_gray"`).`hex`); `// `
`i` `+=` `tlen`;
} `else` {
`if` ((`tlen` `=` `ic_match_any_token`(`input`, `i`, `&ic_char_is_nonwhite`, `operators`)) `>` `0`) {
`ic_highlight`(`henv`, `i`, `tlen`, `get_color`(`current_theme`, `"operators"`).`hex`); `// `
`i` `+=` `tlen`;
} `else` {
`ic_highlight`(`henv`, `i`, `1`, `NULL`); `// `
`i++`;
}
}
}
}
`
为了更好地理解自动补全代码,特别是语法高亮部分,让我们来实现 colorschemes.c:
#include <stdio.h>`
`#include <stdlib.h>`
`#include <string.h>`
`#include "shell_api.h"`
`// `
`typedef` `struct` {
`char` `name`[`MAX_SCHEME_NAME_LENGTH`];
`char` `hex`[`MAX_COLOR_HEX_LENGTH`];
} `Color`;
`// `
`typedef` `struct` {
`char` `name`[`MAX_SCHEME_NAME_LENGTH`];
`Color` `blue`, `red`, `orange`, `purple`, `yellow`, `green`, `cyan`, `white`, `black`, `light_gray`, `dark_gray`;
`Color` `keywords`, `operators`, `digits`, `strings`, `comments`, `programs`, `commands`;
} `ColorScheme`;
`// Gruvbox`
`ColorScheme` `gruvbox` `=` {
.`name` `=` `"gruvbox"`,
.`blue` `=` {`"blue"`, `"#83a598"`},
.`red` `=` {`"red"`, `"#fb4934"`},
.`orange` `=` {`"orange"`, `"#fe8019"`},
.`purple` `=` {`"purple"`, `"#d3869b"`},
.`yellow` `=` {`"yellow"`, `"#fabd2f"`},
.`green` `=` {`"green"`, `"#b8bb26"`},
.`cyan` `=` {`"cyan"`, `"#8ec07c"`},
.`white` `=` {`"white"`, `"#edbbb2"`},
.`black` `=` {`"black"`, `"#282828"`},
.`light_gray` `=` {`"light_gray"`, `"#b9aa95"`},
.`dark_gray` `=` {`"dark_gray"`, `"#504945"`},
.`keywords` `=` {`"keywords"`, `"#83a598"`},
.`operators` `=` {`"operators"`, `"#fb4934"`},
.`digits` `=` {`"digits"`, `"#b8bb26"`},
.`strings` `=` {`"strings"`, `"#b8bb26"`},
.`comments` `=` {`"comments"`, `"#a89984"`},
.`programs` `=` {`"programs"`, `"#d3869b"`},
.`commands` `=` {`"commands"`, `"#fe8019"`}
};
`// onedark`
`ColorScheme` `onedark` `=` {
.`name` `=` `"gruvbox"`,
.`blue` `=` {`"blue"`, `"#61afef"`},
.`red` `=` {`"red"`, `"#e06c75"`},
.`orange` `=` {`"orange"`, `"#d19a66"`},
.`purple` `=` {`"purple"`, `"#c678dd"`},
.`yellow` `=` {`"yellow"`, `"#e5c07b"`},
.`green` `=` {`"green"`, `"#98c379"`},
.`cyan` `=` {`"cyan"`, `"#56b6c2"`},
.`white` `=` {`"white"`, `"#abb2bf"`},
.`black` `=` {`"black"`, `"#282c34"`},
.`light_gray` `=` {`"light_gray"`, `"#9aa1ae"`},
.`dark_gray` `=` {`"dark_gray"`, `"#3e4452"`},
.`keywords` `=` {`"keywords"`, `"#61afef"`},
.`operators` `=` {`"operators"`, `"#e06c75"`},
.`digits` `=` {`"digits"`, `"#98c379"`},
.`strings` `=` {`"strings"`, `"#98c379"`},
.`comments` `=` {`"comments"`, `"#9aa1ae"`},
.`programs` `=` {`"programs"`, `"#c678dd"`},
.`commands` `=` {`"commands"`, `"#d19a66"`}
};
`// `
`ColorScheme*` `current_theme` `=` `&gruvbox`;
`/**`
`* @brief `
`*`
`* @param scheme The scheme`
`*/`
`void` `set_color_scheme`(`ColorScheme*` `scheme`) {
`current_theme` `=` `scheme`;
}
`/**`
`* @brief `
`*`
`* @param `
`*/`
`void` `get_scheme_by_name`(`char*` `name`) {
`if` (`strcmp`(`name`, `"gruvbox"`) `==` `0`) {
`set_color_scheme`(`&gruvbox`);
`return`;
} `else` `if` (`strcmp`(`name`, `"onedark"`) `==` `0`) {
`set_color_scheme`(`&onedark`);
`return`;
}
`set_color_scheme`(`&gruvbox`);
}
`/**`
`* @brief `
`*`
`* @param scheme `
`* @param name `
`*`
`* @return `
`*/`
`Color` `get_color`(`ColorScheme*` `scheme`, `char*` `name`) {
`struct` {`char` `*text`; `Color` `color`; } `colors`[] `=` {
{.`text="blue"` , .`color` `=` `scheme->blue`},
{.`text="red"` , .`color` `=` `scheme->red`},
{.`text="orange"` , .`color` `=` `scheme->orange`},
{.`text="yellow"` , .`color` `=` `scheme->yellow`},
{.`text="green"` , .`color` `=` `scheme->green`},
{.`text="cyan"` , .`color` `=` `scheme->cyan`},
{.`text="white"` , .`color` `=` `scheme->white`},
{.`text="black"` , .`color` `=` `scheme->black`},
{.`text="light_gray"` , .`color` `=` `scheme->light_gray`},
{.`text="dark_gray"` , .`color` `=` `scheme->dark_gray`},
{.`text="keywords"` , .`color` `=` `scheme->keywords`},
{.`text="operators"` , .`color` `=` `scheme->operators`},
{.`text="purple"` , .`color` `=` `scheme->purple`},
{.`text="digits"` , .`color` `=` `scheme->digits`},
{.`text="strings"` , .`color` `=` `scheme->strings`},
{.`text="comments"` , .`color` `=` `scheme->comments`},
{.`text="programs"` , .`color` `=` `scheme->programs`},
{.`text="commands"` , .`color` `=` `scheme->commands`},
};
`const` `int` `len` `=` `sizeof`(`colors`) `/` `sizeof`(`colors`[`0`]);
`for` (`int` `i` `=` `0`; `i` `<` `len`; `++i`) {
`if` (`strcmp`(`name`, `colors`[`i`].`text`) `==` `0`) {
`return` `colors`[`i`].`color`;
}
}
`return` (`Color`){.`name` `=` `"invalid"`, .`hex="#FFFFFF"`};
`
接下来两步,我们将完成 src/features 目录的开发。如果您还记得项目结构,可能会注意到还有两个未实现的模块:environment.c 和 fuzzysearch.c。我们先从 environment.c 开始。它包含一些用于获取和创建环境变量的辅助命令。
#include <stdio.h>`
`#include <string.h>`
`#include <stdlib.h>`
`int` `setenv`(`const` `char*` `envname`, `const` `char*envval`, `int` `overwrite`);
`/**`
`* @brief `
`*`
`* @param[in] variable_name `
`*`
`* @return `
`*/`
`char` `*get_environment_variable`(`char*` `variable_name`) {
`char*` `variable_value` `=` `getenv`(`variable_name`);
`if` (`variable_value` `==` `NULL`)
`return` `NULL`;
`return` `variable_value`;
}
`/**`
`* @brief `
`*`
`* @param[in] variable_name `
`* @param[in] variable_value `
`*`
`* @return string Статус`
`*/`
`char*` `set_environment_variable`(`char*` `variable_name`, `const` `char*` `variable_value`) {
`int` `status` `=` `setenv`(`variable_name`, `variable_value`, `1`);
`if` (`status` `==` `1`) {
`return` `"ERROR"`;
} `else` {
`return` `"SUCCESS"`;
}
}
`
现在我们可以处理功能目录中的最后一个模块------fuzzysearch.c。
#include <stdio.h>`
`#include <stdlib.h>`
`#include <string.h>`
`#include <ctype.h>`
`#include <time.h>`
`#include <stdbool.h>`
`#include "fuzzy.h"`
`/**`
`* @brief Fuzzy search`
`*`
`* @param[in] text The text`
`* @param[in] query The query`
`* @param[in] build_score The build score`
`* @param score The score`
`* @param score_len The score length`
`*`
`* @return { description_of_the_return_value }`
`*/`
`int` `fuzzy_search`(`const` `char` `*text`, `const` `char` `*query`, `bool` `build_score`, `int` `**score`, `size_t` `*score_len`) {
`size_t` `total_score` `=` `0`;
`if` (`build_score`) { `// Build score is an optimization when searching through large database`
(`*score`) `=` (`int*`)`malloc`(`sizeof`(`int`) `*` `strlen`(`text`));
`memset`(`*score`, `0`, `sizeof`(`int`)`*strlen`(`text`));
`*score_len` `=` `strlen`(`text`);
}
`size_t` `first_character_boosts` `=` `1`;
`for` (`size_t` `t_idx` `=` `0`; `t_idx` `<` `strlen`(`text`); `t_idx++`) {
`char` `t` `=` `tolower`(`text`[`t_idx`]); `// NOTE(deter0): to lower performs kind of strangely probably due to UTF8`
`for` (`size_t` `q_idx` `=` `0`; `q_idx` `<` `strlen`(`query`); `q_idx++`) {
`char` `q` `=` `tolower`(`query`[`q_idx`]);
`if` (`t` `==` `q`) {
`// Start of word awards more but falls off fast`
`if` (`t_idx` `==` `0` `||` (`t_idx` `>` `0` `&&` `isspace`(`text`[`t_idx` `-` `1`]))) {
`int` `factor` `=` `8/`(`first_character_boosts++`);
`if` (`build_score`) (`*score`)[`t_idx`] `+=` `factor`;
`total_score` `+=` `factor`;
} `else` {
`if` (`build_score`) (`*score`)[`t_idx`]`++`;
`total_score++`;
}
`size_t` `streak` `=` `0`;
`for` (`size_t` `s_idx` `=` `1`; `s_idx` `<` `strlen`(`query`)`-q_idx`; `s_idx++`) {
`char` `sq` `=` `tolower`(`query`[`q_idx` `+` `s_idx`]);
`char` `st` `=` `tolower`(`text`[`t_idx` `+` `s_idx`]);
`if` (`sq` `!=` `st`) `break`;
`streak++`;
`// Beginning of string yields few points more; eg -> "Term" :: "Terminus", "Fluent Terminal"`
`if` (((`float`)`t_idx` `/` (`float`)`strlen`(`text`)) `<=` `0.35`) {
`streak++`;
}
`int` `factor` `=` `streak*3/`(`strlen`(`query`)`*0.2`);
`if` (`build_score`)
(`*score`)[`t_idx` `+` `s_idx`] `+=` `factor`;
`total_score` `+=` `factor`;
}
`// (N * (N + 1) ) /2`
`// (*score)[t_idx] += (streak * (streak + 1)) / 2;`
`t_idx` `+=` `streak`;
}
}
}
`return` `total_score`;
}
`
现在我们来看配置目录,更准确地说,是同名文件(目前也是唯一一个)config.c。它负责配置。目前,它的配置相当基础,缺乏任何丰富的自定义选项:
#include <stdio.h>`
`#include <stdlib.h>`
`#include <string.h>`
`#include <unistd.h>`
`#include "colors.h"`
`#include "colorschemes.h"`
`#include "shell_api.h"`
`char*` `username_color`;
`char*` `pwd_color`;
`char*` `curr_time_color`;
`typedef` `struct` {
`char` `name`[`64`];
`char` `command`[`256`];
} `Alias`;
`Alias` `aliases`[`MAX_ALIASES`];
`int` `num_aliases` `=` `0`;
`/**`
`* load_config`
`* `
`* @brief `
`*/`
`void` `load_config_aliases`(`void`) {
`char` `*home_dir` `=` `getenv`(`"HOME"`);
`char` `config_path`[`strlen`(`home_dir`) `+` `strlen`(`CONFIG_ALIASES`) `+` `1`];
`sprintf`(`config_path`, `"%s%s"`, `home_dir`, `CONFIG_ALIASES`);
`FILE*` `config_file` `=` `fopen`(`config_path`, `"r"`);
`if` (`!config_file`) {
`return`;
}
`FILE` `*fp`;
`char` `alias_file`[`strlen`(`home_dir`) `+` `11`];
`sprintf`(`alias_file`, `"%s%s"`, `home_dir`, `CONFIG_ALIASES`);
`fp` `=` `fopen`(`alias_file`, `"r"`);
`if` (`fp` `==` `NULL`) {
`print_message`(`"Could not open config"`, `ERROR`);
`return`;
}
`char` `line`[`MAX_LINE_LENGTH`];
`while` (`fgets`(`line`, `MAX_LINE_LENGTH`, `fp`) `!=` `NULL` `&&` `num_aliases` `<` `MAX_ALIASES`) {
`char` `*alias_name` `=` `strtok`(`line`, `"="`);
`char` `*alias_cmd` `=` `strtok`(`NULL`, `"\n"`);
`if` (`alias_name` `!=` `NULL` `&&` `alias_cmd` `!=` `NULL`) {
`strncpy`(`aliases`[`num_aliases`].`name`, `alias_name`, `sizeof`(`aliases`[`num_aliases`].`name`) `-` `1`);
`strncpy`(`aliases`[`num_aliases`].`command`, `alias_cmd`, `sizeof`(`aliases`[`num_aliases`].`command`) `-` `1`);
`num_aliases++`;
}
}
`fclose`(`fp`);
}
`/**`
`* load_config`
`@brief `
`*/`
`void` `load_main_config`(`void`) {
`char*` `home_dir` `=` `getenv`(`"HOME"`);
`char` `line`[`256`];
`username_color` `=` `DEFAULT_USERNAME_COLOR`;
`pwd_color` `=` `DEFAULT_PWD_COLOR`;
`curr_time_color` `=` `DEFAULT_CURR_TIME_COLOR`;
`if` (`!home_dir`) {
`return`;
}
`// `
`char` `config_path`[`strlen`(`home_dir`) `+` `strlen`(`CONFIG_FILE`) `+` `1`];
`sprintf`(`config_path`, `"%s%s"`, `home_dir`, `CONFIG_FILE`);
`FILE*` `config_file` `=` `fopen`(`config_path`, `"r"`);
`if` (`!config_file`) {
`username_color` `=` `DEFAULT_USERNAME_COLOR`;
`pwd_color` `=` `DEFAULT_PWD_COLOR`;
`return`;
}
`// `
`while` (`fgets`(`line`, `sizeof`(`line`), `config_file`)) {
`char*` `key` `=` `strtok`(`line`, `"="`);
`char*` `value` `=` `strtok`(`NULL`, `"\n"`);
`if` (`key` `&&` `value`) {
`if` (`strcmp`(`key`, `"USERNAME_COLOR"`) `==` `0`) { `// `
`username_color` `=` `get_color_by_name`(`value`);
} `else` `if` (`strcmp`(`key`, `"PWD_COLOR"`) `==` `0`) { `// `
`pwd_color` `=` `get_color_by_name`(`value`);
} `else` `if` (`strcmp`(`key`, `"TIME_COLOR"`) `==` `0`) { `// `
`curr_time_color` `=` `get_color_by_name`(`value`);
} `else` `if` (`strcmp`(`key`, `"COLORSCHEME"`) `==` `0`) { `// `
`get_scheme_by_name`(`value`);
}
}
}
`fclose`(`config_file`);
}
`
最大部分
接下来我们来看内容最丰富的部分------执行目录。您可以在这里看到:
`src/execution/
├── builtin
│ ├── base.c
│ ├── gapf.c
│ ├── lsblk.c
│ ├── `ls`.c
│ └── shegang_config.c
├── executor.c
└── tasks_processing.c
`
让我们从最基本的模块 tasks_processing 开始。
但要实现它,我们需要了解一些基础知识。让我们从 fork 开始------简单来说,fork 系统调用会创建一个当前进程的完整副本;它们之间的区别仅在于它们的标识符,即 pid:
#include <unistd.h>`
`#include <stdio.h>`
`#include <wait.h>`
`int` `main`(`int` `argc`, `char*` `argv`[]) {
`pid_t` `pid` `=` `fork`();
`if` (`pid` `==` `0`) {
} `else` {
`wait`(`NULL`);
}
`return` `0`;
}
`
fork 系统调用会创建一个进程的克隆,即一个父进程和一个子进程。进程的执行顺序没有指定,因此父进程会等待子进程终止(wait(NULL);)。
exec 系统调用会将当前进程替换为第三方进程。当然,第三方进程是通过函数的参数指定的。
#include <stdio.h>`
`#include <stdlib.h>`
`#include <unistd.h>`
`#include <wait.h>`
`int` `main`(`int` `argc`, `char*` `argv`[]) {
`pid_t` `pid` `=` `fork`();
`if` (`pid` `==` `0`) {
`execlp`(`"echo"`, `"echo"`, `"I"`, `"Love"`, `"C"`, `NULL`);
`exit`(`1`);
} `else` {
`waitpid`(`pid`, `NULL`, `0`);
}
`return` `0`;
}
`
这个程序会打印"我爱 C"。这是怎么回事呢?很简单:父进程等待子进程终止,然后子进程会将自身替换为命令"echo 我爱 C"。
现在我们可以开始编写代码了。基本上,和上次相比并没有太大变化,但我们添加了模糊搜索和来自 colors.c 的全局状态码:
#include <wait.h>`
`#include <string.h>`
`#include <time.h>`
`#include <stdio.h>`
`#include <stdlib.h>`
`#include <unistd.h>`
`#include <signal.h>`
`#include <errno.h>`
`#include "colors.h"`
`#include "executor.h"`
`#include "autocomplete.h"`
`#include "fuzzy.h"`
`// Functions definitions`
`int` `kill`(`pid_t` `pid`, `int`);
`extern` `int` `global_status_code`; `// `
`/**`
`* `
`* `
`* @param pid_t PID `
`* @param int is_finished `
`* @param char* timestamp `
`* @param char* command `
`**/`
`struct` `background_task_t` {
`pid_t` `pid`;
`int` `is_finished`;
`char*` `timestamp`;
`char*` `command`;
};
`typedef` `struct` `background_task_t` `bg_task`;
`/**`
`* `
`* `
`* @param pid_t PID `
`* @param int is_finished `
`**/`
`struct` `foreground_task_t` {
`pid_t` `pid`;
`int` `is_finished`;
};
`typedef` `struct` `foreground_task_t` `fg_task`;
`/**`
`* `
`* `
`* @param fg_task foreground_task `
`* @param bg_task* background_task `
`* @param size_t cursor `
`* @param size_t capacity `
`**/`
`struct` `tasks_t` {
`fg_task` `foreground_task`;
`bg_task*` `background_task`;
`size_t` `cursor`;
`size_t` `capacity`;
};
`typedef` `struct` `tasks_t` `tasks`;
`/**`
`* @brief `
`**/`
`tasks` `tasks_structure` `=` {
.`foreground_task` `=` {
.`pid` `=` `-1`,
.`is_finished` `=` `1`
},
.`background_task` `=` `0`,
.`cursor` `=` `0`,
.`capacity` `=` `0`
};
`/**`
`* @brief `
`* `
`* @param pid_t pid PID `
`**/`
`void` `set_foreground_task`(`pid_t` `pid`) {
`tasks_structure`.`foreground_task`.`pid` `=` `pid`;
`tasks_structure`.`foreground_task`.`is_finished` `=` `0`;
}
`/**`
`* @brief `
`* `
`* @param pid_t pid PID `
`* @param char* name `
`* `
`* @return int `
`**/`
`int` `add_background_task`(`pid_t` `pid`, `char*` `name`) {
`bg_task*` `bt`;
`if` (`tasks_structure`.`cursor` `>=` `tasks_structure`.`capacity`) {
`tasks_structure`.`capacity` `=` `tasks_structure`.`capacity` `*` `2` `+` `1`;
`tasks_structure`.`background_task` `=` (`bg_task*`)`realloc`(`tasks_structure`.`background_task`, `sizeof`(`bg_task`) `*` `tasks_structure`.`capacity`);
`if` (`tasks_structure`.`background_task` `==` `0` `||` `tasks_structure`.`background_task` `==` `NULL`) {
`print_message`(`"Couldn't reallocate buffer for background tasks!"`, `ERROR`);
`return` `-1`;
}
}
`printf`(`"[%zu] task started\n"`, `tasks_structure`.`cursor`);
`bt` `=` `&tasks_structure`.`background_task`[`tasks_structure`.`cursor`];
`bt->pid` `=` `pid`;
`bt->is_finished` `=` `0`;
`time_t` `timestamp` `=` `time`(`NULL`);
`bt->timestamp` `=` `ctime`(`×tamp`);
`bt->command` `=` `name`;
`tasks_structure`.`cursor` `+=` `1`;
`return` `1`;
}
`/**`
`* @brief `
`**/`
`void` `kill_foreground_task`(`void`) {
`if` (`tasks_structure`.`foreground_task`.`pid` `!=` `-1`) {
`kill`(`tasks_structure`.`foreground_task`.`pid`, `SIGTERM`);
`tasks_structure`.`foreground_task`.`is_finished` `=` `1`;
`printf`(`"\n"`);
}
}
`/**`
`* @brief `
`* @param char** args `
`* `
`* @return int `
`**/`
`int` `term_background_task`(`char**` `args`) {
`char*` `idx_str`;
`int` `proc_idx` `=` `0`;
`if` (`args`[`1`] `==` `NULL`) {
`print_message`(`"No process index to stop"`, `ERROR`);
} `else` {
`idx_str` `=` `args`[`1`];
`while` (`*idx_str` `>=` `'0'` `&&` `*idx_str` `<=` `'9'`) {
`proc_idx` `=` (`proc_idx` `*` `10`) `+` ((`*idx_str`) `-` `'0'`);
`idx_str` `+=` `1`;
}
`if` (`*idx_str` `!=` `'\0'` `||` (`size_t`)`proc_idx` `>=` `tasks_structure`.`cursor`) {
`print_message`(`"Incorrect background process index!"`, `ERROR`);
} `else` `if` (`tasks_structure`.`background_task`[`proc_idx`].`is_finished` `==` `0`) {
`kill`(`tasks_structure`.`background_task`[`proc_idx`].`pid`, `SIGTERM`);
}
}
`return` `1`;
}
`/**`
`* @brief `
`* `
`* @param char** args `
`* `
`* @return int (bool) `
`**/`
`int` `is_background_task`(`char**` `args`) {
`int` `last_arg_id` `=` `0`;
`while` (`args`[`last_arg_id` `+` `1`] `!=` `NULL`) {
`last_arg_id++`;
}
`if` (`strcmp`(`args`[`last_arg_id`], `"&"`) `==` `0`) {
`args`[`last_arg_id`] `=` `NULL`;
`return` `1`;
}
`return` `0`;
}
`/**`
`* @brief `
`*`
`* @param args `
`*/`
`void` `fuzzy_search_valid_command`(`char**` `args`) {
`int` `*score` `=` `NULL`;
`size_t` `score_len` `=` `0`;
`int` `max_score` `=` `0`;
`const` `char` `*text` `=` `"N/A"`;
`const` `char**` `apps` `=` `get_sys_binaries`(); `// `
`for` (`size_t` `i` `=` `0`; `i` `<` `sizeof`(`apps`)`/sizeof`(`apps`[`0`]); `i++`) {
`int` `*score_tmp`;
`size_t` `score_len_tmp`;
`int` `total_score` `=` `fuzzy_search`(`apps`[`i`], `args`[`0`], `true`, `&score_tmp`, `&score_len_tmp`);
`if` (`total_score` `>` `max_score`) {
`if` (`score`) `free`(`score`);
`score` `=` `score_tmp`;
`score_len` `=` `score_len_tmp`;
`text` `=` `apps`[`i`];
`max_score` `=` `total_score`;
} `else` {
`free`(`score_tmp`);
}
}
`if` (`max_score` `>` `0`) `// `
`// `
`printf`(`"Did you mean the command %s%s%s?\n"`, `BOLD`, `text`, `RESET`);
}
`/**`
`* @brief `
`*`
`* @param char** args `
`*`
`* @return { description_of_the_return_value }`
`*/`
`int` `launch_task`(`char**` `args`) {
`pid_t` `pid`;
`int` `background` `=` `is_background_task`(`args`);
`pid` `=` `fork`();
`if` (`pid` `<` `0`) {
`print_message`(`"Couldn't create child process!"`, `ERROR`);
} `else` `if` (`pid` `==` `0`) {
`if` (`execvp`(`args`[`0`], `args`) `!=` `0`) { `// `
`print_message`(`"Couldn't execute unknown command!"`, `ERROR`);
`fuzzy_search_valid_command`(`args`); `// `
`global_status_code` `=` `-1`; `// `
}
`exit`(`-1`);
} `else` {
`if` (`background` `==` `1`) {
`if` (`add_background_task`(`pid`, `args`[`0`]) `==` `-1`) {
`quit_from_shell`(`args`);
}
} `else` {
`set_foreground_task`(`pid`);
`if` (`waitpid`(`pid`, `NULL`, `0`) `==` `-1`) {
`if` (`errno` `!=` `EINTR`) {
`print_message`(`"Couldn't track the completion of the process"`, `WARNING`);
`global_status_code` `=` `-1`;
}
}
}
}
`return` `1`;
}
`/**`
`* @brief `
`*/`
`void` `mark_ended_task`(`void`) {
`bg_task*` `bt`;
`pid_t` `pid` `=` `waitpid`(`-1`, `NULL`, `0`);
`if` (`pid` `==` `tasks_structure`.`foreground_task`.`pid`) {
`tasks_structure`.`foreground_task`.`is_finished` `=` `1`;
} `else` {
`for` (`size_t` `i` `=` `0`; `i` `<` `tasks_structure`.`cursor`; `i++`) {
`bt` `=` `&tasks_structure`.`background_task`[`i`];
`if` (`bt->pid` `==` `pid`) {
`printf`(`"Task %zu is finished\n"`, `i`);
`bt->is_finished` `=` `1`;
`break`;
}
}
}
}
`
主要变化是增加了 fuzzy_search_valid_command 函数,以及对 launch_task 函数进行了修改:
/**`
`* @brief `
`*`
`* @param args `
`*/`
`void` `fuzzy_search_valid_command`(`char**` `args`) {
`int` `*score` `=` `NULL`;
`size_t` `score_len` `=` `0`;
`int` `max_score` `=` `0`;
`const` `char` `*text` `=` `"N/A"`;
`const` `char**` `apps` `=` `get_sys_binaries`(); `// `
`for` (`size_t` `i` `=` `0`; `i` `<` `sizeof`(`apps`)`/sizeof`(`apps`[`0`]); `i++`) {
`int` `*score_tmp`;
`size_t` `score_len_tmp`;
`int` `total_score` `=` `fuzzy_search`(`apps`[`i`], `args`[`0`], `true`, `&score_tmp`, `&score_len_tmp`);
`if` (`total_score` `>` `max_score`) {
`if` (`score`) `free`(`score`);
`score` `=` `score_tmp`;
`score_len` `=` `score_len_tmp`;
`text` `=` `apps`[`i`];
`max_score` `=` `total_score`;
} `else` {
`free`(`score_tmp`);
}
}
`if` (`max_score` `>` `0`) `// `
`// `
`printf`(`"Did you mean the command %s%s%s?\n"`, `BOLD`, `text`, `RESET`);
}
`/**`
`* @brief `
`*`
`* @param char** args `
`*`
`* @return { description_of_the_return_value }`
`*/`
`int` `launch_task`(`char**` `args`) {
`pid_t` `pid`;
`int` `background` `=` `is_background_task`(`args`);
`pid` `=` `fork`();
`if` (`pid` `<` `0`) {
`print_message`(`"Couldn't create child process!"`, `ERROR`);
} `else` `if` (`pid` `==` `0`) {
`if` (`execvp`(`args`[`0`], `args`) `!=` `0`) { `// `
`print_message`(`"Couldn't execute unknown command!"`, `ERROR`);
`fuzzy_search_valid_command`(`args`); `// `
`global_status_code` `=` `-1`; `// `
}
`exit`(`-1`);
} `else` {
`if` (`background` `==` `1`) {
`if` (`add_background_task`(`pid`, `args`[`0`]) `==` `-1`) {
`quit_from_shell`(`args`);
}
} `else` {
`set_foreground_task`(`pid`);
`if` (`waitpid`(`pid`, `NULL`, `0`) `==` `-1`) {
`if` (`errno` `!=` `EINTR`) {
`print_message`(`"Couldn't track the completion of the process"`, `WARNING`);
`global_status_code` `=` `-1`;
}
}
}
}
`return` `1`;
}
`
接下来我们来看 executor.c。这个模块会包含内置目录中的许多模块,但别担心,我们稍后就会实现它们。
#include <string.h>`
`#include <unistd.h>`
`#include <stdio.h>`
`#include <linenoise.h>`
`// `
`#include "colors.h"`
`#include "tasks_processing.h"`
`#include "userinput.h"`
`#include "config.h"`
`#include "builtin.h"`
`#include "executor.h"`
`#include "environment.h"`
`#include "isocline.h"`
`extern` `tasks` `tasks_structure`; `// `
`extern` `Alias` `aliases`[`MAX_ALIASES`]; `// `
`extern` `int` `num_aliases`; `// `
`extern` `int` `global_status_code`; `// `
`/**`
`* @brief `
`*`
`* @return int `
`*/`
`int` `help`(`char**` `args`) {
`println`(`"she#gang Linux Shell in C @ by alxvdev\n"`);
`if` (`args`[`1`] `==` `NULL`) {
`printf`(
`"Built-in shell special functions:\n\n"`
`" cd <path> - Change the directory\n"`
`" term <bg_task_idx> - Kill background task by id\n"`
`" help - Prints info about she#gang\n"`
`" bg - Prints list with background tasks\n"`
`" quit/exit - Terminate shell with all active tasks\n"`
`" reload - Reload the shell and config\n"`
`" shegang_config - Configurate the some config values in current session without saving (help exists)\n"`
`" history - Print the shell commands history\n"`
`" sgls - `ls` command analogue, with colors and nerd icons (help exists)\n"`
`" sglsblk - `lsblk` command analogue\n"`
`" environment - Set and get env vars\n"`
`" echon - `echo -n` command analogue\n"`
`" sghint - small util for view built-in command hints\n"`
`" gapf - `cat` command simple analogue\n\n"`
`" Additional arguments: shegang_config, sgls; environment\n"`
);
} `else` `if` (`strcmp`(`args`[`1`], `"shegang_config"`) `==` `0`) {
`println`(`"Built-in function `shegang_config`\n"`);
`printf`(
`"set <VAR> <VALUE> - set value for var (ex. set USERNAME_COLOR RED)\n"`
`"\nExisting variables: USERNAME_COLOR; PWD_COLOR; TIME_COLOR;\n"`
`"Existing colors: RED, GREEN, BLUE, YELLOW, MAGENTA, GRAY, BLACK, WHITE, CYAN\n"`
);
} `else` `if` (`strcmp`(`args`[`1`], `"sgls"`) `==` `0`) {
`println`(`"Built-in function `sgls`\n"`);
`printf`(
`"SGLS - A `ls` command analogue from shegang\n\n"`
`"-t Show time\n"`
`"-a Show hidden\n"`
`"-l Show as list\n"`
`"-p Show permissions\n"`
);
} `else` `if` (`strcmp`(`args`[`1`], `"environment"`) `==` `0`) {
`println`(`"Built-in function `sgls`\n"`);
`printf`(
`"get <VAR> - get value of var (ex. get USER)\n"`
`"set <VAR> <VALUE> - set value for var (ex. set GANG SHELL)\n"`
);
}
`return` `1`;
}
`/**`
`* @brief `
`*`
`* @return int `
`*/`
`int` `quit_from_shell`(`char**` `args`) {
`bg_task*` `bt`;
`signal`(`SIGCHLD`, `SIG_IGN`);
`if` (`!tasks_structure`.`foreground_task`.`is_finished`) {
`kill_foreground_task`();
}
`for` (`size_t` `i` `=` `0`; `i` `<` `tasks_structure`.`cursor`; `i++`) {
`bt` `=` `&tasks_structure`.`background_task`[`i`];
`if` (`bt->is_finished` `==` `0`) {
`kill`(`bt->pid`, `SIGTERM`);
}
`free`(`bt->command`);
}
`return` `0`;
}
`/**`
`* @brief `
`*`
`* @return int `
`*/`
`int` `print_history`(`char**` `args`) {
`if` (`args`[`1`] `!=` `NULL` `&&` `strcmp`(`args`[`1`], `"clear"`) `==` `0`) { `// `
`printf`(`"Clean...\n"`);
`ic_history_clear`(); `// `
`return` `1`;
}
`// `
`FILE*` `file` `=` `fopen`(`get_history_file_path`(), `"r"`);
`if` (`file` `==` `NULL`) {
`perror`(`"fopen"`);
`return` `1`;
}
`char` `line`[`256`];
`int` `lineNumber` `=` `1`;
`// `
`while` (`fgets`(`line`, `sizeof`(`line`), `file`)) {
`printf`(`"%3d: %s"`, `lineNumber`, `line`);
`lineNumber++`;
}
`fclose`(`file`);
`return` `1`;
}
`/**`
`* @brief `
`*/`
`int` `reload_shell`(`char**` `args`) {
`load_main_config`();
`load_config_aliases`();
`return` `1`;
}
`/**`
`* @brief `
`*`
`* @param args `
`*`
`* @return `
`*/`
`int` `execute_alias`(`char` `**args`) {
`for` (`int` `i` `=` `0`; `i` `<` `num_aliases`; `i++`) {
`if` (`strcmp`(`aliases`[`i`].`name`, `args`[`0`]) `==` `0`) {
`char` `cmd`[`strlen`(`aliases`[`i`].`command`)];
`sprintf`(`cmd`, `"%s"`, `aliases`[`i`].`command`);
`printf`(`"alias %s=%s\n\n"`, `aliases`[`i`].`name`, `aliases`[`i`].`command`); `// `
`char**` `command_tokens` `=` `split_into_tokens`(`cmd`);
`return` `execute`(`command_tokens`);
}
}
`return` `0`;
}
`/**`
`* @brief `
`*`
`* @param args The arguments`
`*`
`* @return status code`
`*/`
`int` `environment`(`char**` `args`) {
`if` (`args`[`1`] `==` `NULL` `||` `args`[`2`] `==` `NULL`) {
`global_status_code` `=` `-1`;
`println`(`"Usage: environment <get> <var> OR environment <set> <var> <value>"`);
`return` `1`;
}
`if` (`strcmp`(`args`[`1`], `"get"`) `==` `0`) {
`printf`(`"%s\n"`, `get_environment_variable`(`args`[`2`]));
`return` `1`;
} `else` `if` (`strcmp`(`args`[`1`], `"set"`) `==` `0` `&&` `args`[`2`] `!=` `NULL` `&&` `args`[`3`] `!=` `NULL`) {
`char*` `result` `=` `set_environment_variable`(`args`[`2`], `args`[`3`]);
`printf`(`"ENV: %s\n"`, `result`);
`return` `1`;
}
`println`(`"Usage: environment <get> <var> OR environment <set> <var> <value>"`);
`return` `1`;
}
`/**`
`* @brief `
`*`
`* @param args `
`*`
`* @return `
`*/`
`int` `echo_n`(`char` `**args`) {
`for` (`int` `i` `=` `1`; `args`[`i`] `!=` `NULL`; `i++`) {
`if` (`strncmp`(`args`[`i`], `"$"`, `1`) `==` `0`) {
`char*` `env_var` `=` `get_environment_variable`(`args`[`i`] `+` `1`);
`if` (`env_var` `!=` `NULL`) {
`printf`(`"%s"`, `env_var`);
} `else` {
`printf`(`"Error: environment variable '%s' not found\n"`, `args`[`i`] `+` `1`);
}
} `else` {
`printf`(`"%s"`, `args`[`i`]);
`if` (`args`[`i` `+` `1`] `!=` `NULL`) {
`printf`(`" "`);
}
}
}
`printf`(`"\n"`);
`return` `1`;
}
`/**`
`* @brief `
`*`
`* @param `
`*`
`* @return int `
`*/`
`int` `execute`(`char**` `args`) {
`struct` { `char` `*text`, `*hint`; `int` `arguments_count`; `int` (`*command`)(`char**`); } `commands`[] `=` {
`// `
{.`text="cd"` , .`arguments_count=1`, .`hint="Change directory: cd <dir>"`, .`command=&change_directory`},
{.`text="exit"` , .`arguments_count=0`, .`hint="Quit/exit from shell: exit"`, .`command=&quit_from_shell`},
{.`text="help"` , .`arguments_count=0`, .`hint="Help command. Support args."`, .`command=&help`},
{.`text="bg"` , .`arguments_count=0`, .`hint="Print background tasks"`, .`command=&bg_tasks`},
{.`text="term"` , .`arguments_count=1`, .`hint="Terminate background task"`, .`command=&term_background_task`},
{.`text="history"` , .`arguments_count=0`, .`hint="Shows or clean command history"`, .`command=&print_history`},
{.`text="reload"` , .`arguments_count=0`, .`hint="Reload shell configuration"`, .`command=&reload_shell`},
{.`text="shegang_config"` , .`arguments_count=0`, .`hint="Configurate current session"`, .`command=&shegang_config`},
{.`text="sgls"` , .`arguments_count=0`, .`hint="list: sgls <dir> -p|t|l|a"`, .`command=&sgls`},
{.`text="sglsblk"` , .`arguments_count=0`, .`hint="lsblk command analogue"`, .`command=&sglsblk`},
{.`text="gapf"` , .`arguments_count=1`, .`hint="cat analogue: gapf <file>"`, .`command=&gapf`},
{.`text="environment"` , .`arguments_count=2`, .`hint="Env vars (view help environment)"`, .`command=&environment`},
{.`text="echon"` , .`arguments_count=0`, .`hint="echo with envvars"`, .`command=&echo_n`}
};
`const` `int` `len` `=` `sizeof`(`commands`) `/` `sizeof`(`commands`[`0`]);
`int` `status` `=` `1`;
`// `
`if` (`strcmp`(`args`[`0`], `"sghint"`) `==` `0`) {
`for` (`int` `i` `=` `0`; `i` `<` `len`; `++i`) {
`if` (`args`[`1`] `==` `NULL`) {
`printf`(`"Usage: sghint <built-in command>\n"`);
`return` `1`;
} `else` `if` (`strcmp`(`args`[`1`], `commands`[`i`].`text`) `==` `0`) {
`printf`(`"%ssghint%s: %s\n"`, `GRAY`, `RESET`, `commands`[`i`].`hint`);
}
}
`return` `1`;
}
`// `
`for` (`int` `i` `=` `0`; `i` `<` `len`; `++i`) {
`if` (`strcmp`(`args`[`0`], `commands`[`i`].`text`) `==` `0`) {
`if` (`strcmp`(`commands`[`i`].`text`, `"exit"`) `==` `0`) {
`commands`[`i`].`command`(`args`);
}
`if` (`commands`[`i`].`arguments_count` `>` `0`) {
`for` (`int` `j` `=` `1`; `j` `<` `commands`[`i`].`arguments_count` `+` `1`; `++j`) {
`if` (`args`[`j`] `==` `NULL`) {
`printf`(`"[Argument Count Error at %s:%d] %s\n"`, `commands`[`i`].`text`, `j`, `commands`[`i`].`hint`);
`global_status_code` `=` `-1`;
`return` `1`;
}
}
}
`return` `commands`[`i`].`command`(`args`);
}
}
`// `
`if` (`execute_alias`(`args`) `==` `0`) {
`return` `launch_task`(`args`);
}
`return` `1`;
}
`
可以说,只剩最后一件事要做了。execution/builtin 子目录包含内置实用程序。我们先从以下程序开始base.c:
#include <string.h>`
`#include <unistd.h>`
`#include <stdio.h>`
`#include "colors.h"`
`#include "tasks_processing.h"`
`#include "config.h"`
`extern` `tasks` `tasks_structure`;
`extern` `int` `global_status_code`;
`/**`
`* @brief `
`* `
`* @param char** args `
`* `
`* @return int `
`**/`
`int` `change_directory`(`char**` `args`) {
`if` (`args`[`1`] `==` `NULL`) {
`print_message`(`"Expected argument for \"cd\" command"`, `ERROR`);
} `else` `if` (`chdir`(`args`[`1`]) `!=` `0`) {
`global_status_code` `=` `-1`;
`print_message`(`"Couldn't change directory"`, `ERROR`);
}
`return` `1`;
}
`/**`
`* @brief `
`* `
`* @return int `
`**/`
`int` `bg_tasks`(`char**` `args`) {
`bg_task*` `bt`;
`for` (`size_t` `i` `=` `0`; `i` `<` `tasks_structure`.`cursor`; `i++`) {
`bt` `=` `&tasks_structure`.`background_task`[`i`];
`printf`(
`"[%zu]%s command; %s%s;%s pid: %s%d; %s"`
`"state: %s%s;%s timestamp: %s%s"`, `i`,
`MAGENTA`, `RESET`, `bt->command`,
`MAGENTA`, `RESET`, `bt->pid`,
`MAGENTA`, `RESET`, `bt->is_finished` `?` `"is_finished"` : `"active"`,
`MAGENTA`, `RESET`, `bt->timestamp`
);
}
`return` `1`;
}
`
接下来是 gapf.c。GAPF(获取并打印文件)是我编写的一个小工具,类似于 cat 命令。它会显示文件的内容,包括行号,并在最后总结文件内容:
`File Details:
File Permissions: `-rw-r--r--`(644)
Last modified: Sun Sep `8` `15`:39:14 `2024`
File Type: Markdown
`
以下是代码本身:
`#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <time.h>
#include "colors.h"
/**
* @brief
*
* @param[in] filename
*/
void printFileDetails(const char* filename) {
struct stat fileStat;
if (stat(filename, &fileStat) < 0) {
perror("stat");
return;
}
printf("\nFile Details:\n");
printf("File Permissions: ");
printf((S_ISDIR(fileStat.st_mode)) ? "d" : "-");
printf((fileStat.st_mode & S_IRUSR) ? "r" : "-");
printf((fileStat.st_mode & S_IWUSR) ? "w" : "-");
printf((fileStat.st_mode & S_IXUSR) ? "x" : "-");
printf((fileStat.st_mode & S_IRGRP) ? "r" : "-");
printf((fileStat.st_mode & S_IWGRP) ? "w" : "-");
printf((fileStat.st_mode & S_IXGRP) ? "x" : "-");
printf((fileStat.st_mode & S_IROTH) ? "r" : "-");
printf((fileStat.st_mode & S_IWOTH) ? "w" : "-");
printf((fileStat.st_mode & S_IXOTH) ? "x" : "-");
printf("(%o)\n", fileStat.st_mode & 0777);
printf("Last modified: %s", ctime(&fileStat.st_mtime));
}
/**
* @brief
*
* @param[in] filename The filename
*/
void printFileType(const char* filename) {
const char* extension = strrchr(filename, '.');
if (extension != NULL) {
if (strcmp(extension, ".md") == 0) {
printf("File Type: Markdown\n");
} else if (strcmp(extension, ".c") == 0) {
printf("File Type: C Code\n");
} else if (strcmp(extension, ".cpp") == 0) {
printf("File Type: C++ Code\n");
} else if (strcmp(extension, ".py") == 0) {
printf("File Type: Python Code\n");
} else if (strcmp(extension, ".sh") == 0) {
printf("File Type: Shell code\n");
} else {
printf("File Type: Unknown\n");
}
} else {
printf("File Type: Unknown\n");
}
}
/**
* @brief Get And Print File
*
* @param char** args
*
* @return
*/
int gapf(char** args) {
if (args[1] == NULL) {
printf("Usage: %s <filename>\n", args[0]);
return 1;
}
FILE* file = fopen(args[1], "r");
if (file == NULL) {
perror("fopen");
return 1;
}
char line[256];
int lineNumber = 1;
while (fgets(line, sizeof(line), file)) {
printf("%s%3d: %s%s", BLUE, lineNumber, RESET, line);
lineNumber++;
}
fclose(file);
printFileDetails(args[1]);
printFileType(args[1]);
return 1;
}
`
以下是代码本身(请注意,图标可能不会显示。如果您不需要它们,可以将其删除;这很简单):
#include <stdio.h>`
`#include <dirent.h>`
`#include <sys/stat.h>`
`#include <sys/types.h>`
`#include <unistd.h>`
`#include <stdlib.h>`
`#include <stdbool.h>`
`#include <time.h>`
`#include <string.h>`
`#include "colors.h"`
`#include "shell_api.h"`
`/**`
`* @brief `
`*`
`* @param[in] color `
`* @param[in] text `
`* @param[in] spch `
`*/`
`void` `print_styled`(`const` `char*` `color`, `const` `char*` `text`, `char*` `spch`) {
`if` (`strcmp`(`spch`, `" "`) `==` `1`) { `// if special char is not empty`
`printf`(`"%s%s%s%s\t"`, `spch`, `color`, `text`, `RESET`);
} `else` {
`printf`(`"%s%s%s\t"`, `color`, `text`, `RESET`);
}
}
`/**`
`* @brief `
`*`
`* @param[in] fileStat file stat`
`*/`
`void` `check_permissions`(`struct` `stat` `fileStat`) {
`printf`(`YELLOW`);
`printf`((`S_ISDIR`(`fileStat`.`st_mode`)) `?` `"d"`
: ((`S_ISLNK`(`fileStat`.`st_mode`)) `?` : `"-"`));
`printf`(`GREEN`);
`printf`((`fileStat`.`st_mode` `&` `S_IRUSR`) `?` `"r"` : `"-"`);
`printf`((`fileStat`.`st_mode` `&` `S_IWUSR`) `?` `"w"` : `"-"`);
`printf`((`fileStat`.`st_mode` `&` `S_IXUSR`) `?` `"x"` : `"-"`);
`printf`(`BLUE`);
`printf`((`fileStat`.`st_mode` `&` `S_IRGRP`) `?` `"r"` : `"-"`);
`printf`((`fileStat`.`st_mode` `&` `S_IWGRP`) `?` `"w"` : `"-"`);
`printf`((`fileStat`.`st_mode` `&` `S_IXGRP`) `?` `"x"` : `"-"`);
`printf`(`MAGENTA`);
`printf`((`fileStat`.`st_mode` `&` `S_IROTH`) `?` `"r"` : `"-"`);
`printf`((`fileStat`.`st_mode` `&` `S_IWOTH`) `?` `"w"` : `"-"`);
`printf`((`fileStat`.`st_mode` `&` `S_IXOTH`) `?` `"x"` : `"-"`);
`printf`(`RESET`);
}
`/**`
`* @brief `
`*`
`* @param items `
`* @param[in] count `
`* @param colors `
`*/`
`void` `print_grid`(`char**` `items`, `int` `count`, `char**` `colors`) {
`int` `max_length` `=` `0`;
`int` `length` `=` `0`;
`for` (`int` `i` `=` `0`; `i` `<` `count`; `i++`) {
`length` `=` `strlen`(`items`[`i`]) `+` `strlen`(`colors`[`i`]); `// `
`if` (`length` `>` `max_length`) {
`max_length` `=` `length`;
}
}
`int` `columns` `=` `MAX_GRID_COLUMNS`;
`int` `rows` `=` (`count` `+` `columns` `-` `1`) `/` `columns`;
`for` (`int` `r` `=` `0`; `r` `<` `rows`; `r++`) {
`for` (`int` `c` `=` `0`; `c` `<` `columns`; `c++`) {
`int` `index` `=` `r` `+` `c` `*` `rows`;
`if` (`index` `<` `count`) {
`printf`(`"%s%-*s"`, `colors`[`index`], `max_length`, `items`[`index`]);
}
}
`printf`(`"\n"`);
}
}
`/**`
`* @brief `
`*`
`* @param[in] dir_path `
`* @param[in] show_permissions `
`* @param[in] show_time `
`* @param[in] show_hidden `
`* @param[in] list_show `
`*/`
`void` `list_files`(`const` `char*` `dir_path`, `bool` `show_permissions`, `bool` `show_time`,
`bool` `show_hidden`, `bool` `list_show`) {
`DIR*` `dir`;
`struct` `dirent*` `entry`;
`struct` `stat` `file_stat`;
`char**` `items` `=` (`char**`)`malloc`(`sizeof`(`char*`) `*` `MAX_FILE_NAME_LENGTH`);
`char**` `colors` `=` (`char**`)`malloc`(`sizeof`(`char*`) `*` `MAX_FILE_NAME_LENGTH`);
`int` `count` `=` `0`;
`// Open the directory`
`dir` `=` `opendir`(`dir_path`);
`if` (`dir` `==` `NULL`) {
`print_message`(`"Error opening the directory"`, `ERROR`);
`return`;
}
`// Read the directory entries`
`while` ((`entry` `=` `readdir`(`dir`)) `!=` `NULL`) {
`char` `full_path`[`MAX_FILE_NAME_LENGTH`];
`char` `formattedString`[`MAX_FILE_NAME_LENGTH`];
`char` `color`[`MAX_FILE_NAME_LENGTH`];
`stat`(`full_path`, `&file_stat`);
`snprintf`(`full_path`, `sizeof`(`full_path`), `"%s/%s"`, `dir_path`, `entry->d_name`);
`// Skip hidden files and current/parent directories`
`if` (`entry->d_name`[`0`] `==` `'.'` `&&` `!show_hidden`) {
`continue`;
}
`// Get the file stats`
`if` (`lstat`(`full_path`, `&file_stat`) `<` `0`) {
`print_message`(`"Error get file stats"`, `ERROR`);
`continue`;
}
`if` (`show_permissions`) {
`list_show` `=` `true`;
`check_permissions`(`file_stat`);
`printf`(`"%s(%lo)%s "`, `YELLOW`,
(`unsigned` `long`)`file_stat`.`st_mode` `&` `0777`, `RESET`);
}
`if` (`show_time`) {
`list_show` `=` `true`;
`char` `time_str`[`100`];
`strftime`(`time_str`, `sizeof`(`time_str`), `"%d.%m.%Y %H:%M:%S"`,
`localtime`(`&file_stat`.`st_ctime`));
`printf`(`"%s[%s]%s "`, `CYAN`, `time_str`, `RESET`);
}
`if` (`S_ISDIR`(`file_stat`.`st_mode`)) { `// `
`if` (`!list_show`) {
`sprintf`(`formattedString`, `" %s"`, `entry->d_name`); `// `
`sprintf`(`color`, `"%s"`, `BLUE`); `// `
} `else` {
`print_styled`(`BLUE`, `entry->d_name`, `" "`); `// `
}
} `else` `if` (`S_ISLNK`(`file_stat`.`st_mode`)) { `// `
`if` (`!list_show`) {
`sprintf`(`formattedString`, `" %s"`, `entry->d_name`);
`sprintf`(`color`, `"%s"`, `CYAN`);
} `else` {
`print_styled`(`CYAN`, `entry->d_name`, `" "`);
}
} `else` `if` ((`file_stat`.`st_mode` `&` `S_IXUSR`) `||` `// `
(`file_stat`.`st_mode` `&` `S_IXGRP`)
`||` (`file_stat`.`st_mode` `&` `S_IXOTH`)) {
`if` (`!list_show`) {
`sprintf`(`formattedString`, `" %s"`, `entry->d_name`);
`sprintf`(`color`, `"%s"`, `GREEN`);
} `else` {
`print_styled`(`GREEN`, `entry->d_name`, `" "`);
}
} `else` `if` (`access`(`full_path`, `R_OK`) `==` `0` `&&` `// `
(`strstr`(`entry->d_name`, `".png"`) `!=` `NULL`
`||` `strstr`(`entry->d_name`, `".jpg"`) `!=` `NULL`
`||` `strstr`(`entry->d_name`, `".jpeg"`) `!=` `NULL`
`||` `strstr`(`entry->d_name`, `".svg"`) `!=` `NULL`
`||` `strstr`(`entry->d_name`, `".bmp"`) `!=` `NULL`)) {
`if` (`!list_show`) {
`sprintf`(`formattedString`, `" %s"`, `entry->d_name`);
`sprintf`(`color`, `"%s"`, `MAGENTA`);
} `else` {
`print_styled`(`MAGENTA`, `entry->d_name`, `" "`);
}
} `else` {
`if` (`!list_show`) {
`sprintf`(`formattedString`, `" %s"`, `entry->d_name`);
`sprintf`(`color`, `"%s"`, `WHITE`);
} `else` {
`print_styled`(`BOLD`, `entry->d_name`, `" "`);
}
}
`if` (`!list_show`) {
`items`[`count`] `=` `strdup`(`formattedString`);
`colors`[`count`] `=` `strdup`(`color`);
} `else` {
`printf`(`"\n"`);
}
`count++`;
}
`if` (`!list_show`)
`print_grid`(`items`, `count`, `colors`);
`closedir`(`dir`);
`free`(`items`);
}
`/**`
`* @brief `
`*`
`* @param `
`*`
`* @return `
`*/`
`int` `get_array_size`(`char**` `array`) {
`int` `count` `=` `0`;
`if` (`array` `!=` `NULL`) {
`while` (`array`[`count`] `!=` `NULL`) {
`count++`;
}
}
`return` `count`;
}
`/**`
`* @brief sgls - `
`*`
`* @param args `
`*`
`* @return `
`*/`
`int` `sgls`(`char**` `args`) {
`char` `*dir_path` `=` `"."`;
`bool` `show_permissions` `=` `false`;
`bool` `show_time` `=` `false`;
`bool` `show_hidden` `=` `false`;
`bool` `show_list` `=` `false`;
`for` (`int` `i=0`; `args`[`i`] `!=` `NULL`; `i++`) {
`if` (`strcmp`(`args`[`i`], `"-p"`) `==` `0`) {
`show_permissions` `=` `true`;
} `else` `if` (`strcmp`(`args`[`i`], `"-t"`) `==` `0`) {
`show_time` `=` `true`;
} `else` `if` (`strcmp`(`args`[`i`], `"-a"`) `==` `0`) {
`show_hidden` `=` `true`;
} `else` `if` (`strcmp`(`args`[`i`], `"-l"`) `==` `0`) {
`show_list` `=` `true`;
} `else` `if` (`strcmp`(`args`[`i`], `"-h"`) `==` `0`) {
`println`(`"Built-in function `sgls`\n"`);
`printf`(
`"SGLS - A `ls` command analogue from shegang\n\n"`
`"-t Show time\n"`
`"-a Show hidden\n"`
`"-l Show as list\n"`
`"-p Show permissions\n"`
);
`return` `1`;
} `else` {
`dir_path` `=` `args`[`i`];
}
}
`if` (`args`[`1`] `==` `NULL`) {
`dir_path` `=` `"."`;
}
`list_files`(`dir_path`, `show_permissions`, `show_time`,
`show_hidden`, `show_list`);
`return` `1`;
}
`
现在让我们编写 lsblk 的最简单类似物sglsblk:
#include <stdio.h>`
`#include <stdlib.h>`
`#include <string.h>`
`#include <stdbool.h>`
`#include <sys/stat.h>`
`#include <sys/sysmacros.h>`
`#include <dirent.h>`
`#include <linux/kdev_t.h>`
`#include "colors.h"`
`#include "shell_api.h"`
`/**`
`* @brief `
`*/`
`struct` `Disk` {
`char` `name`[`DEFAULT_BUFFER_SIZE`];
`int` `maj_min`;
`int` `rm`;
`long` `long` `size`;
`bool` `ro`;
`char` `type`[`DEFAULT_BUFFER_SIZE`];
`char` `mountpoint`[`DEFAULT_BUFFER_SIZE`];
};
`/**`
`* @brief `
`*`
`* @param[in] disk `
`*/`
`void` `print_disk`(`struct` `Disk` `disk`) {
`printf`(`"%-6s %-7d %-2d %-6lld %-2d %-6s %-12s\n"`, `disk`.`name`, `disk`.`maj_min`, `disk`.`rm`, `disk`.`size`, `disk`.`ro`, `disk`.`type`, `disk`.`mountpoint`);
}
`/**`
`* @brief sglsblk (lsblk)`
`*`
`* @return `
`*/`
`int` `sglsblk`(`char**` `args`) {
`DIR` `*dir`;
`struct` `dirent` `*entry`;
`struct` `stat` `st`;
`char` `path`[`DEFAULT_BUFFER_SIZE`];
`struct` `Disk` `disk`;
`FILE` `*mounts`;
`printf`(`"%-6s %-7s %-3s %-7s %-3s %-7s %-12s\n"`, `"NAME"`, `"MAJ:MIN"`, `"RM"`, `"SIZE"`, `"RO"`, `"TYPE"`, `"MOUNTPOINTS"`);
`dir` `=` `opendir`(`"/sys/block"`);
`if` (`dir` `==` `NULL`) {
`print_message`(`"Error opening the dir"`, `ERROR`);
`return` `1`;
}
`while` ((`entry` `=` `readdir`(`dir`)) `!=` `NULL`) {
`if` (`entry->d_name`[`0`] `==` `'.'`)
`continue`;
`snprintf`(`path`, `DEFAULT_BUFFER_SIZE`, `"/sys/block/%s/removable"`, `entry->d_name`);
`if` (`stat`(`path`, `&st`) `==` `-1`)
`continue`;
`if` (`S_ISREG`(`st`.`st_mode`) `&&` `st`.`st_size` `>` `0`) {
`disk`.`rm` `=` `1`;
} `else` {
`disk`.`rm` `=` `0`;
}
`snprintf`(`path`, `DEFAULT_BUFFER_SIZE`, `"/sys/block/%s/ro"`, `entry->d_name`);
`if` (`stat`(`path`, `&st`) `==` `-1`)
`continue`;
`if` (`S_ISREG`(`st`.`st_mode`) `&&` `st`.`st_size` `>` `0`) {
`disk`.`ro` `=` `true`;
} `else` {
`disk`.`ro` `=` `false`;
}
`snprintf`(`path`, `DEFAULT_BUFFER_SIZE`, `"/sys/block/%s/size"`, `entry->d_name`);
`if` (`stat`(`path`, `&st`) `==` `-1`)
`continue`;
`disk`.`size` `=` `st`.`st_size` `*` `512ULL`;
`disk`.`maj_min` `=` `makedev`(`st`.`st_rdev` `>>` `8`, `st`.`st_rdev` `&` `0xFF`);
`strcpy`(`disk`.`name`, `entry->d_name`);
`snprintf`(`path`, `DEFAULT_BUFFER_SIZE`, `"/dev/%s"`, `entry->d_name`);
`if` ((`disk`.`maj_min` `!=` `0` `||` `disk`.`size` `!=` `0`) `&&` `stat`(`path`, `&st`) `==` `0` `&&` `S_ISBLK`(`st`.`st_mode`)) {
`strcpy`(`disk`.`type`, `"disk"`);
`strcpy`(`disk`.`mountpoint`, `"-"`);
} `else` {
`strcpy`(`disk`.`type`, `"part"`);
`sprintf`(`path`, `"/sys/block/%s/%s/mountpoint"`, `entry->d_name`, `disk`.`name`);
`mounts` `=` `fopen`(`path`, `"r"`);
`if` (`mounts` `==` `NULL`) {
`print_message`(`"Error get mountpoint"`, `ERROR`);
`strcpy`(`disk`.`mountpoint`, `"-"`);
} `else` {
`fgets`(`disk`.`mountpoint`, `DEFAULT_BUFFER_SIZE`, `mounts`);
`strtok`(`disk`.`mountpoint`, `"\n"`);
`fclose`(`mounts`);
}
}
`print_disk`(`disk`);
}
`closedir`(`dir`);
`return` `1`;
}
`
该实用程序本身提供的输出类似于以下内容:
`NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS
sdb `0` `1` `2097152` `1` disk `-`
sda `0` `1` `2097152` `1` disk `-`
zram0 `0` `1` `2097152` `1` disk `-`
`
最后,最后一个文件是 shegang_config.c:
#include <stdio.h>`
`#include <string.h>`
`#include "colors.h"`
`#include "colorschemes.h"`
`#include "userinput.h"`
`extern` `char*` `username_color`;
`extern` `char*` `pwd_color`;
`extern` `char*` `curr_time_color`;
`/**`
`* @brief `
`*`
`* @param var_name `
`* @param value `
`*`
`* @return int `
`*/`
`int` `set_value_for_var`(`char*` `var_name`, `char*` `value`) {
`char*` `color` `=` `get_color_by_name`(`value`);
`if` (`strcmp`(`var_name`, `"USERNAME_COLOR"`) `==` `0`) {
`username_color` `=` `color`;
} `else` `if` (`strcmp`(`var_name`, `"PWD_COLOR"`) `==` `0`) {
`pwd_color` `=` `color`;
} `else` `if` (`strcmp`(`var_name`, `"TIME_COLOR"`) `==` `0`) {
`curr_time_color` `=` `color`;
} `else` {
`print_message`(`"Expected argument for \"shegang_config\" command: variable name"`, `WARNING`);
}
`return` `1`;
}
`/**`
`* @brief shegang_config`
`*`
`* @param args `
`*`
`* @return int `
`*/`
`int` `shegang_config`(`char**` `args`) {
`if` (`args`[`1`] `==` `NULL`) {
`print_message`(`"Expected argument for \"shegang_config\" command. Launch with help for view help page"`, `WARNING`);
`return` `1`;
} `else` `if` (`strcmp`(`args`[`1`], `"help"`) `==` `0` `||` `strcmp`(`args`[`1`], `"-h"`) `==` `0` `||` `strcmp`(`args`[`1`], `"--help"`) `==` `0`) {
`println`(`"Built-in function `shegang_config`\n"`);
`printf`(
`"set <VAR> <VALUE> - set value for var (ex. set USERNAME_COLOR RED)\n"`
`"\nExisting variables: USERNAME_COLOR; PWD_COLOR; TIME_COLOR\n"`
`"Existing colors: RED, GREEN, BLUE, YELLOW, MAGENTA, GRAY, BLACK, WHITE, CYAN\n"`
`"Special variables: COLORSCHEME (values: onedark, gruvbox)"`
);
} `else` `if` (`strcmp`(`args`[`1`], `"set"`) `==` `0`) {
`// `
`if` (`args`[`3`] `==` `NULL` `||` `args`[`2`] `==` `NULL`) {
`print_message`(`"Expected argument for \"shegang_config\" command: <value>"`, `WARNING`);
`return` `1`;
}
`// `
`if` (`strcmp`(`args`[`2`], `"COLORSCHEME"`) `==` `0`) {
`get_scheme_by_name`(`args`[`3`]);
`setup_isocline`();
`return` `1`;
}
`// `
`char*` `color` `=` `get_color_by_name`(`args`[`3`]);
`return` `set_value_for_var`(`args`[`2`], `color`);
}
`return` `1`;
}
`
好了,就这些。不过,也不全是。我承诺过要展示插件系统的基本、最小化实现。
该模块需要一个头文件plugins_manager.h。根据我们的项目结构,头文件存储在 include 目录中。
那么,在我们的 shell 定义中,插件是什么呢?插件是一段包含特定必要参数的 C 代码(稍后我会举一个插件示例)。要将其包含在 shell 中,它必须编译成 .so 库文件,然后才能链接到该文件。
#ifndef PLUGINS_MANAGER_H`
`#define PLUGINS_MANAGER_H`
`// `
`typedef` `struct` {
`char*` `name`;
`char*` `description`;
`char*` `author`;
`char*` `license`;
} `plugin_metadata_t`;
`// `
`typedef` `struct` {
`int` (`*init`)(`void`);
`int` (`*mainloop`)(`char*` `input`);
`int` (`*deinit`)(`void`);
} `plugin_functions_t`;
`// `
`typedef` `struct` {
`plugin_metadata_t` `metadata`;
`plugin_functions_t` `functions`;
} `plugin_t`;
`// `
`int` `load_plugin`(`const` `char*` `plugin_path`, `plugin_t*` `plugin`);
`int` `unload_plugin`(`plugin_t*` `plugin`);
`int` `execute_plugin`(`plugin_t*` `plugin`, `char*` `input`);
`int` `print_plugin_info`(`plugin_t*` `plugin`);
`#endif // PLUGINS_MANAGER_H`
`
以下是代码本身:
#include "dlfcn.h"`
`#include <stdio.h>`
`#include <stdlib.h>`
`#include <string.h>`
`#include "colors.h"`
`#include "plugins_manager.h"`
`/**`
`* @brief `
`*`
`* @param[in] plugin_path `
`* @param plugin `
`*`
`* @return `
`*/`
`int` `load_plugin`(`const` `char*` `plugin_path`, `plugin_t*` `plugin`) {
`void*` `handle` `=` `dlopen`(`plugin_path`, `RTLD_LAZY`);
`if` (`!handle`) {
`fprintf`(`stderr`, `"%sError occured when loading plugin: %s%s\n"`, `RED`, `dlerror`(), `RESET`);
`return` `-1`;
}
`// `
`plugin->functions`.`init` `=` `dlsym`(`handle`, `"plugin_init"`);
`plugin->functions`.`mainloop` `=` `dlsym`(`handle`, `"plugin_mainloop"`);
`plugin->functions`.`deinit` `=` `dlsym`(`handle`, `"plugin_deinit"`);
`if` (`!plugin->functions`.`init` `||`
`!plugin->functions`.`mainloop` `||`
`!plugin->functions`.`deinit`) {
`fprintf`(`stderr`, `"%sError occured when get plugin functions:%s%s\n"`, `RED`, `dlerror`(), `RESET`);
`dlclose`(`handle`);
`return` `-1`;
}
`void` (`*get_plugin_metadata`)(`plugin_metadata_t*`) `=` `dlsym`(`handle`, `"get_plugin_metadata"`);
`if` (`!get_plugin_metadata`) {
`fprintf`(`stderr`, `"%sError occured when get plugin metadata:%s%s\n"`, `RED`, `dlerror`(), `RESET`);
`dlclose`(`handle`);
`return` `-1`;
}
`// `
`get_plugin_metadata`(`&plugin->metadata`);
`printf`(`"Plugin %s has been loaded\n"`, `plugin->metadata`.`name`);
`return` `1`;
}
`/**`
`* @brief `
`*`
`* @param plugin `
`*`
`* @return `
`*/`
`int` `unload_plugin`(`plugin_t*` `plugin`) {
`printf`(`"Plugin %s has been unloaded\n"`, `plugin->metadata`.`name`);
`return` `0`;
}
`/**`
`* @brief `
`*`
`* @param plugin `
`* @param input `
`*`
`* @return `
`*/`
`int` `execute_plugin`(`plugin_t*` `plugin`, `char*` `input`) {
`return` `plugin->functions`.`mainloop`(`input`);
}
`/**`
`* @brief `
`*`
`* @param plugin `
`*/`
`int` `print_plugin_info`(`plugin_t*` `plugin`) {
`printf`(`"Plugin: %s\n"`, `plugin->metadata`.`name`);
`printf`(`"\tDescription: %s\n"`, `plugin->metadata`.`description`);
`printf`(`"\tAuthor: %s\n"`, `plugin->metadata`.`author`);
`printf`(`"\tLicense: %s\n"`, `plugin->metadata`.`license`);
`return` `1`;
}
`
以下是该插件的一个示例:
/**`
`* Example plugin for Shegang shell`
`* `
`*/`
`#include <stdio.h>`
`#include <stdlib.h>`
`#include <unistd.h>`
`#include <string.h>`
`#include <sys/types.h>`
`#include <sys/wait.h>`
`// Metadata`
`typedef` `struct` {
`char*` `name`;
`char*` `description`;
`char*` `author`;
`char*` `license`;
} `plugin_metadata_t`;
`// Init - before shell mainloop`
`int` `plugin_init`(`void`) {
`printf`(`"Plugin Pipegang has been initialized. !Pipegang plugin is not ready to use!\n"`);
`return` `1`;
}
`/**`
`* @brief Work with user input in shell mainloop`
`*`
`* @param input The input`
`*`
`* @return status code`
`*/`
`int` `plugin_mainloop`(`char*` `input`) {
`printf`(`"INPUT: %s\n\n"`, `input`);
`return` `0`;
}
`// Deinit - after exit from shell`
`int` `plugin_deinit`(`void`) {
`printf`(`"Deinitialize Pipegang plugin\n"`);
`return` `0`;
}
`// Get plugin metadata;`
`void` `get_plugin_metadata`(`plugin_metadata_t*` `metadata`) {
`metadata->name` `=` `"Pipegang"`;
`metadata->description` `=` `"This is example plugin for SheGang"`;
`metadata->author` `=` `"Alexeev Bronislav"`;
`metadata->license` `=` `"MIT License"`;
}
`
结局
暂时就这些。剩下的就是把所有功能合并到一个文件------shegang.c 中:
#include <stdio.h>`
`#include <unistd.h>`
`#include <locale.h>`
`#include "userinput.h"`
`#include "executor.h"`
`#include "tasks_processing.h"`
`#include "plugins_manager.h"`
`#include "config.h"`
`#include "isocline.h"`
`extern` `tasks` `tasks_structure`;
`extern` `char*` `username_color`;
`extern` `char*` `pwd_color`;
`/**`
`* @brief `
`*/`
`void` `print_welcome_message`(`void`) {
`printf`(
`"%s ____ %s %s%sSHE#GANG - powerful command interpreter (shell) for linux written in C%s\n"`
`"%s __/ / /_%s %s%sBlazing fast, cool, simple shell in C%s\n"`
`"%s /_ . __/%s %s%sdeveloped by alxvdev%s\n"`
`"%s/_ __/ %s %s%shttps://github.com/alxvdev/shegang%s\n"`
`"%s /_/_/ %s %sMIT License%s\n\n"`, `GREEN`,
`RESET`, `GREEN`, `BOLD`, `RESET`,
`GREEN`, `RESET`, `GREEN`, `ITALIC`, `RESET`,
`GREEN`, `RESET`, `CYAN`, `DIM`, `RESET`,
`GREEN`, `RESET`, `CYAN`, `UNDERLINE`, `RESET`,
`GREEN`, `RESET`, `DIM`, `RESET`
);
}
`int` `main`(`int` `argc`, `char**` `argv`) {
`setlocale`(`LC_ALL`, `"C.UTF-8"`);
`char*` `line`;
`char**` `args`;
`char**` `args_by_delims`;
`int` `status` `=` `1`;
`int` `returned_value` `=` `0`;
`int` `have_plugin` `=` `0`;
`int` `num_tokens`;
`// `
`load_main_config`();
`load_config_aliases`();
`setup_isocline`();
`print_welcome_message`();
`// `
`plugin_t` `myplugin`;
`// `
`if` (`argv`[`1`] `!=` `NULL`) {
`have_plugin` `=` `1`;
}
`if` (`have_plugin`) {
`if` (`load_plugin`(`argv`[`1`], `&myplugin`) `==` `-1`) {
`fprintf`(`stderr`, `"Plugin loading error\n"`);
`return` `-1`;
}
`myplugin`.`functions`.`init`();
`print_plugin_info`(`&myplugin`);
}
`ic_async_stop`();
`signal`(`SIGINT`, `kill_foreground_task`); `// `
`signal`(`SIGCHLD`, `mark_ended_task`);
`do` {
`display_ps`(`status`);
`line` `=` `read_user_input`();
`if` (`line` `==` `NULL`) {
`continue`;
}
`// `
`if` (`have_plugin`) {
`myplugin`.`functions`.`mainloop`(`line`);
}
`args_by_delims` `=` `split_by_delims`(`line`, `"&&||;"`, `&num_tokens`); `// `
`// `
`for` (`int` `i` `=` `0`; `i` `<` `num_tokens`; `i++`) {
`args` `=` `split_into_tokens`(`args_by_delims`[`i`]);
`status` `=` `execute`(`args`);
}
`free`(`line`);
`free`(`args`);
} `while`(`status` `>=` `1` `||` `status` `<` `0`);
`return` `0`;
}
`
我的主要想法之一是改进插件的配置和加载。插件有点麻烦------它们虽然存在,但功能有限,而且一次只能在 shell 中运行一个插件。
结论
谢谢大家的关注!对我来说,这是一次非常有趣的经历,因为这是我第一个用 C 语言编写的大型项目。