【Linux】命令行解释器脚本编写

👀樊梓慕:个人主页

 🎥个人专栏:《C语言》《数据结构》《蓝桥杯试题》《LeetCode刷题笔记》《实训项目》《C++》《Linux》《算法》

🌝每一个不曾起舞的日子,都是对生命的辜负


目录

前言

1.简单了解命令行解释器

2.为什么要手写一个命令行解释器?

3.命令行解释器脚本编写

3.1打印提示符

3.2获取用户输入

3.3解析用户输入(分割字符串)

3.4创建子进程进行程序替换

3.5内建命令的处理

3.5.1什么是内建命令?

3.5.2『 cd』

 3.5.3『 export』

3.5.4『 echo』

3.6重定向

4.完整代码


前言

综合前面所学,我们今天来写一个经典的shell脚本,『 命令行解释器』。


欢迎大家📂收藏📂以便未来做题时可以快速找到思路,巧妙的方法可以事半功倍。

=========================================================================

GITEE相关代码:🌟樊飞 (fanfei_c) - Gitee.com🌟

=========================================================================


1.简单了解命令行解释器

学习linux,我们最先接触到的就是命令行解释器,与windows这种注重用户体验以及简单易操作的操作系统不同,linux并没有设计出像windows一样的美观的图形化界面。

第一次打开linux系统,你看到的只有这一行孤零零的字符串,而这行字符串就是我们与linux系统进行交互的重要工具,它被称为命令行解释器。

我们可以通过不同的指令与linux系统进行交互,比如这样:

它是与计算机进行交互的一种文本界面,相比于图形用户界面,命令行界面更加灵活和高效。 

这样的软件程序我们常常称其为Shell


2.为什么要手写一个命令行解释器?

鉴于之前对于『 进程周边』的学习,包括进程创建,进程终止,进程等待,进程程序替换等,并且有关『 重定向』我们也有了一定的了解。

为了更好的『 理解与掌握』,我们需要搭建一个适合的『 应用场景』用来实践,通过自己手写一个简单的命令行解释器,我们可以『 更好地理解』这些概念。


3.命令行解释器脚本编写

3.1打印提示符

const char* HostName()
{
    char *hostname = getenv("HOSTNAME");
    if(hostname) return hostname;
    else return "None";
}

const char* UserName()
{
    char *hostname = getenv("USER");
    if(hostname) return hostname;
    else return "None";
}

const char *CurrentWorkDir()
{
    char *hostname = getenv("PWD");
    if(hostname) return hostname;
    else return "None";
}

int main()
{
    // 输出提示符并获取用户输入的命令字符串"ls -a -l"
    printf("[%s@%s %s]$ ", UserName(), HostName(), CurrentWorkDir());
}

3.2获取用户输入

因为用户输入中可能会有空格存在,所以获取用户输入我们采用fgets函数。

  • 第一个参数是用于读取文本的字符数组的指针。
  • 第二个参数是最大读取的字符数(包括换行符和空字符)。
  • 第三个参数是要读取的文件流,这里我们传入标准输入stdin即可。
#define SIZE 1024

int main()
{
    char commandline[SIZE];//声明用户输入的字符串
    fgets(commandline, SIZE, stdin);//获取输入
    commandline[strlen(commandline)-1] = 0; //清除最后的\n
    return 0;
}

如果用户直接回车,传入一个\n怎么办?

所以我们可以这样设计:

#define SIZE 1024

int Interactive(char out[], int size)
{
    // 输出提示符并获取用户输入的命令字符串"ls -a -l"
    printf("[%s@%s %s]$ ", UserName(), HostName(), CurrentWorkDir());
    fgets(out, size, stdin);
    out[strlen(out)-1] = 0; 
    return strlen(out);
}

