【Linux内核系列】:深入解析输出以及输入重定向

🔥 本文专栏:Linux
🌸作者主页:努力努力再努力wz

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

在这里插入图片描述
★★★ 本文前置知识:

文件系统以及文件系统调用接口
用c语言简单实现一个shell外壳程序

内容回顾

那么在此前的学习中,我们对于Linux的文件系统已经有了一个基本的概念,那么我们先来来做一个简单的回顾,那么Linux上的一切事物都可以视作文件,那么我们对于Linux上各种文件可以按照状态将其分为两类分别是打开的文件和未打开的文件,那么对于打开的文件,那么它的元数据会被加载到内存中,我们操作系统会为其定义一个file的结构体来记录其各种属性,那么管理这些文件就是管理这些文件对应的file结构体所组织而成的双链表的数据结构,那么我们要打开一个文件就得通过相应的代码来打开,那么意味着我们打开文件只能通过我们的进程来打开,那么打开文件的对象必定就是进程,而单个进程与文件之间的关系是一对多的,那么必然我们进程对应的task_struct结构体要维护一个指针数组,也就是文件描述表,其中该数组的每一个元素都指向一个打开的文件,而其中会默认打开三个标准输入输出文件,也就是stdin和stdout和stderr,那么分别对应指针数组下标0,1,2这三个位置,那么该数组的下标就是我们的文件描述符,那么我们可以用我们open系统调用接口来打开一个文件,那么open系统接口会为奇创建一个file结构体,并且在指针数组中分配一个位置的指针来指向该file结构体,并且返回该位置的数组下标也就是文件描述符来为后续比如write以及read系统接口来使用,那么这就是我们上一篇文章主要讲述的内容,如果对着部分内容还是感到有点陌生的话,那么可以去看我们的上一篇文章

引入

那么在此前我们学习Linux指令的时候,我们知道会有这样一条指令:

echo "hello Linux" > log.txt
echo "hello Linux" >>log.txt

而我们的echo指令本身是获取之后的字符串“hello Linux",然后将该字符串打印在我们的终端也就是显示器上,但是添加了之后的大于符号后,那么它会将“hello Linux”这个字符串给写入到log.txt文件中,而这就是所谓的输出重定向的现象,而如果后面是两个大于符号,那么则是在之前文件末尾继续写入,那么也就是追加重定向。

< log.txt

而小于符号则是本应该从键盘文件中读取内容,替换为读取小于符号后面的目标文件的文件内容,那么这就是输入重定向,那么输入重定向我们相对比较陌生,而输出以及追加重定向我们则比较熟悉,那么在引入了文件系统以及相关的系统调用接口之后,那么输入以及输出重定向的原理本质上就是对文件进行操作,那么我们就可以根据输入以及输出重定向的原理,结合系统调用接口来自己实现输入以及输出重定向,并且完善我们之前的shell外壳程序

输出以及输入重定向的原理以及实现

那么我们知道进程对应的task_struct结构体内部会有一个指针指向一个指针数组,那么该指针数组的每一个元素则指向该进程已经打开的文件的结构体,那么我们知道像我们的echo和pwd以及ls指令,那么这些指令在执行过后,都会在显示器终端打印信息,那么它们既然能够在显示器打印相应的内容,那么必然他们是要将它们要打印的内容给写入到显示器文件中,而我们知道我们进程会默认打开三个标准输入输出文件,那么其中下标为1的位置就是我们的显示器文件,那么他们必然就是往文件描述符下标为1的文件中做写入,而所谓的输出重定向就是原本我们要将显示器文件中写入的内容给替换写入到另一个目标文件中,所以当我们运行ehco “hello Linux” > log.txt时,我们发现我们终端不会打印“hello Linux" 字符串,而是将该字符串写入到了目标文件log.txt当中,那么这就是我们的输出重定向

那么要做到输出重定向的话,那么这里我们就得引入一个关键的系统调用接口,那么就是dup系统调用接口

  • dup
  • 头文件:<unistd.h>
  • 函数原型:
int dup(int fd)
  • 原理:

