极简shell制作

🌎自定义简单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。


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

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

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

相关文章

WebP格式:图片压缩的新标准

&#x1f90d; 前端开发工程师、技术日更博主、已过CET6 &#x1f368; 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 &#x1f560; 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》 &#x1f35a; 蓝桥云课签约作者、上架课程《Vue.js 和 E…

qcheckbox互斥 也就是单选 纯代码实现 没有ui界面转到槽

1.init&#xff08;&#xff09;函数把所有的qcheckbox找到&#xff0c;然后通过信号与槽&#xff0c;做到点击哪个qcheckbox&#xff0c;哪个qcheckbox就发出信号 2.checkchange&#xff08;&#xff09;槽函数&#xff0c;通过42行拿到是哪个qcheckbox发出的信号&#xff0c…

Kubernetes 弃用Docker后 Kubelet切换到Containerd

containerd 是一个高级容器运行时&#xff0c;又名 容器管理器。简单来说&#xff0c;它是一个守护进程&#xff0c;在单个主机上管理完整的容器生命周期&#xff1a;创建、启动、停止容器、拉取和存储镜像、配置挂载、网络等。 containerd 旨在轻松嵌入到更大的系统中。Docke…

Python 深度学习(三)

原文&#xff1a;zh.annas-archive.org/md5/98cfb0b9095f1cf64732abfaa40d7b3a 译者&#xff1a;飞龙 协议&#xff1a;CC BY-NC-SA 4.0 第八章&#xff1a;深度学习与电脑游戏 上一章关注的是解决棋盘游戏问题。在本章中&#xff0c;我们将研究更复杂的问题&#xff0c;即训练…

深入探究C++四大关键特性:初始化列表、友元函数、内部类与static成员

目录 1. 构造函数不为人知的那些事 1.1 构造函数体赋值与初始化列表对比 1.2 explicit关键字与构造函数隐式转换 2. static成员 2.1 static成员的概念 2.2 static成员的特性与应用 2.3 小结 3. C11 成员变量初始化新用法 4. 友元 4.1 友元函数 4.2 友元类 5. 内部类…

Python 深度学习(一)

原文&#xff1a;zh.annas-archive.org/md5/98cfb0b9095f1cf64732abfaa40d7b3a 译者&#xff1a;飞龙 协议&#xff1a;CC BY-NC-SA 4.0 序言 随着全球对人工智能的兴趣不断增长&#xff0c;深度学习引起了广泛的关注。每天&#xff0c;深度学习算法被广泛应用于不同行业。本书…

QT - 创建Qt Widgets Application项目

在Qt中结合OpenGL使用&#xff0c;可以创建一个Qt Widgets应用程序项目。在创建项目时&#xff0c;您可以选择使用OpenGL模板来生成一个已经集成了OpenGL的项目。这个模板会自动帮助您集成OpenGL和Qt&#xff0c;并生成一个基本的OpenGL窗口。您可以在这个窗口中进行OpenGL的开…

vue快速入门(四十七)路由基本用法

注释很详细&#xff0c;直接上代码 上一篇 新增内容 路由基本用法多级路由方法演示路由样式修改示范路由默认页面写法路由默认样式名修改方法路由高亮的两种匹配方法解析 源码 src/router/index.js //导入所需模块 import Vue from "vue"; import VueRouter from &q…

高级变换与动画基础

1、平移+旋转 1.1 矩阵变换库cuon-matrix.js OpenGL提供了一系列有用的函数来帮助我们创建变换矩阵。例如,通过调用glTranslate()函数并传入在X,Y,Z轴上的平移距离,就可以创建一个平移矩阵。 glTranslatef(5,80,30) ==》 WebGL没有提供类似的矩阵函数,因此,如果想要使用…

【web安全】-- 命令执行漏洞详解

本文将从原理开始介绍命令执行漏洞并附有三个实例来供各位客官学习 文章目录 一、什么是命令执行漏洞二、出现的原因三、有可能存在命令执行漏洞的函数&#xff08;php&#xff09;1、利用一些函数来实现命令执行2、直接执行系统命令的函数 四、命令拼接符号1、Windows2、linux…