int main()
{
    while(1)//命令行解释器的本质就是一直运行死循环获取指令
    {
        char commandline[SIZE];
        // 1. 打印命令行提示符,获取用户输入的命令字符串
        int n = Interactive(commandline, SIZE);
        if(n == 0) continue;
    }
}

我们将打印提示符与获取输入封装为一个函数,然后检测如果为空串则跳过本次循环。


3.3解析用户输入(分割字符串)

在获取用户输入后,我们要获取指令,然后根据参数执行具体操作。

首先我们对字符串进行分割,需要用到strtok函数:

  • 第一个参数是要进行分割的字符串,
  • 第二个参数为用于指定分割子字符串的分隔符字符串。
  • 返回值为子字符串的指针

strtok的函数原型为char *strtok(char *s, char *delim),功能为“Parse S into tokens separated by characters in DELIM.If S is NULL, the saved pointer in SAVE_PTR is used as the next starting point. ” 翻译成汉语就是:作用于字符串s,以包含在delim中的字符为分界符,将s切分成一个个子串;如果,s为空值NULL,则函数保存的指针SAVE_PTR在下一次调用中将作为起始位置。

根据返回值,所以我们可以这样设计:

#define SEP " "
char *argv[MAX_ARGC];

void Split(char in[])
{
    int i = 0;
    argv[i++] = strtok(in, SEP); // "ls -a -l"
    while(argv[i++] = strtok(NULL, SEP)); // 故意将== 写成 =
    if(strcmp(argv[0], "ls") ==0)//如果是ls指令
    {
        argv[i-1] = (char*)"--color";//加上后,颜色为auto
        argv[i] = NULL;
    }
}

3.4创建子进程进行程序替换

Linux操作系统中,命令行解释器为bash,bash执行命令往往创建一个子进程再进程程序替换为指定的进程去执行,这样做的好处就是确保bash的稳定运行,由于进程的独立性,当出现错误时,只有子进程会出问题,而bash进程不会受到任何影响。

主进程创建子进程,并使用execvp函数进行进程程序替换,最后父进程回收子进程的资源。

为了获取子进程的退出信息,定义一个全局变量lastCode。

int lashcode = 0;
void Execute()
{
    pid_t id = fork();
    if(id == 0)
    {
        // 让子进程执行命令
        execvp(argv[0], argv);
        exit(1);
    }
    int status = 0;
    pid_t rid = waitpid(id, &status, 0);
    if(rid == id) lastcode = WEXITSTATUS(status); 
}

有关进程程序替换函数『 execvp』详解请见:【Linux】进程周边007之进程控制-CSDN博客


3.5内建命令的处理

3.5.1什么是内建命令?

所谓内建命令,就是由 Bash 自身提供的命令,而不是文件系统中的某个可执行文件。

可以使用 type 来确定一个命令是否是内建命令。

通常来说,内建命令会比外部命令执行得更快,执行『 外部命令』时不但会触发磁盘 I/O,还需要 『 fork 』出一个单独的进程来执行,执行完成后再退出

而执行内建命令相当于调用当前 Shell 进程的一个函数。

常见的内建命令有cd、export和echo。

3.5.2『 cd』

我们的shell还没有对内建命令进行单独处理,所以此时cd也被默认为外部命令执行。

让我们来看看现象:

这是为什么呢?

因为cd此时默认为外部命令,所以执行cd会创建子进程,子进程的当前工作路径被修改了,然后子进程子会被父进程回收,但是父进程myshell的工作路径并没有修改,这也就是为什么cd前后当前工作目录没有被修改的原因。

所以如果我们要改变父进程的工作路径,不能创建子进程!在父进程中对工作路径进行修改,用的是chdir函数,但是chdir并不会修改环境变量,如果不修改可能会造成如下图问题:

实际改了,但是提示符处并没有修改。

因为我们打印提示符那块打印当前工作目录用的是环境变量的获取,所以这里我们最好也将环境变量PWD一并修改。

解决方案如下:

void BuildinCmd()
{
    if(strcmp("cd", argv[0]) == 0)
    {
        char *target = argv[1]; //cd XXX or cd
        if(!target) target = Home();
        chdir(target);//修改当前工作目录
        char temp[1024];
        getcwd(temp, 1024);//获取当前工作目录
        snprintf(pwd, SIZE, "PWD=%s", temp);
        putenv(pwd);//修改环境变量
    }
}


 3.5.3『 export』

void BuildinCmd()
{
    if(strcmp("export", argv[0]) == 0)
    {
        if(argv[1])
        {
            strcpy(env, argv[1]);
            putenv(env);
        }
    }
}

3.5.4『 echo』

void BuildinCmd()
{
    if(strcmp("echo", argv[0]) == 0)
    {
        if(argv[1] == NULL) {
            printf("\n");
        }
        else{
            if(argv[1][0] == '$')
            {
                if(argv[1][1] == '?')//打印进程退出码
                {
                    printf("%d\n", lastcode);
                    lastcode = 0;
                }
                else{
                    char *e = getenv(argv[1]+1);//打印环境变量
                    if(e) printf("%s\n", e);
                }
            }
            else{
                printf("%s\n", argv[1]);
            }
        }
    }
}

当检测到是内建命令时,我们执行以上逻辑,不走执行外部命令的逻辑,所以我们将内建命令的处理进行封装,然后定义一个ret返回值,在执行外部命令之前先进行检测是否为内建命令,如果是返回1否则返回0,根据返回值判断是否跳过执行外部命令的函数:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>


#define SIZE 1024
#define MAX_ARGC 64
#define SEP " "
    
char *argv[MAX_ARGC];
char pwd[SIZE];
char env[SIZE]; 
int lastcode = 0;

const char* HostName()
{
    char *hostname = getenv("HOSTNAME");
    if(hostname) return hostname;
    else return "None";
}

const char* UserName()
{
    char *hostname = getenv("USER");
    if(hostname) return hostname;
    else return "None";
}

const char *CurrentWorkDir()
{
    char *hostname = getenv("PWD");
    if(hostname) return hostname;
    else return "None";
}

char *Home()
{
    return getenv("HOME");
}

int Interactive(char out[], int size)
{
    // 输出提示符并获取用户输入的命令字符串"ls -a -l"
    printf("[%s@%s %s]$ ", UserName(), HostName(), CurrentWorkDir());
    fgets(out, size, stdin);
    out[strlen(out)-1] = 0;
    return strlen(out);
}

void Split(char in[])
{
    int i = 0;
    argv[i++] = strtok(in, SEP); // "ls -a -l"
    while(argv[i++] = strtok(NULL, SEP)); // 故意将== 写成 =
    if(strcmp(argv[0], "ls") ==0)//如果是ls命令
    {
        argv[i-1] = (char*)"--color";//加上后,颜色为auto
        argv[i] = NULL;
    }
}

void Execute()
{
    pid_t id = fork();
    if(id == 0)
    {
        // 让子进程执行命名
        execvp(argv[0], argv);
        exit(1);
    }
    int status = 0;
    pid_t rid = waitpid(id, &status, 0);
    if(rid == id) lastcode = WEXITSTATUS(status); 
    //printf("run done, rid: %d\n", rid);
}

