极简shell制作

news2024/10/7 14:33:00

🌎自定义简单shell制作

(ps: 文末有完整代码)

文章目录:

自定义简单shell制作

    简单配置Linux文件

    自定义Shell编写

      命令行解释器
      获取输入的命令
      字符串分割
      子进程进行进程替换

      内建命令处理

        cd命令处理
        路径显示问题
        export命令处理
        echo 命令处理

    自定义Shell源码


前言:

  通过我们之前所学Linux知识以及C语言的知识,到目前为止,我们完全可以独立完成简易shell的制作,那么话不多说,开始今天的话题!

在这里插入图片描述


🚀简单配置Linux文件

  首先,再开始项目之前,需要先简单配置一下Linux文件,选择一个位置,创建本次项目的目录:

mkdir myshell#名字随意,这里方便区分命名myshell

在这里插入图片描述
  如图所示在该目录下,我们还需要创建 makefile文件C的源文件

touch makefile#或者 Makefile
touch myshell.c#其他名字都行,后缀是.c即可

  因为我们构建的是C语言项目,所以makefile内文件配置也很简单,使用vim(vim介绍及其使用)打开makefile文件:

vim makefile

配置makefile文件

cc=-std=c99
mybin:file.c
		gcc -o $@ $^ -g $(cc)
.PHONY:clean
clean:
		rm -f mybin 

  保存退出之后,就可以开始编写我们C语言代码啦,配置还是很简单的。


🚀自定义Shell编写

✈️命令行解释器

  首先,我们根据常用的shell行为分析:

在这里插入图片描述

  常用 shell 都有叫做 命令行解释器 的东西(上图红框),而命令行解释器其实就是 由不同的字符串所构成 的,可以拆分成三部分:

第一部分是用户,随后在@之后是主机名字符串,第三部分是 当前所处工作目录。

  我们曾经学过一个获取环境变量的接口 getenv

在这里插入图片描述
  因为上述三个部分皆可以在系统的环境变量中找到,所以我们可以使用 getenv 接口,将环境变量导出,拿到字符串作为我们自定义shell的命令行解释器:

#include<stdio.h>
#include<stdlib.h>

char* HostName()//获取主机名
{
    char* hostname = getenv("HOSTNAME");//获取主机名环境变量
    if(hostname) return hostname;
    else return "None";
}

char* UserName()//获取用户名
{
    char* hostname = getenv("USER");//从用户名环境变量的获取用户名
    if(hostname) return hostname;
    else return "None";
}

char* CurrentWorkDir()//获取当前工作目录
{
    char* hostname = getenv("PWD");//获取当前路径
    if(hostname) return hostname;
    else return "None";
}

int main()
{
    //命令行提示符编写
    printf("[%s@%s %s]$ ",UserName(), HostName(), CurrentWorkDir());
    
    return 0;
}

效果展示:

在这里插入图片描述

  效果还是很不错的,有些细节仍需该进,会在后面慢慢改善。


✈️ 获取输入的命令

  有了命令行解释器,我们在 shell 上还有输入命令这一行为,那么我们自定义shell就需要接收输入的命令行字符串。

  那么我们需要考虑的就是输入命令的情况:1、单个命令输入。2、命令+选项输入。 其实他们的区别很明显,一种 字符串不带空格,一种字符串 带一个或多个空格,比如:

在这里插入图片描述
  使用C语言的scanf显然是行不通的,在这里我推荐使用 fgets 接口,可以接收输入的空格:

在这里插入图片描述
在这里插入图片描述

  返回值表示输入的字符串,前两个参数不用说,但是最后一个参数可以能很多人不太理解,我们在学C语言的时候,可能大家学过三个流:stdin、stdout、stderr 流:

在这里插入图片描述
  当然,不了解也不要紧,我们仅需要知道 我们输入需要从 stdin 流中获取即可,表示从标准输入内获取信息。

  函数第一个参数表示 接收字符串的位置,第二个参数表示 接收大小,我们定义一个数组,用来接收输入的命令行参数:

#define CMD_SIZE 1024//定义数组大小
char commandline[CMD_SIZE];//接收命令行参数的数组

  那么我们就需要把接收的命令行参数放入到 commandline数组里。在 Shell中,一行命令输入完成之后将直接生效。所以在命令输入完成之后,我们有必要给commandline数组结尾,也就是添加 ‘\0’:

int main()
{
    char commandline[CMD_SIZE];
    //命令行提示符编写
    printf("[%s@%s %s]$ ",UserName(), HostName(), CurrentWorkDir());
  
    fgets(commandline, CMD_SIZE, stdin);//获取输入命令,将其放在commandline数组内
    commandline[strlen(commandline) - 1] = '\0';//结束本行命令,记得包含string头文件

    printf("cmd line: %s\n", commandline);
    return 0;
}

在这里插入图片描述

  将shell运行起来之后,我们输入的命令就可以被检测并输入到字符数组里面了。

  为了让代码更具可读性,我们可以将输出命令行解释器和输入命令接收操作封装在一个函数内,再在main函数调用:

void Interactive(char out[], int size)//接口封装
{
    printf("[%s@%s %s]$ ",UserName(), HostName(), CurrentWorkDir());
    fgets(out, CMD_SIZE, stdin);
    out[strlen(out) - 1] = '\0';
}

int main()
{
    char commandline[CMD_SIZE];
    Interactive(commandline, CMD_SIZE); //使用接口调用即可

    printf("cmd line: %s\n", commandline);
    return 0;
}

✈️ 字符串分割

  我们平时在shell 中输入的命令选项是不确定的,有时候有多个选项,有时候有一个选项,有时候没有选项,而shell会根据不同的选项来执行不同的动作。

  所以我们有必要将字符串切割,而我们之前在学习命令行参数的时候,提到过main函数参数有一个叫做 argv命令行参数表(const char* argv[]),那么我们就可以创建一个命令行参数表来接收每一个子串。

  那么如何切割字符串呢?这里有一个C语言的接口可供大家使用 strtok

在这里插入图片描述

  第一个参数表示 指向要分割的字符串第一次调用时需要指定这个参数以后的调用要继续分割同一个字符串,就应该把参数 str 设置为 NULL

  第二个参数表示 以什么字符或字符串为结尾进行切割,返回值表示 返回切割后的子串,如果查找不到切割点了,就会返回NULL。

  而我们命令行都是以 空格作为分隔符 的,所以,空格字符就是该接口的第二个参数了,而这个接口会被频繁调用,所以,我们直接使用宏定义空格:

#define MAX_ARGC 64//argv数组的大小
#define SEP " "//表示空格 

   argv是一个指针数组,所以每一个元素都可以指向一段字符串,同时,我们希望argv数组下标能一一对应,所以需要一个键值作为索引:

int i = 0;
argv[i++] =  strtok(commandline, SEP);

  但是,我们输入的命令很可能不止一个空格,所以,我们需要使用循环控制子串的切割,让argv数组的每一个元素都能对应到切割的字符串:

while(argv[i++] = strtok(NULL, SEP));//注意这里用的是=并非==

  并且,这样一个好处就是 在argv数组最后是以 NULL结尾的。同样,为了代码的可读性,我们可以将切割子串的功能封装为一个接口,并且 argv数组放在全局位置,因为根据以往的经验,父子进程可能都会需要argv数组:

void Split(char in[], int size)
{
    int i = 0;
    argv[i++] = strtok(in, SEP);//进行子串切割
    while(argv[i++] = strtok(NULL, SEP));
}

int main()
{
    char commandline[CMD_SIZE];
    Interactive(commandline, CMD_SIZE); 
    
    //对命令行字符串切割
    Split(commandline, CMD_SIZE);

    for(int j = 0; argv[j]; ++j)//测试命令行参数是否切割成功
    {
        printf("argv[%d]:%s\n", j, argv[j]);
    }
    return 0;
}

在这里插入图片描述


✈️ 子进程进行进程替换

  前面我们学习过,程序替换成功时,后续程序就不会往下走,又因为进程之间具有独立性,所以需要创建一个子进程来完成进程替换这件事情。

pid_t id = fork();
if(id == 0)
{
	//子进程,执行程序替换
	exec*();//进程替换函数,待定
	exit(0);
}
pid_t rid = waitpid(id, NULL, 0);//阻塞等待子进程
printf("run done, rid: %d\n", rid);

  如果要执行命令,那就需要进行程序替换,但是程序替换我们介绍了七个接口,使用哪一个接口会比较好呢?根据前面所写的代码,我们已经有了 argv 这张命令行参数表,所以使用接口一定是要带 ‘v’ 的。

  带 ‘v’ 的接口也有三个,execvp 接口是最好的选择,为什么大家可以自己思考一下,很简单:

	execvp(argv[0], argv);//根据命令在环境变量里查找,在根据选项做出对应的动作

  同样为了代码可读性,我们将其封装为一个接口:

void Execute()
{
    pid_t id = fork();
    if(id == 0)
    {
        //child process 
        execvp(argv[0], argv);
        exit(1);
    }
    pid_t rid = waitpid(id, NULL, 0);
    printf("run done, rid: %d\n", rid);
}

在这里插入图片描述

  但是这里我们自定义shell只能运行一次,为了让命令一直能运行下去,我们就得循环执行:

int main()
{
    while(1)
    {
        char commandline[CMD_SIZE];
        Interactive(commandline, CMD_SIZE); 
    
        //对命令行字符串切割
        Split(commandline, CMD_SIZE);

        //执行命令
        Execute();
    }
    return 0;
}

在这里插入图片描述
  这样,我们的shell就初具雏形了!


✈️内建命令处理
🚩 cd命令处理

  我们来看这样一个现象:

在这里插入图片描述
  命名我已经切换目录很多次了,但是为什么目录没有改变呢?其实这是因为我们一直是在使用子进程执行命令的,所以仅仅是子进程一直在切换目录,父进程的目录却一直不变。

  所以向cd 这种命令,我们就不能交给子进程操作,而这样的命令我们称为 内建命令

  为了解决内建命令,我们可以 把cd 命令来单独处理,用一个接口封装。在执行命令之前,检测输入的命令是否是内建命令,如果是,则处理内建命令,如果不是则直接跳过,执行其他命令。

  我们根据封装接口的返回值判断是否为cd 命令,在选择跳过还是处理命令,那么在接口内部的实现。

int BuildinCmd()
{
	int ret = 0;
	if(strcmp("cd", argv[0]) == 0)//通过字符串匹配检测是不是cd 命令
	{
		ret = 1;
		//处理cd 命令
	}
	return ret;
}

  处理cd 命令之前我们得先了解cd 命令有哪些特殊表示,cd 命令无外乎:cd -,cd ~,cd /工作目录或文件/,cd。其中只有cd 是不带空格的,其行为是:

在这里插入图片描述
  如果cd 不带任何选项,那么其行为就是 切换到家目录。知道了这种特殊情况之后就好办了,除了这个不带选型的命令以外,其他的命令全部要根据选项处理,那么就要根据选项切换目录了,我们可以使用 chdir 接口切换目录:

在这里插入图片描述

const char* Home()
{
	return getenv("HOME");//从HOME环境变量获取当前系统的家目录
}

int BuildinCmd()
{
	int ret = 0;
	if(strcmp("cd", argv[0]) == 0)//通过字符串匹配检测是不是cd 命令
	{
		ret = 1;
		//处理cd 命令
		char* target = argv[1];
		if(!target) target = Home();
		chdir(target);
	}
	return ret;
}

int main()
{
    while(1)
    {
        char commandline[CMD_SIZE];
        Interactive(commandline, CMD_SIZE); 
    
        //对命令行字符串切割
        Split(commandline, CMD_SIZE);

        //处理内建命令
        int n = BuildinCmd();
        if(n) continue;

        //执行命令
        Execute();
    }
    return 0;
}

在这里插入图片描述
  这样cd 命令自由的使用了,切换目录也丝毫不费力了。


🚩 路径显示问题

  这里还有一个很明显的错误行为,我的命令行解释器的路径从开始就没有变过,其实是因为我们没有更新PWD环境变量,我们可以手动给当前进程更新环境变量,使用一个数组存储当前目录,再使用 putenv 将环境变量导出:

在这里插入图片描述

char pwd[CMD_SIZE];//定义全局数组

int BuildinCmd()
{
	int ret = 0;
	if(strcmp("cd", argv[0]) == 0)//通过字符串匹配检测是不是cd 命令
	{
		ret = 1;
		//处理cd 命令
		char* target = argv[1];
		if(!target) target = Home();
		chdir(target);

		snprintf(pwd, CMD_SIZE, "PWD=%s", target);//将改变后的路径以 PWD=...的形式写入进pwd数组
		putenv(pwd);//此时数组内容为PWD=...此时putenv就可以更改环境变量了
	}
	return ret;
}

在这里插入图片描述
  刚才的问题解决了…吗??并没有,我们使用cd …或者cd -这种命令的时候路径就显示不出来了,虽然说我们这么写的代码不对,但是我们思路是对的,更新PWD环境变量,那么我们只好使用 Linux 提供的 getcwd 接口了:

在这里插入图片描述

  这个接口可以 获取当前工作目录的绝对路径。