亿图图示使用教程

亿图图示是一款强大的图形绘制工具&#xff0c;可以用于创建流程图、思维导图、组织结构图等多种类型的图表。下面是一些基本的使用教程&#xff1a; 下载和安装&#xff1a;首先&#xff0c;你需要在官方网站上下载亿图图示的安装包&#xff0c;然后按照提示进行安装。 新建项…

如何使用Go语言进行并发安全的数据访问?

文章目录 并发安全问题的原因解决方案1. 使用互斥锁&#xff08;Mutex&#xff09;示例代码&#xff1a; 2. 使用原子操作&#xff08;Atomic Operations&#xff09;示例代码&#xff1a; 3. 使用通道&#xff08;Channels&#xff09; 在Go语言中&#xff0c;进行并发编程是常…

亚马逊云科技AWS和微软白送的云计算/IT福利不来领一下?

亚马逊和微软经常举办很多活动&#xff0c;免费给大家送各种礼品&#xff0c;如徽章、水杯、T恤、帽子、充电线、电脑包、手提袋等等&#xff0c;小李哥拿的已经手软&#xff0c;今天就也给大家分享下如何领取这些攻略。1️⃣亚马逊云AWS Community Builder周边 中文名亚马逊云…

一个好用的MQTT客户端软件

软件功能如下&#xff0c;实现的协议版本是 3.1.1 仅实现了常用的 CONNECT , PUBLISH , SUBSCRIBE 及相应的应答报文。支持以 Hex 格式显示接收的原始报文&#xff08;方便初学者学习&#xff09;。支持所有字段的自定义配置。支持保存与加载配置文件。 软件界面如下所示&…

笔记本上打造专属的LLama3聊天机器人

1. 引言 万众期待的 Meta 第三代 Llama 发布了&#xff0c;我想确保你知道如何以最佳方式部署这个最先进的LLM。在本教程中&#xff0c;我们将在笔记本上部署该模型&#xff0c;并指导大家一步步具体操作步骤。 闲话少说&#xff0c;我们直接开始吧&#xff01; 2. LLama3 …

K8s容器部署maven项目

最近在整一整套devops自动化持续集成的东西&#xff0c;一开始就做好了踩坑的准备。 failed to verify certificate: x509: certificate signed by unknown authority 今天在执行kubectl get nodes的时候报的证书验证问题&#xff0c;看了一圈首次搭建k8s的都是高频出现的问题…

《代环问题》

代环问题 什么是代环代环的结构 怎么判断代环还是不代环呢&#xff1f;举一反三1&#xff1a;为什么一定会相遇,有没有可能会错过永远追不上? 请证明2:slow一次走一步&#xff0c;那么fast走3、4、5、6......n步可不可以?N是奇数C是偶数时&#xff0c;那就永远追不上这个条件…

Linux 安装Python3.12.0

下载源文件。 wget https://www.python.org/ftp/python/3.12.0/Python-3.12.0.tgz 解压。 tar -zxvf Python-3.12.0.tgz 进入文件夹。 cd Python-3.12.0 指定安装目录。 ./configure --prefix/usr/local/python3.12/ 1 编译&#xff0c;把源码包里面的代码编译成linux服务器可以…

【JAVASE】带你了解的方法魅力

✅作者简介&#xff1a;大家好&#xff0c;我是橘橙黄又青&#xff0c;一个想要与大家共同进步的男人&#x1f609;&#x1f609; &#x1f34e;个人主页&#xff1a;橘橙黄又青-CSDN博客 目标&#xff1a; 1. 掌握方法的定义以及使用 2. 掌握方法传参 3. 掌握方法重载 …

自学Java要到什么程度才足够能力去实习和就业?

引言 Java&#xff0c;作为当今软件开发领域的主流编程语言之一&#xff0c;对于初学者而言&#xff0c;明确掌握到什么程度才能开始寻找实习和入职机会是至关重要的。这涉及到对Java知识体系的理解深度、技能掌握程度以及实际项目经验的积累。 本文将分别从实习和入职两个不…