int BuildinCmd()
{
    int ret = 0;
    // 1. 检测是否是内建命令, 是 1, 否 0
    if(strcmp("cd", argv[0]) == 0)
    {
        // 2. 执行
        ret = 1;
        char *target = argv[1]; //cd XXX or cd
        if(!target) target = Home();
        chdir(target);//修改当前工作目录
        char temp[1024];
        getcwd(temp, 1024);//获取当前工作目录
        snprintf(pwd, SIZE, "PWD=%s", temp);
        putenv(pwd);//修改环境变量
    }
    else if(strcmp("export", argv[0]) == 0)
    {
        ret = 1;
        if(argv[1])
        {
            strcpy(env, argv[1]);
            putenv(env);
        }
    }
    else if(strcmp("echo", argv[0]) == 0)
    {
        ret = 1;
        if(argv[1] == NULL) {
            printf("\n");
        }
        else{
            if(argv[1][0] == '$')
            {
                if(argv[1][1] == '?')//打印进程退出码
                {
                    printf("%d\n", lastcode);
                    lastcode = 0;
                }
                else{
                    char *e = getenv(argv[1]+1);//打印环境变量
                    if(e) printf("%s\n", e);
                }
            }
            else{
                printf("%s\n", argv[1]);
            }
        }
    }
    return ret;
}
int main()
{
    while(1)
    {
        char commandline[SIZE];
        // 1. 打印命令行提示符,获取用户输入的命令字符串
        int n = Interactive(commandline, SIZE);
        if(n == 0) continue;
        // 2. 对命令行字符串进行切割
        Split(commandline);
        // 3. 处理内建命令
        n = BuildinCmd();
        if(n) continue;
        // 4. 执行这个命令
        Execute();
    }
    return 0;
}

3.6重定向

重定向符号有三种情况:

  • 输出重定向 >
  • 追加重定向 >>
  • 输入重定向 <

一般重定向指令由三部分内容构成:

ls -a > log.txt

左面为指令及参数,中间为重定向符号,最后为文件名。

(1)我们需要获取的是重定向的类型,这里我们可以宏定义为多个整型值,比如:

#define NoneRedir  -1 //无重定向
#define StdinRedir  0 //输入重定向
#define StdoutRedir 1 //输出重定向
#define AppendRedir 2 //追加重定向

然后我们在定义一个全局变量,通过改变改变量的值,获取当前指令是那种类型的重定向:

int redir_type = NoneRedir; //初始化为无重定向

 (2)获取文件名,定义一个全局变量:

char *filename = NULL;

(3)记得重定向符号与文件名之间可能会存在空格,我们可以设计一个宏用来跳过空格:

#define IgnSpace(buf,pos) do{ while(isspace(buf[pos])) pos++; }while(0)

 while(0)的目的是可以在语句中结尾加;,可以消除宏与函数之间的这部分差异,让该宏看起来像个函数。

以上操作我们放在分割字符串之前进行, 然后在执行外部命令的函数中,程序替换之前,判断是否需要重定向,然后利用dup2函数替换对应的输入或输出。

思路如上,开始模拟实现:

#define NoneRedir  -1
#define StdinRedir  0
#define StdoutRedir 1
#define AppendRedir 2

#define STREND '\0'
#define SEP " "

#define IgnSpace(buf,pos) do{ while(isspace(buf[pos])) pos++; }while(0)

int redir_type = NoneRedir;
char *filename = NULL;

void CheckRedir(char in[])
{
    // ls -a -l
    // ls -a -l > log.txt
    // ls -a -l >> log.txt
    // cat < log.txt
    redir_type = NoneRedir; //初始化为无重定向
    filename = NULL;
    int pos = strlen(in) - 1;
    while( pos >= 0 )
    {
        if(in[pos] == '>')
        {
            if(in[pos-1] == '>') //如果是追加重定向
            {
                redir_type = AppendRedir; //设置为追加重定向
                in[pos-1] = STREND; //该位置设置为\0,方便后续切割字符串
                pos++;
                IgnSpace(in, pos); //跳过空格
                filename = in+pos; //获取文件名
                break;
            }
            else //否则为输出重定向
            {
                redir_type = StdoutRedir; //设置为输出重定向
                in[pos++] = STREND; //该位置设置为\0,方便后续切割字符串
                IgnSpace(in, pos); //跳过空格
                filename = in+pos; //获取文件名
                break;
            }
        }
        else if(in[pos] == '<') //如果是输入重定向
        {
            redir_type = StdinRedir; //设置为输入重定向
            in[pos++] = STREND; //该位置设置为\0,方便后续切割字符串
            IgnSpace(in, pos); //跳过空格
            filename = in+pos; //获取文件名
            break;
        }
        else
        {
            pos--;
        }
    }
}