int BuildinCmd()
{
    int ret = 0;
    if(strcmp("cd", argv[0]) == 0)
    {
        ret = 1;
        char* target = argv[1];
        if(!target) target = Home();
        chdir(target);
        
        char tmp[1024];
        getcwd(tmp, 1024);//获取当前工作目录
        
        snprintf(pwd, CMD_SIZE, "PWD=%s", tmp);
        putenv(pwd);
    }

    return ret;
}

在这里插入图片描述

  这样就可以正常切换目录了。


🚩 export命令处理

  当我们在 自定义 Shell 中导入一个新的环境变量时,也是由子进程进行程序替换完成这件事的,所以,当我们使用hell进行env时,是看不到导入的环境变量的:

在这里插入图片描述

  所以,export也是一个内建命令,那么我们就需要在对应的接口里处理export命令:

char env[CMD_SIZE];//全局数组,接收环境变量

int BuildinCmd()
{
    int ret = 0;
    if(strcmp("cd", argv[0]) == 0)
    {//...}
    else if(strcmp("export", argv[0]) == 0)//处理export内建命令
    {
        ret = 1;
        if(argv[1])
        {
            strcpy(env, argv[1]);//将需要导入的环境变量放到数组当中
            putenv(env);//使用接口导入环境变量
        }
    }
	return ret;
}

  此处理方法与cd命令类似,仔细看注释也是很好理解的:

在这里插入图片描述


🚩 echo 命令处理

  我们曾经在shell中演示过 echo的各种用法,其中有 echo $? 表示上一个进程的退出码,除此之外,还有:echoecho $env_nameecho ...,这些特殊情况我们依旧需要处理。

  首先,比较特殊的就是 echo $?这个命令,这个命令需要显示上一个进程的退出码,而获取进程的退出码,这个时候我们就需要先在全局范围内设置退出码变量:

int lastcode = 0;//退出码

  退出码是在执行完进程之后返回的结果,所以必定要在 execute 接口内接收执行命令的退出码(进程退出码相关知识):

int lastcode = 0;

void Execute()
{
    pid_t id = fork();
    if(id == 0)
    {
        //child process 
        execvp(argv[0], argv);
        exit(1);
    }
    int status = 0;
    pid_t rid = waitpid(id, &status, 0);//使用阻塞等待
    if(rid == id) lastcode = WEXITSTATUS(status);//保存退出码
}

  而echo 命令也是一个内建命令。它是在 shell 程序中提供的命令,用于在终端输出文本或环境变量的值。所以我们也需要在内建命令中处理echo命令:

int BuildinCmd()
{
    int ret = 0;
    if(strcmp("cd", argv[0]) == 0)
    {
        ret = 1;
		//...
    }
    else if(strcmp("export", argv[0]) == 0)
    {
        ret = 1;
		//...
    }
    else if(strcmp("echo", argv[0]) == 0)//处理echo命令
    {
        ret = 1;
        if(argv[1] == NULL)//单单echo命令
        {
            printf("\n");//仅仅换行
        }
        else{
            if(argv[1][0] == '$')//argv是一个指针数组,相当于 *(argv[1]),这样获取对你理解有帮助
            {
               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;
}

在这里插入图片描述


🚀自定义Shell源码

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

#define CMD_SIZE 1024
#define MAX_ARGC 64
#define SEP " "

int lastcode = 0;

char* argv[MAX_ARGC];
char pwd[CMD_SIZE];
char env[CMD_SIZE];

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");
}

void Interactive(char out[], int size)
{
    printf("[%s@%s %s]$ ",UserName(), HostName(), CurrentWorkDir());
    fgets(out, CMD_SIZE, stdin);
    out[strlen(out) - 1] = '\0';
}

void Split(char in[], int size)
{
    int i = 0;
    argv[i++] = strtok(in, SEP);//进行子串切割
    while(argv[i++] = strtok(NULL, SEP));
    if(strcmp(argv[0], "ls") == 0)
    {
        argv[i - 1] = "--color";
        argv[i] = NULL;
    }
}

void Execute()
{
    pid_t id = fork();
    if(id == 0)
    {
        //child process 
        execvp(argv[0], argv);
        exit(1);
    }
    int status = 0;
    pid_t rid = waitpid(id, &status, 0);
    if(rid == id) lastcode = WEXITSTATUS(status);//保存退出码
}

int BuildinCmd()
{
    int ret = 0;
    if(strcmp("cd", argv[0]) == 0)
    {
        ret = 1;
        char* target = argv[1];
        if(!target) target = Home();
        chdir(target);
        char tmp[CMD_SIZE];
        getcwd(tmp, CMD_SIZE);
        snprintf(pwd, CMD_SIZE, "PWD=%s", tmp);
        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)//单单echo命令
        {
            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[CMD_SIZE];
        Interactive(commandline, CMD_SIZE); 
    
        //对命令行字符串切割
        Split(commandline, CMD_SIZE);

        //处理内建命令
        int n = BuildinCmd();
        if(n) continue;

        //执行命令
        Execute();
    }
    return 0;
}

  自定义Shell目前就到此为止,当然你可以根据你的喜好去在此基础上拓展更多内容,更加完善这个Shell。


在这里插入图片描述
  如果这篇文章对您有用的话,还望三连支持博主~~

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

商品计划在服装品牌供应链管理中的突出地位

在服装行业中&#xff0c;商品计划是供应链管理的核心&#xff0c;它影响着产品的设计、生产、分销和销售。一个精准的商品计划能够使品牌在竞争激烈的市场中保持领先地位&#xff0c;同时满足消费者的需求和预期。 商品计划的定义与重要性 定义 商品计划是服装品牌基于市场趋…

梯度下降中学习率的调节与优化技巧

在深度学习和机器学习的世界中&#xff0c;梯度下降算法无疑占据了举足轻重的地位。作为优化算法的核心&#xff0c;梯度下降通过迭代的方式&#xff0c;不断调整模型的参数&#xff0c;以最小化损失函数&#xff0c;进而提升模型的预测性能。而在梯度下降的过程中&#xff0c;…

Linux红帽(RHCE)认证学习笔记-(1)Linux 文件管理

Linux 文件管理 1. Linux下的目录结构 / 是Linux里的根目录 Linux的一级目录 /boot &#xff1a;存放的是系统的启动配置⽂件和内核⽂件/dev &#xff1a;存放的是Linux的设备⽂件/etc&#xff1a;存放的是Linux下的配置文件/home&#xff1a;存放普通用户的家目录/media&am…

15天搭建ETF量化交易系统Day1—数据源模块

搭建过程 每个交易者都应该形成一套自己的交易系统。 很多交易者也清楚知道&#xff0c;搭建自己交易系统的重要性。现实中&#xff0c;从&#xff10;到&#xff11;往往是最难跨越的一步。 授人鱼不如授人以渔&#xff0c;为了帮助大家跨出搭建量化系统的第一步&#xff0c;我…

Edge浏览器新特性深度解析,写作ai免费软件

首先&#xff0c;这篇文章是基于笔尖AI写作进行文章创作的&#xff0c;喜欢的宝子&#xff0c;也可以去体验下&#xff0c;解放双手&#xff0c;上班直接摸鱼~ 按照惯例&#xff0c;先介绍下这款笔尖AI写作&#xff0c;宝子也可以直接下滑跳过看正文~ 笔尖Ai写作&#xff1a;…

Adfind的使用

Adfind是一个使用C语言写的活动目录查询工具&#xff0c;它允许用户轻松地搜索各种活动目录信息。它不需要安装&#xff0c;因为它是基于命令行的。它提供了许多选项&#xff0c;可以细化搜索并返回相关细节。下面讲解Adfind的参数以及其使用。 参数 执行如下命令即可查看Adf…

机器学习周记(第三十六周:语义分割)2024.4.22~2024.4.28

目录 摘要 ABSTRACT 1 FCN 2 双线性插值 3 膨胀卷积 3.1 gridding effect 摘要 本周继续学习了语义分割的内容&#xff0c;主要包括 FCN&#xff08;全卷积网络&#xff09;、双线性插值和膨胀卷积等方面。FCN 通过上采样的倍率可以划分为 FCN-32S、FCN-16S、FCN-8S&…

Unity | 优化专项-包体 | 字体

1. 字体包体占用 常用汉字字体文件大小通常在 10M~12M 左右&#xff0c;大概包含常见汉字 3.5w 个。我国汉字有大约将近十万个&#xff0c;全字库的大小对于游戏包体是灾难性的 在小游戏中&#xff0c;即使是常见汉字&#xff0c;大小也足以影响小游戏总包体&#xff0c;进而…

深入探索计算机视觉:高级主题与前沿应用的全面解析

引言 计算机视觉&#xff0c;作为人工智能领域的一个重要分支&#xff0c;旨在让计算机能够“看”懂世界&#xff0c;理解和解释视觉场景。随着深度学习技术的迅猛发展&#xff0c;计算机视觉已经在许多领域取得了显著的进展&#xff0c;如自动驾驶、安防监控、医疗诊断等。在…

vue使用source map调试

一、开发环境 1、开启配置&#xff1a;devtool: ‘eval-source-map’,跟mode配置平级 效果就是控制台报错行数和源码行数完全一致 二、生产环境 1、在生产环境下&#xff0c;一般要关闭source map&#xff0c;如果只想定位报错的具体行数&#xff0c;且不想暴露源码。此时可…

《苍穹外卖》Day11部分知识点记录(数据统计——图像报表)

一、Apache ECharts 介绍 Apache ECharts是一款基于javascript的数据可视化图标库&#xff0c;提供直观、生动、可交互、可个性化定制的数据可视化图表。 官网地址&#xff1a;https://echarts.apache.org/zh/index.html 效果展示 柱形图饼图折线图 入门案例 1. 在 echart…

CAS机制(Compare And Swap)源码解读与三大问题

&#x1f3f7;️个人主页&#xff1a;牵着猫散步的鼠鼠 &#x1f3f7;️系列专栏&#xff1a;Java源码解读-专栏 &#x1f3f7;️个人学习笔记&#xff0c;若有缺误&#xff0c;欢迎评论区指正 目录 1. 前言 2. 原子性问题 3. 乐观锁与悲观锁 4. CAS操作 5. CAS算法带来的…

【算法】组合回溯专题

组合总数 给你一个 无重复元素 的整数数组 candidates 和一个目标整数 target &#xff0c;找出 candidates 中可以使数字和为目标数 target 的 所有 不同组合 &#xff0c;并以列表形式返回。你可以按 任意顺序 返回这些组合。 candidates 中的 同一个 数字可以 无限制重复被…

MySQL-多表查询-练习

练习 1.写一个查询显示所有雇员的 last name、department id、anddepartment name。 SELECT e.LAST_NAME,e.DEPARTMENT_ID,d.DEPARTMENT_NAME FROM employees e,departments d WHERE e.DEPARTMENT_ID d.DEPARTMENT_ID;2.创建一个在部门 80 中的所有工作岗位的唯一列表&#x…

2024长三角快递物流展:科技激荡,行业焕发新活力

7月8日&#xff0c;杭州将迎来快递物流科技盛宴&#xff0c;这是一年一度的行业盛会&#xff0c;吸引了全球领先的快递物流企业和创新技术汇聚一堂。届时&#xff0c;会展中心将全方位展示快递物流及供应链、分拣系统、输送设备、智能搬运、智能仓储、自动识别、无人车、AGV机器…

nginx修改http为https

Linux运维工具-ywtool 目录 一. 获取 SSL 证书1.安装openssl2.自签名证书 二.安装SSL证书三.配置Nginx支持HTTPS四.重启nginx 一. 获取 SSL 证书 SSL/TLS证书是用来验证服务器身份和提供一个安全的连接通道的 获取SSL/TLS证书有几种方法 1.购买域名,购买SSL证书 2.自签名证书…

测试基础 学习测试你必须要知道的基础知识

1.认识测试 在学习测试之前,我们需要明白以下几点 1.什么是测试 2.测试的岗位有哪些 3.测试开发和开发之间的区别 4.优秀的测试人员需要有哪些品质 我们大概说一说 其实生活中处处有测试 我们试衣服 我们在买手机之前先看手机功能符不符合需求 这些都是测试 测试主要就是为了发…

Java | Leetcode Java题解之第46题全排列

题目&#xff1a; 题解&#xff1a; class Solution {public List<List<Integer>> permute(int[] nums) {List<List<Integer>> res new ArrayList<List<Integer>>();List<Integer> output new ArrayList<Integer>();for (i…

保护企业财务报告,这款防泄密软件做得到!

在日益增长的金融欺诈和网络攻击中&#xff0c;保护企业的财务报告是维持公司声誉和稳定运营的关键。财务报告包含了公司的敏感信息&#xff0c;如利润、收入、财务结构等&#xff0c;一旦泄露&#xff0c;可能会对公司造成不利影响。华企盾DSC数据防泄密系统为企业提供了全面的…

第58篇:创建Nios II工程之Hello_World<四>

Q&#xff1a;最后我们在DE2-115开发板上演示运行Hello_World程序。 A&#xff1a;先烧录编译Quartus硬件工程时生成的.sof文件&#xff0c;在FPGA上成功配置Nios II系统&#xff1b;然后在Nios II Eclipse窗口右键点击工程名hello_world&#xff0c;选择Run As-->Nios II …