那么dup接口会接受一个文件描述符fd,那么该文件描述符fd所指向的file结构体就是要复制的对象,那么接下来dup接口会从前往后线性的扫描整个指针数组,直到找到一个空的位置也就是没有指向任何结构体的指针的位置,然后将该位置的指针复制之前文件描述符fd所指向的结构体,并返回该数组下标,而如果分配复制失败则返回-1,而由于这个接口由于要线性扫描整个指针数组,所以一般不推荐使用dup这个接口

  • dup2
  • 头文件:<unistd.h>
  • 函数原型:
int dup2(int oldfd, int newfd);
  • 原理:

那么dup2会接受两个参数分别是oldfd以及newfd,那么我们的oldfd就会是被复制的对象,而我们的newfd则是被替换的对象,那么dup2首先会先close掉我们的指针数组newfd下标所对应的位置,那么其中就会让其指向的结构体的引用计数减一,如果该结构体引用计数为0,那么操作系统则会回收清理该文件对应的结构体以及在内存中的元数据,那么引用计数不为0,那么则是只是将位置的指针给置空,那么close完指针数组中的newfd位置后,那么下一步就是将oldfd指针所指向的结构体给拷贝到newfd,那么此时newfd和oldfd就指向了同一个file结构体,该接口调用成功的返回值就是newfd,失败则返回-1


所以我们输出重定向就是调用该dup2接口,那么我们假设输出重定向的目标文件是log.txt,那么我们首先就是先调用open接口打开我们的log.txt,然后获得了log.txt的文件描述符,那么接着我们在调用dup2,那么将我们原本要向显示器文件也就是文件描述符为1的位置中写入的内容给写入到log.txt中,所以接下来我们就调用dup2接口,那么oldfd就是目标文件log.txt的文件描述符,而newfd则是显示器文件的文件描述符也就是1,那么这样就是实现了所谓的输出重定向,而我们知道我们open的时候可以指定我们打开该文件要进行的行为或者说模式,也就是通过第二个参数来指定,那么如果是输出重定向,那么我们每次输出都是会清空之前的文本内容,从文本开始处写入,所以我们需要或上O_TRUNC的宏定义,而追加重定向则是从之前的文本末尾处接着写入,所以追加重定向打开目标文件的open的第二个参数不能或上O_TRUNC,而是或上O_APPPEND,那么这就是我们输出以及追加重定向的原理实现

而输入重定向的话则是我们打开的模式要设置为只读打开,第二个参数要或上O_RDONLY,那么我们用open打开我们的目标文件假设为log.txt,然后得到其描述符,那么这里我们接着调用dup2替换的文件也就是键盘文件,那么对应的文件描述符就是0,所以第二个参数newfd就是0,那么这样就实现了我们的输入重定向

完善shell外壳程序

那么之前我们的shell外壳程序只是简单实现了获取用户输入的指令,那么判断指令是内置指令还是外部命令,如果是内置指令的话就交给父进程也就是shell外壳程序来执行,而如果是外部指令的话,那么则交给子进程来执行,那么子进程执行流中就会被进程替换为要执行的指令的进程的上下文,而父进程的执行流则是等待子进程的退出,获取子进程的退出码,那么这就是之前我们shell外壳程序所实现的功能

那么在此基础上,我们shell外壳程序无法进行输出以及输入重定向,那么在本文学习了输出以及输入重定向之后,我们就可以完善我们的shell外壳程序这部分功能

1.获取重定向内容

那么我们知道我们的shell外壳程序的第一个环节就是获取用户输入的指令,本质上就是获取用户输入的字符串然后将其保存在临时字符数组temp中,而用户输入指令时会手动用空格分割隔开指令部分和各个参数部分,所以下一个环节就是解析指令部分与参数部分,利用strtok函数利用空格作为分隔符将其分割的各个参数部分的字符串保存在argv字符指针数组的各个位置中,那么在解析的这一步骤中,我们用户可能有输入或者输出重定向的情况出现,那么在解析完字符串的各个参数部分后,那么我们就得从后往前线性扫描每一个元素所指向的字符串中是否出现重定向的符号也就是"<",">",">>",我们可以定义一个int类型的全局变量check_redir来追踪重定向的情况,如果出现了重定向,那么该变量则不为0,那么如果是输出重定向我们则设置为1,追加重定向设置为2,输入重定向则设置为3,然后再定义一个全局属性字符指针,因为如果出现重定向之后,那么该重定向符号的下一个位置就一定还是目标文件的文件名,所以我们得用指针来保存
实现细节:

细节1:.由于这里我们的重定向的符号以及目标文件名并不是有用的指令部分以及参数部分,所以我们接得保存完该文件名之后,就得将其分别设置为NULL,因为在进程替换的时候,会扫描我们该字符指针数组argv,直到遇到NULL结束,而重定向符号如“>”以及文件名如log.txt不是有命令行参数,所以得设置为空

细节2:这里由于我们字符指针数组argv的最后一个元素是NULL,而我们每一个位置都要调用strcmp函数来匹配,所以这里匹配的开始一定是从字符指针数组的倒数第二个位置开始匹配

细节3:这里由于我们的重定向符号以及之后的文件名不是命令行参数,而我们之前解析命令行参数argc是将其计入了的,所以我们就得重新设置我们的返回的命令行参数的个数,这是一个细节

细节4:我们这里定义了两个全局属性的变量分别追踪重定向的情况以及保存文件名,所以在每一次外部的while循环之后,我们都得将其给重新设置

代码实现:

int getString(char temp[],char* argv[])
{
       int len=strlen(temp);
       if(len>0&&temp[len-1]=='\n')
       {
           temp[len-1]='\0';
           len--;
       }
       int argc=0;
       char* toke=strtok(temp," ");
       while(toke!=NULL&&argc<length-1)
       {
              argv[argc++]=toke;
             toke=strtok(NULL," ");
       }
       argv[argc]=NULL;
        for(int i=argc-1;i>=0;i--)
       {
       	  if(strcmp(argv[i],">")==0)
       	  {
       	  	   check_redir=1;
       	  	   filename=argv[i+1];
       	  	   argc=i;
       	  	   argv[i]=NULL;
       	  	   argv[i+1]=NULL;
       	  	   break;
			 }
			 if(strcmp(argv[i],">>")==0)
			 {
			 	 check_redir=2;
			 	 filename=argv[i+1];
			 	 argc=i;
       	  	   argv[i]=NULL;
       	  	   argv[i+1]=NULL;
			 	 break;
			 }
			 if(strcmp(argv[i],"<")==0)
			 {
			 	check_redir=3;
			 	filename=argv[i+1];
			 	argc=i;
       	  	   argv[i]=NULL;
       	  	   argv[i+1]=NULL;
			 	break;
			 }
	   }
       return argc;
}

2.内置命令的重定向

那么这里我们注意对于内置命令来说,那么由于我们对于一些要往显示器写入的内置命令要做重定向,所以这里我们注意由于内置命令的执行是在父进程中执行的,那么我们关闭比如显示器文件的话就一定会影响父进程,意味着之后我们还得再重新打开,因为下一个命令的执行有可能需要向显示器文件写入,所以这里我个人在实现的时候,我是的思路首先是先open打开目标文件,得到该文件的文件描述符,然后我直接将该指令要向显示器文件中写入的内容用一个数组来保存,然后利用write函数将数组的内容写入该文件,这样就避免了关闭显示器文件,这里由于我自己在实现shell外壳程序的时候,只有cd和pwd两个内置指令,所以这里就只实现了pwd内置命令的输出重定向

代码实现:

void ordercomplete(int argc,char* argv[])
{
       if(strcmp(argv[0],"cd")==0)
       {
                if(argc==2)
                {
                	if(chdir(argv[1])==0)
                	{
                		 char cwd[1024];
                		 getcwd(cwd,sizeof(cwd));
                		 setenv("pwd",cwd,1);
					}
               else{
                perror("chdir");
            }
                }else
                {
                    printf("error: expected argument for 'cd'\n");
                }
       }
       if(strcmp(argv[0],"pwd")==0)
       {
              char cwd[length];  // 定义一个足够大的缓冲区来存储路径

    if (getcwd(cwd, sizeof(cwd)) != NULL) {
    	if(check_redir!=0)
    	{
    		  int fd;
    		  int flag=O_CREAT;
    		  if(check_redir==1)
    		  {
    		  	 flag|=O_WRONLY|O_TRUNC;
			  }
			  if(check_redir==2)
			  {
			  	flag|=O_APPEND|O_WRONLY;
			  }
			  fd=open(filename,flag,0666);
			  if(fd<0)
			  {
			  	perror("open");
			  	return;
			  }
			  int m=write(fd,cwd,strlen(cwd));
			  if(m<0)
			  {
			  	perror("write");
			  	close(fd);
			  	return;
			  }
			  close(fd);
    	}
		else{
        printf("Current working directory: %s\n", cwd);
    }
}
 else {
        perror("getcwd failed");  // 输出错误信息
    }
}

3.外部命令的重定向

而对于外部命令的重定向,那么我们知道外部命令的执行则是创建一个子进程来执行,而我们知道创建一个子进程的本质就是拷贝父进程的task_struct结构体得到子进程的一份task_struct结构体以及父子进程共享同一个物理内存页,而其中拷贝父进程的task_struct结构体意味着就会拷贝一份父进程所对应的文件描述表,所以父子进程各自有一份独立的文件描述表,那么这里我们对于子进程的重定向,那么就无需关心关闭显示器文件所带来的影响,因为子进程对应的文件描述表中的显示器文件也就是下标为1的位置被关闭了,那么它不会影响父进程的文件描述表,所以我们可以在子进程中直接调用我们的dup2系统接口,至于进程替换,由于进程替换只会影响进程的地址空间以及页表的映射,不会影响文件描述符表,所以我们就得在进程替换之前,调用dup2系统接口来完成我们外部命令的重定向,而其中的实现就是我们上文介绍输出以及输入重定向的原理实现

代码实现:

int id=fork();
            if(id==0)
            {
            	if(check_redir!=0)
            	{
            		int fd;
            		int flag=O_CREAT;
            		int exchange;
            		 if(check_redir==1)
            		 {
            		 	   flag|=O_WRONLY|O_TRUNC;
            		 	   exchange=1;
					 }
					 if(check_redir==2)
					 {
					 	flag|=O_WRONLY|O_APPEND;
					 	exchange=1;
					 }
					 if(check_redir==3)
					 {
					 	flag|=O_RDONLY;
					 	exchange=0;
					 }
					 fd=open(filename,flag,0664);
					 if(fd<0)
					 {
					 	perror("open");
					 	exit(1);
					 }
					 int m=dup2(fd,exchange);
					 if(m<0)
					 {
					 	perror("dup2");
					 	close(fd);
					 	exit(2);
					 }
					 close(fd);
				}
                execvp(argv[0],argv);
                perror("execvp");
                exit(EXIT_FAIL);
            }

4.完整实现

完整代码:

#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<fcntl.h>
#include<stdlib.h>
#include<stdbool.h>
#define length 1000
#define EXIT_FAIL 40
const char* order[]={"cd","pwd",NULL};
int check_redir=0;
char* filename;
int getString(char temp[],char* argv[])
{
       int len=strlen(temp);
       if(len>0&&temp[len-1]=='\n')
       {
           temp[len-1]='\0';
           len--;
       }
       int argc=0;
       char* toke=strtok(temp," ");
       while(toke!=NULL&&argc<length-1)
       {
              argv[argc++]=toke;
             toke=strtok(NULL," ");
       }
       argv[argc]=NULL;
        for(int i=argc-1;i>=0;i--)
       {
       	  if(strcmp(argv[i],">")==0)
       	  {
       	  	   check_redir=1;
       	  	   filename=argv[i+1];
       	  	   argc=i;
       	  	   argv[i]=NULL;
       	  	   argv[i+1]=NULL;
       	  	   break;
			 }
			 if(strcmp(argv[i],">>")==0)
			 {
			 	 check_redir=2;
			 	 filename=argv[i+1];
			 	 argc=i;
       	  	   argv[i]=NULL;
       	  	   argv[i+1]=NULL;
			 	 break;
			 }
			 if(strcmp(argv[i],"<")==0)
			 {
			 	check_redir=3;
			 	filename=argv[i+1];
			 	argc=i;
       	  	   argv[i]=NULL;
       	  	   argv[i+1]=NULL;
			 	break;
			 }
	   }
       return argc;
}
bool check(char* argv[])
{
    for(int i=0;order[i]!=NULL;i++)
    {
         if(strcmp(argv[0],order[i])==0)
         {
             return true;
         }
    }
      return false;
}
void ordercomplete(int argc,char* argv[])
{
       if(strcmp(argv[0],"cd")==0)
       {
                if(argc==2)
                {
                	if(chdir(argv[1])==0)
                	{
                		 char cwd[1024];
                		 getcwd(cwd,sizeof(cwd));
                		 setenv("pwd",cwd,1);
					}
               else{
                perror("chdir");
            }
                }else
                {
                    printf("error: expected argument for 'cd'\n");
                }
       }
       if(strcmp(argv[0],"pwd")==0)
       {
              char cwd[length];  // 定义一个足够大的缓冲区来存储路径

    if (getcwd(cwd, sizeof(cwd)) != NULL) {
    	if(check_redir!=0)
    	{
    		  int fd;
    		  int flag=O_CREAT;
    		  if(check_redir==1)
    		  {
    		  	 flag|=O_WRONLY|O_TRUNC;
			  }
			  if(check_redir==2)
			  {
			  	flag|=O_APPEND|O_WRONLY;
			  }
			  fd=open(filename,flag,0666);
			  if(fd<0)
			  {
			  	perror("open");
			  	return;
			  }
			  int m=write(fd,cwd,strlen(cwd));
			  if(m<0)
			  {
			  	perror("write");
			  	close(fd);
			  	return;
			  }
			  close(fd);
    	}
		else{
        printf("Current working directory: %s\n", cwd);
    }
}
 else {
        perror("getcwd failed");  // 输出错误信息
    }
}
       
}
int main()
{
      int argc;
      char* argv[length];
      char temp[length];
      
      while(1)
      {
            printf("[%s@%s %s]$",getenv("USER"),getenv("HOSTNAME"),getenv("PWD"));
            check_redir=0;
            filename=NULL;
            if(fgets(temp,sizeof(temp),stdin)==NULL)
            {
                  perror("fgets");
                  continue;
            }
            argc=getString(temp,argv);
            if(argc==0)
            {
                continue;
            }
            if(check(argv))
            {
                ordercomplete(argc,argv);
                continue;
            }
            int id=fork();
            if(id==0)
            {
            	if(check_redir!=0)
            	{
            		int fd;
            		int flag=O_CREAT;
            		int exchange;
            		 if(check_redir==1)
            		 {
            		 	   flag|=O_WRONLY|O_TRUNC;
            		 	   exchange=1;
					 }
					 if(check_redir==2)
					 {
					 	flag|=O_WRONLY|O_APPEND;
					 	exchange=1;
					 }
					 if(check_redir==3)
					 {
					 	flag|=O_RDONLY;
					 	exchange=0;
					 }
					 fd=open(filename,flag,0664);
					 if(fd<0)
					 {
					 	perror("open");
					 	exit(1);
					 }
					 int m=dup2(fd,exchange);
					 if(m<0)
					 {
					 	perror("dup2");
					 	close(fd);
					 	exit(2);
					 }
					 close(fd);
				}
                execvp(argv[0],argv);
                perror("execvp");
                exit(EXIT_FAIL);
            }else
            {
                int status;
                int m=waitpid(id,&status,0);
                if(m<0)
                {
                    perror("waitpid");
                }else
                {
                     if(WIFEXITED(status))
                     {
                           if(WEXITSTATUS(status)==40)
                           {
                                printf("子进程替换失败\n");
                           }
                            if(WEXITSTATUS(status)==1)
                            {
                            	printf("open调用失败\n");
							}
							 if(WEXITSTATUS(status)==2)
							 {
							 	printf("dup2系统调用失败\n");
							 }
                     }
                }
                
            }
      }
      return 0;
}

Linux上的运行截图:
在这里插入图片描述

结语

那么本篇文章就详细解析了我们Linux下的输入以及输出重定向,那么先介绍了输入以及输出重定向的原理以及实现,然后再完善了我们的shell外壳程序,那么下一篇文章我将会解析缓冲区的内容
我会持续更新,希望你能多多关照,那么如果本文对你有所帮组的话,还请多多三连加关注哦,你的支持就是我创作的最大的动力!
在这里插入图片描述

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

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

相关文章

基于YOLO11深度学习的电瓶车进电梯检测与语音提示系统【python源码+Pyqt5界面+数据集+训练代码】

《------往期经典推荐------》 一、AI应用软件开发实战专栏【链接】 项目名称项目名称1.【人脸识别与管理系统开发】2.【车牌识别与自动收费管理系统开发】3.【手势识别系统开发】4.【人脸面部活体检测系统开发】5.【图片风格快速迁移软件开发】6.【人脸表表情识别系统】7.【…

无人机扩频技术对比!

一、技术原理与核心差异 FHSS&#xff08;跳频扩频&#xff09; 核心原理&#xff1a;通过伪随机序列控制载波频率在多个频点上快速跳变&#xff0c;收发双方需同步跳频序列。信号在某一时刻仅占用窄带频谱&#xff0c;但整体覆盖宽频带。 技术特点&#xff1a; 抗干扰…

项目实战--网页五子棋(对战功能)(9)

上期我们完成了websocket建立连接后的数据初始化&#xff0c;今天我们完成落子交互的具体代码&#xff1a; 这里我们先复习一下&#xff0c;之前约定好的落子请求与响应包含的字段&#xff1a; 1. 发送落子请求 我们在script.js文件中找到落子的相关方法&#xff0c;增加发送请…

elementplus的cascader级联选择器在懒加载且多选时的一些问题分析

1. 背景 在之前做的一个项目中使用到了element的级联选择器&#xff0c;并且是需要懒加载、多选、父子不关联等等&#xff0c;在选的时候当然没问题&#xff0c;但是回显的时候就会回显不出来&#xff0c;相信大部分伙伴都遇到过这个问题。我在以前出过一篇文章写过关于级联选…

基于PySide6的CATIA零件自动化着色工具开发实践

引言 在汽车及航空制造领域&#xff0c;CATIA作为核心的CAD设计软件&#xff0c;其二次开发能力对提升设计效率具有重要意义。本文介绍一种基于Python的CATIA零件着色工具开发方案&#xff0c;通过PySide6实现GUI交互&#xff0c;结合COM接口操作实现零件着色自动化。该方案成…

Uniapp项目运行到微信小程序、H5、APP等多个平台教程

摘要&#xff1a;Uniapp作为一款基于Vue.js的跨平台开发框架&#xff0c;支持“一次开发&#xff0c;多端部署”。本文将手把手教你如何将Uniapp项目运行到微信小程序、H5、APP等多个平台&#xff0c;并解析常见问题。 一、环境准备 在开始前&#xff0c;请确保已安装以下工具…

ROS分布式部署通信

目录 一、概念 二、设置 ROS 分布式网络 1. 环境要求 2. 主机&#xff08;Master&#xff09;设置 3. 从机&#xff08;节点设备&#xff09;设置 4. 测试是否正常通信 三、进阶启动多从机节点&#xff08;launch&#xff09;。 一、概念 ROS 分布式通信用于在多台计算机…

qt open3dAlpha重建

qt open3dAlpha重建 效果展示二、流程三、代码效果展示 二、流程 创建动作,链接到槽函数,并把动作放置菜单栏 参照前文 三、代码 1、槽函数实现 void on_actionAlpha_triggered();//alpha重建 void MainWindow::

我的三维引擎独立开发之路:坚持与迷茫

今天终于解决了&#xff0c;之前开发的基于threeceisum开发的融合引擎Merge3D,引用threejs版本过低的问题&#xff0c;也算又前进了一步&#xff01; 有人说&#xff0c;直接用最新版本不就行了&#xff0c;哎关键之前版本怎么办哪&#xff0c;很多不兼容性&#xff0c;需要一个…

【ArcGIS】地理坐标系

文章目录 一、坐标系理论体系深度解析1.1 地球形态的数学表达演进史1.1.1 地球曲率的认知变化1.1.2 参考椭球体参数对比表 1.2 地理坐标系的三维密码1.2.1 经纬度的本质1.2.2 大地基准面&#xff08;Datum&#xff09;的奥秘 1.3 投影坐标系&#xff1a;平面世界的诞生1.3.1 投…

数据分析人员需要掌握sql到什么程度?

学习SQL三个层次 熟悉基本的增删改查语句及函数&#xff0c;包括select、where、group by、having、order by、delete、insert、join、update等&#xff0c;可以做日常的取数或简单的分析&#xff08;该水平已经超过90%非IT同事&#xff09;;掌握并熟练使用高阶语法&#xff0…

简洁实用的3个免费wordpress主题

高端大气动态炫酷的免费企业官网wordpress主题 非常简洁的免费wordpress主题&#xff0c;安装简单、设置简单&#xff0c;几分钟就可以搭建好一个wordpress网站。 经典风格的免费wordpress主题 免费下载 https://www.fuyefa.com/wordpress

golang从入门到做牛马:第一篇-我与golang的缘分,go语言简介

还记得2018年的夏天,刚毕业的我不知道该做些什么,于是自学了一周的go语言,想要找一份go语言工作的代码,当时的go还没有go mod来管理依赖包,在北京找了一个月的工作,找到了一个小公司做了后端开发,当然使用go语言开发,带着兴奋劲,年轻身体也好,边努力学习,边工作。 时…

【Python编程】高性能Python Web服务部署架构解析

一、FastAPI 与 Uvicorn/Gunicorn 的协同 1. 开发环境&#xff1a;Uvicorn 直接驱动 作用&#xff1a;Uvicorn 作为 ASGI 服务器&#xff0c;原生支持 FastAPI 的异步特性&#xff0c;提供热重载&#xff08;--reload&#xff09;和高效异步请求处理。 启动命令&#xff1a; u…

Sentinel 笔记

Sentinel 笔记 1 介绍 Sentinel 是阿里开源的分布式系统流量防卫组件&#xff0c;专注于 流量控制、熔断降级、系统保护。 官网&#xff1a;https://sentinelguard.io/zh-cn/index.html wiki&#xff1a;https://github.com/alibaba/Sentinel/wiki 对比同类产品&#xff1…

JQuery 语法 $

jQuery 语法是通过选取 HTML 元素, 并对选取的元素执⾏某些操作 JQuery 选择器 jQuery 中所有选择器都以 $ 开头&#xff1a;$(). JQuery事件 事件由三部分组成: 1. 事件源: 哪个元素触发的 2. 事件类型: 是点击, 选中, 还是修改? 3. 事件处理程序: 进⼀步如何处理. …

算法每日一练 (9)

&#x1f4a2;欢迎来到张胤尘的技术站 &#x1f4a5;技术如江河&#xff0c;汇聚众志成。代码似星辰&#xff0c;照亮行征程。开源精神长&#xff0c;传承永不忘。携手共前行&#xff0c;未来更辉煌&#x1f4a5; 文章目录 算法每日一练 (9)最小路径和题目描述解题思路解题代码…

2025/3/8 第 27 场 蓝桥入门赛 题解

1. 38红包【算法赛】 签到题&#xff1a; 算倍数就行了 #include <bits/stdc.h> using namespace std; int main() {int ans0;for(int i1;i<2025;i){if(i % 3 0)ans;else if(i % 8 0)ans;else if(i % 38 0)ans;}cout<<ans<<endl;return 0; } 2. 祝福…

《白帽子讲 Web 安全》之深入同源策略(万字详解)

目录 引言 一、同源策略基础认知 &#xff08;一&#xff09;定义 &#xff08;二&#xff09;作用 &#xff08;三&#xff09;作用机制详解 二、同源策略的分类 &#xff08;一&#xff09;域名同源策略 &#xff08;二&#xff09;协议同源策略 &#xff08;三&…

基于SpringBoot的商城管理系统(源码+部署教程)

运行环境 数据库&#xff1a;MySql 编译器&#xff1a;Intellij IDEA 前端运行环境&#xff1a;node.js v12.13.0 JAVA版本&#xff1a;JDK 1.8 主要功能 基于Springboot的商城管理系统包含管理端和用户端两个部分&#xff0c;主要功能有&#xff1a; 管理端 首页商品列…