void Split(char in[])
{
    CheckRedir(in);//分割字符串之前进行判断
    int i = 0;
    argv[i++] = strtok(in, SEP); // "ls -a -l"
    while(argv[i++] = strtok(NULL, SEP)); // 故意将== 写成 =
    if(strcmp(argv[0], "ls") ==0)
    {
        argv[i-1] = (char*)"--color";
        argv[i] = NULL;
    }
}

void Execute()
{
    pid_t id = fork();
    if(id == 0)
    {
        //程序替换之前完成重定向处理
        int fd = -1;
        if(redir_type == StdinRedir)
        {
            fd = open(filename, O_RDONLY);
            dup2(fd, 0);
        }
        else if(redir_type == StdoutRedir)
        {
            fd = open(filename, O_CREAT | O_WRONLY | O_TRUNC);
            dup2(fd, 1);
        }
        else if(redir_type == AppendRedir)
        {
            fd = open(filename, O_CREAT | O_WRONLY | O_APPEND);
            dup2(fd, 1);
        }
        else
        {
            // do nothing
        }

        // 让子进程执行命名
        execvp(argv[0], argv);
        exit(1);
    }
    int status = 0;
    pid_t rid = waitpid(id, &status, 0);
    if(rid == id) lastcode = WEXITSTATUS(status); 
}

4.完整代码

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/wait.h>

#define SIZE 1024
#define MAX_ARGC 64
#define SEP " "
#define STREND '\0'
    
char *argv[MAX_ARGC];
char pwd[SIZE];
char env[SIZE]; 
int lastcode = 0;

#define NoneRedir  -1
#define StdinRedir  0
#define StdoutRedir 1
#define AppendRedir 2

#define IgnSpace(buf,pos) do{ while(isspace(buf[pos])) pos++; }while(0)

int redir_type = NoneRedir;
char *filename = NULL;

const char* HostName()
{
    char *hostname = getenv("HOSTNAME");
    if(hostname) return hostname;
    else return "None";
}

const char* UserName()
{
    char *hostname = getenv("USER");
    if(hostname) return hostname;
    else return "None";
}

const char *CurrentWorkDir()
{
    char *hostname = getenv("PWD");
    if(hostname) return hostname;
    else return "None";
}

char *Home()
{
    return getenv("HOME");
}

int Interactive(char out[], int size)
{
    // 输出提示符并获取用户输入的命令字符串"ls -a -l"
    printf("[%s@%s %s]$ ", UserName(), HostName(), CurrentWorkDir());
    fgets(out, size, stdin);
    out[strlen(out)-1] = 0;
    return strlen(out);
}

void CheckRedir(char in[])
{
    // ls -a -l
    // ls -a -l > log.txt
    // ls -a -l >> log.txt
    // cat < log.txt
    redir_type = NoneRedir; //初始化为无重定向
    filename = NULL;
    int pos = strlen(in) - 1;
    while( pos >= 0 )
    {
        if(in[pos] == '>')
        {
            if(in[pos-1] == '>') //如果是追加重定向
            {
                redir_type = AppendRedir; //设置为追加重定向
                in[pos-1] = STREND; //该位置设置为\0,方便后续切割字符串
                pos++;
                IgnSpace(in, pos); //跳过空格
                filename = in+pos; //获取文件名
                break;
            }
            else //否则为输出重定向
            {
                redir_type = StdoutRedir; //设置为输出重定向
                in[pos++] = STREND; //该位置设置为\0,方便后续切割字符串
                IgnSpace(in, pos); //跳过空格
                filename = in+pos; //获取文件名
                break;
            }
        }
        else if(in[pos] == '<') //如果是输入重定向
        {
            redir_type = StdinRedir; //设置为输入重定向
            in[pos++] = STREND; //该位置设置为\0,方便后续切割字符串
            IgnSpace(in, pos); //跳过空格
            filename = in+pos; //获取文件名
            break;
        }
        else
        {
            pos--;
        }
    }
}

void Split(char in[])
{
    CheckRedir(in);//分割字符串之前进行判断
    int i = 0;
    argv[i++] = strtok(in, SEP); // "ls -a -l"
    while(argv[i++] = strtok(NULL, SEP)); // 故意将== 写成 =
    if(strcmp(argv[0], "ls") ==0)
    {
        argv[i-1] = (char*)"--color";
        argv[i] = NULL;
    }
}

int BuildinCmd()
{
    int ret = 0;
    // 1. 检测是否是内建命令, 是 1, 否 0
    if(strcmp("cd", argv[0]) == 0)
    {
        // 2. 执行
        ret = 1;
        char *target = argv[1]; //cd XXX or cd
        if(!target) target = Home();
        chdir(target);//修改当前工作目录
        char temp[1024];
        getcwd(temp, 1024);//获取当前工作目录
        snprintf(pwd, SIZE, "PWD=%s", temp);
        putenv(pwd);//修改环境变量
    }
    else if(strcmp("export", argv[0]) == 0)
    {
        ret = 1;
        if(argv[1])
        {
            strcpy(env, argv[1]);
            putenv(env);
        }
    }
    else if(strcmp("echo", argv[0]) == 0)
    {
        ret = 1;
        if(argv[1] == NULL) {
            printf("\n");
        }
        else{
            if(argv[1][0] == '$')
            {
                if(argv[1][1] == '?')//打印进程退出码
                {
                    printf("%d\n", lastcode);
                    lastcode = 0;
                }
                else{
                    char *e = getenv(argv[1]+1);//打印环境变量
                    if(e) printf("%s\n", e);
                }
            }
            else{
                printf("%s\n", argv[1]);
            }
        }
    }
    return ret;
}

void Execute()
{
    pid_t id = fork();
    if(id == 0)
    {
        //程序替换之前完成重定向处理
        int fd = -1;
        if(redir_type == StdinRedir)
        {
            fd = open(filename, O_RDONLY);
            dup2(fd, 0);
        }
        else if(redir_type == StdoutRedir)
        {
            fd = open(filename, O_CREAT | O_WRONLY | O_TRUNC);
            dup2(fd, 1);
        }
        else if(redir_type == AppendRedir)
        {
            fd = open(filename, O_CREAT | O_WRONLY | O_APPEND);
            dup2(fd, 1);
        }
        else
        {
            // do nothing
        }

        // 让子进程执行命名
        execvp(argv[0], argv);
        exit(1);
    }
    int status = 0;
    pid_t rid = waitpid(id, &status, 0);
    if(rid == id) lastcode = WEXITSTATUS(status); 
}

int main()
{
    while(1)
    {
        char commandline[SIZE];
        // 1. 打印命令行提示符,获取用户输入的命令字符串
        int n = Interactive(commandline, SIZE);
        if(n == 0) continue;
        // 2. 对命令行字符串进行切割
        Split(commandline);
        // 3. 处理内建命令
        n = BuildinCmd();
        if(n) continue;
        // 4. 执行这个命令
        Execute();
    }
    return 0;
}

=========================================================================

如果你对该系列文章有兴趣的话,欢迎持续关注博主动态,博主会持续输出优质内容

🍎博主很需要大家的支持,你的支持是我创作的不竭动力🍎

🌟~ 点赞收藏+关注 ~🌟

=========================================================================

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/377103.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

设计师常用的软件有哪些?推荐5款设计工具

设计软件的使用对设计师来说非常重要。设计工具的使用是否直接影响到最终结果的质量&#xff0c;然后有人会问&#xff1a;设计需要使用什么软件&#xff1f;这里有一些设计师和那些对设计感兴趣的朋友列出了五个有用的设计工具。 1、即时设计 即时设计操作简单&#xff0c;内…

Unity制作随风摇摆的植物

今天记录一下如何实现随风摇摆的植物&#xff0c;之前项目里面的植物摇摆实现是使用骨骼动画实现的&#xff0c;这种方式太消耗性能&#xff0c;植物这种东西没必要&#xff0c;直接使用顶点动画即可。 准备 植物不需要使用标准的PBR流程&#xff0c;基础的颜色贴图加上法向贴…

leetcode 算法 69.x的平方根(python版)

需求 给你一个非负整数 x &#xff0c;计算并返回 x 的 算术平方根 。 由于返回类型是整数&#xff0c;结果只保留 整数部分 &#xff0c;小数部分将被 舍去 。 注意&#xff1a;不允许使用任何内置指数函数和算符&#xff0c;例如 pow(x, 0.5) 或者 x ** 0.5 。 示例 1&#…

跳过mysql5.7密码并重置密码 shell脚本

脚本 目前只是验证了5.7 版本是可以的&#xff0c;8.多的还需要验证 以下是一个简单的Shell脚本&#xff0c;用于跳过MySQL密码设置并重置密码&#xff1a; #!/bin/bash yum install psmisc -y# 停止MySQL服务 sudo service mysqld stop# 跳过密码验证 sudo mysqld --skip-g…

算法学习(一)排序

排序 1. 概念 排序算法是《数据结构与算法》中最基本的算法之一。 排序算法可以分为内部排序和外部排序&#xff0c;内部排序是数据记录在内存中进行排序&#xff0c;而外部排序是因排序的数据很大&#xff0c;一次不能容纳全部的排序记录&#xff0c;在排序过程中需要访问外…

Java学习16-- 面向对象学习45. 面向对象三大特征抽象类和接口

面向对象学习4. 面向对象三大特征 1封装&#xff1a;高内聚(内部细节自己用&#xff0c;外部不能介入)&#xff0c;低耦合(保留很少接口给外部使用)&#xff0c;信息隐藏&#xff08;禁止外界直接访问内部数据(private)&#xff0c;如需要&#xff0c;可通过get/set接口访问&a…

【办公技巧】如何设置Word文档部分内容无法编辑?

工作中&#xff0c;我们可能会在word中制作一些请柬、表格之类的&#xff0c;有些文件内容不想要进行修改&#xff0c;为了防止他人随意修改内容。我们可以设置限制编辑&#xff0c;可以对一部分内容设置限制编辑&#xff0c;具体方法如下&#xff1a; 我们将需要将可以编辑的…

centos7 安装mysql8

下载mysql wget https://cdn.mysql.com//Downloads/MySQL-8.0/mysql-8.0.36-1.el7.x86_64.rpm-bundle.tar解压安装 tar xvf mysql-8.0.36-1.el7.x86_64.rpm-bundle.tar yum -y localinstall *.rpm初始化 mysqld --initialize --usermysql需要选择mysql用户&#xff0c;否则可…

HARRYPOTTER: NAGINI

攻击机 192.168.223.128 目标机 192.168.223.145主机发现 nmap -sP 192.168.223.0/24 端口扫描 nmap -sV -p- -A 192.168.223.145 开启了22 80端口 先看一下web界面 跟系列1一样是一张图片 看一下源码 没东西 看一下robots.txt 也没东西 再扫一下目录吧 gobuster dir…

用通俗易懂的方式讲解:一文搞懂大模型 Prompt Engineering(提示工程)

本文将从提示工程的本质、提示工程的原理、提示工程的应用三个方面&#xff0c;带您一文搞懂提示工程 Prompt Engineering 。 文末精选通俗易懂的方式讲解系列&#xff0c;如果你喜欢技术交流&#xff0c;文末可以加入 文章目录 提示工程的本质提示工程的原理提示工程的应用技…

常见的 MIME(媒体)类型速查

一、简介 MIME(Multipurpose Internet Mail Extensions)多用途互联网邮件扩展类型&#xff0c;是设定某种扩展名的文件用一种应用程序来打开的方式类型&#xff0c;当该扩展名文件被访问的时候&#xff0c;浏览器会自动使用指定应用程序来打开。多用于指定一些客户端自定义的文…

java程序jar包xjar加密及破解解密

超短连接转换j1z.cc&#xff08;永久有效&#xff09; 背景 项目要部署到第三方服务器上&#xff0c;于是研究了一下jar包加密的方式&#xff0c;其中在github上有一个项目XJar&#xff0c;挺多使用用户&#xff0c;也搜到了破解的教程&#xff0c;于是研究了一下。详细说下如…

实战分享:SpringBoot在创新创业项目管理中的应用

✍✍计算机编程指导师 ⭐⭐个人介绍&#xff1a;自己非常喜欢研究技术问题&#xff01;专业做Java、Python、微信小程序、安卓、大数据、爬虫、Golang、大屏等实战项目。 ⛽⛽实战项目&#xff1a;有源码或者技术上的问题欢迎在评论区一起讨论交流&#xff01; ⚡⚡ Java实战 |…

【前端web入门第五天】01 结构伪类选择器与伪元素选择器

文章目录: 1.结构伪类选择器 1.1 nth-child(公式) 2.伪元素选择器 1.结构伪类选择器 作用:根据元素的结构关系查找元素。 选择器说明E:first-child查找第一个E元素E:last-child查找最后一个E元素E:nth-child(N)查找第N个E元素&#xff08;第一个元素N值为1) 一个列表结构…

leetcode 1.两数之和(C++详细题解)DAY1

文章目录 1.题目描述示例提示 2.解答思路&#xff08;1&#xff09;暴力解法&#xff08;2&#xff09;哈希表 3.实现代码结果 4.总结 1.题目描述 给定一个整数数组 nums 和一个整数目标值 target&#xff0c;请你在该数组中找出 和为目标值 target 的那 两个 整数&#xff0c…

位运算01 插入[C++]

图源&#xff1a;文心一言 上机题目练习整理&#xff0c;位运算&#xff0c;供小伙伴们参考~&#x1f95d;&#x1f95d; 网页版目录在页面的右上角↗~&#x1f95d;&#x1f95d; 第1版&#xff1a;在力扣新手村刷题的记录~&#x1f9e9;&#x1f9e9; 编辑&#xff1a;梅…

vue3-内置组件-KeepAlive

KeepAlive <KeepAlive> 是一个内置组件&#xff0c;它的功能是在多个组件间动态切换时缓存被移除的组件实例。 基本使用 默认情况下&#xff0c;一个组件实例在被替换掉后会被销毁。这会导致它丢失其中所有已变化的状态——当这个组件再一次被显示时&#xff0c;会创建…

深入探究 HTTP 简化:httplib 库介绍

✏️心若有所向往&#xff0c;何惧道阻且长 文章目录 简介特性主要类介绍httplib::Server类httplib::Client类httplib::Request类httplib::Response类 示例服务器客户端 总结 简介 在当今的软件开发中&#xff0c;与网络通信相关的任务变得日益普遍。HTTP&#xff08;Hypertext…

Linux | 进度条 | Linux简单小程序 | 超级简单 | 这一篇就够了

进度条—实例示范 在学习了基本的Linux指令&#xff0c;Linux上vim编译器等等之后&#xff0c;我们就来学习写代码喽~ 今天就给大家详细讲解一下进度条的编写&#xff0c;需要的效果如下图&#xff1a; 进度条—必备知识 回车和换行 在我们学习编程语言中&#xff0c;经常…