Linux学习之路 -- 进程篇 -- 自定义shell的编写

2024-04-28 23:36

本文主要是介绍Linux学习之路 -- 进程篇 -- 自定义shell的编写,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

前面介绍了进程程序替换的相关知识,接下来,我将介绍如何基于前面的知识,编写一个简单的shell,另外本文的所展示的shell可能仅供参考。

目录

<1>获取用户的输入和打印命令行提示符

<2>切割字符串

<3>执行这个命令

<4>判断内建命令

<1>cd命令

<2>export

<3>echo命令

<5>全部代码


<1>获取用户的输入和打印命令行提示符

首先我们打开shell时,一般都会看到一个命令行提示符

目前光标卡在当前位置不动,就是在等待用户输入一段命令,这一段命令会被当成字符串。所以我们首先要做的工作就是获取命令行提示符和用户的输入。

在获取命令行提示符前,我们需要回顾一下命令行提示符的组成

虽然用户名、主机名和路径都能通过系统接口进行获取,但是我们也可以通过环境变量来获取这些数据。而我们可以通过getenv接口,就能获得特定环境变量的内容。

下面演示一下输出命令行提示符

#include<unistd.h>
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>const char* HostName()
{char* hostname = getenv("HOSTNAME");if(hostname){return hostname;}else{return "NONE";}
}
const char* UserName()
{char* username = getenv("USER");if(username){return username;}else{return "NONE";}
}
const char* Currentdir()
{char* dirname = getenv("PWD");if(dirname){return dirname;}else{return "NONE";}
}
int main(int argc, char* argv[],char* env[])
{printf("[%s@%s -- %s]$",UserName(),HostName(),Currentdir());return 0;
}

 这里需要注意的是,HOSTNAME在一些操作系统中可能没有,这可能是很多原因导致的。所以我们可以在获取HOSTNAME这个环境变量之前手动添加。

运行效果

接下来再解决一下用户输入问题

我们可以用一个字符数组先储存命令行参数。至于输入,我们可以使用fgetc,不能用scanf函数,scanf在读到空串时,会自动停止读取。所以我们使用fgetc从缓冲区里面读取(当然也可以使用其他的接口,这里我以fgets为例)。下面介绍一下fgetc接口

第一个参数表示,存放缓冲区数据的数组,第二个表示数组大小,第三个表示输入流指针(这个暂不做介绍,涉及文件系统内容,这里直接写stdin即可)。

下面演示一下代码

#include<unistd.h>
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<string.h>
#define SIZE 1024const char* HostName()
{char* hostname = getenv("HOSTNAME");if(hostname){return hostname;}else{return "NONE";}
}
const char* UserName()
{char* username = getenv("USER");if(username){return username;}else{return "NONE";}
}
const char* Currentdir()
{char* dirname = getenv("PWD");if(dirname){return dirname;}else{return "NONE";}
}
int main(int argc, char* argv[],char* env[])
{char command[SIZE];printf("[%s@%s -- %s]$",UserName(),HostName(),Currentdir());fgets(command,SIZE,stdin);printf("command line: %s\n",command);return 0;
}

运行结果

这里我们会发现,中间printf打印完一条语句后,多了一行空白,这行空白是其实是因为我们在输入时也会敲回车键,所以实际在执行printf语句时,会有两个\n,这就会造成中间多了一行空白。这里我们只需要将command数组里面存储的最后一个字符由‘\n’变成‘\0’或0即可。所以只需在fgets语句后添加下面一条语句即可。

    command[strlen(command) - 1] = 0;

无需担心command长度为零的情况,因为无论如何你都要输入一个‘\n’。所以command的长度至少也是1。但是我们需要对空串进行一下判断,如果是空串,后面的代码就不需要执行了(这条下面封装时会用到)。

调整后的结果

这里我们可以对命令行提示符的显示和用户输入功能封装一下。

<2>切割字符串

如果我们要执行一个命令,就必需调用程序替换的接口,而在之前的介绍里面,程序替换接口的参数都是没有空格的字符串,而且都是一个一个分开的。所以我们必需要将字符串切割成一个一个子串,然后传递给这些接口。所以第一步就是先分割子串,那我们该如何切割呢?下面介绍一个函数strtok

str参数表示要切割的串,delim表示以什么为分割符。需要注意的是,第一次调用该函数时,str参数传需要切割的字符串指针,第二次传NULL,delim不变,就是” “(空格)。

下面演示一下代码

#include<unistd.h>
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<string.h>#define SIZE 1024
#define argc 128
#define SYM  " "
char* argv[argc];const char* HostName()
{char* hostname = getenv("HOSTNAME");if(hostname){return hostname;}else{return "NONE";}
}
const char* UserName()
{char* username = getenv("USER");if(username){return username;}else{return "NONE";}
}
const char* Currentdir()
{char* dirname = getenv("PWD");if(dirname){return dirname;}else{return "NONE";}
}
int interactive(char* command)
{printf("[%s@%s -- %s]$",UserName(),HostName(),Currentdir());fgets(command,SIZE,stdin);command[strlen(command) - 1] = 0;return strlen(command);
}
void Split(char* command)
{int i = 0;argv[i++] = strtok(command,SYM);//argv为全局变量,用于存放字符变量while(argv[i++] = strtok(NULL, SYM));//SYM为空格,这里是个宏
}
int main()
{char command[SIZE];//1.获取用户指令并打出命令行提示符int ret = interactive(command);//如果是空串,则下面的代码就不必执行了if(!ret){}else{//2.切割命令行Split(command);for(int i = 0; argv[i];i++){printf("argv[%d]:%s\n",i,argv[i]);}}return 0;
}

Split函数中的while循环条件变成argv[i++] = strtok(NULL, SYM),可以直接把strtok切割的子串放进argv里面,而且在切完后,strtok会返回NULL,我们之前了解过,命令行参数列表的结尾就是NULL。同时我们argv[ i ]设为空后,条件判断也就不成立了,此时也就跳出循环并 i++。

运行结果

从结果上来看,上述的代码逻辑并没有什么问题。

<3>执行这个命令

在切割完命令行后,我们就需要依照argv来执行命令。在这里我们依照程序替换的方式来进行执行命令。不过在这之前,我们需要创建一个子进程。这是因为我们的shell需要关注用户和机器之间的交互,如果我们直接让shell执行命令,那交互性能就会变差。万一程序崩溃了,shell也会无法运行。所以这里我们就需要让子进程替我们执行命令。

在创建完子进程后,我们就可以让子进程执行对应的任务。执行任务的过程其实并不难,就是选择一个合适的程序替换接口即可。这里我们选择execvp这个接口是最好的,因为该接口的参数是最少的,并且中间不用做处理。在执行完后,我们需要等待回收子进程,这里我使用的是阻塞等待。

下面是演示的代码

#include<unistd.h>
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<string.h>#define SIZE 1024
#define argc 128
#define SYM  " "
char* argv[argc];const char* HostName()
{char* hostname = getenv("HOSTNAME");if(hostname){return hostname;}else{return "NONE";}
}
const char* UserName()
{char* username = getenv("USER");if(username){return username;}else{return "NONE";}
}
const char* Currentdir()
{char* dirname = getenv("PWD");if(dirname){return dirname;}else{return "NONE";}
}
int interactive(char* command)
{printf("[%s@%s -- %s]$",UserName(),HostName(),Currentdir());fgets(command,SIZE,stdin);command[strlen(command) - 1] = 0;return strlen(command);
}
void Split(char* command)
{int i = 0;argv[i++] = strtok(command,SYM);//argv为全局变量,用于存放字符变量while(argv[i++] = strtok(NULL, SYM));//SYM为空格,这里是个宏
}void Execute()
{pid_t id = fork();if(id == 0){execvp(argv[0],argv);}pid_t rid = waitpid(id,NULL,0);// if(rid > 0)// {//     printf("wait success, pid: %d\n",rid);// }
}
int main()
{while(1)//让shell持续运行{char command[SIZE];//1.获取用户指令并打出命令行提示符int ret = interactive(command);if(!ret){continue;}//2.切割命令行Split(command);//3.执行命令Execute();}// for(int i = 0; argv[i];i++)// {//     printf("argv[%d]:%s\n",i,argv[i]);// }return 0;
}

运行结果:

此时,我们运行普通的命令已经没有什么问题了,但是一旦我们运行类似于cd .. 命令时,就会出现无法执行的情况。其实这是因为决定当前路径的父进程,这里我们使用子进程执行cd 命令,但是父进程的路径并没有改变,所以当我们执行cd命令后,再执行pwd命令,会发现路径并没有改变。像这样的情况还有很多,这些命令本就不应该交由子进程执行,而是让父进程直接执行。而这些命令叫做内建命令。我们在执行命令前要加一个步骤,那就是判断内建命令,并让父进程去执行这个命令。

<4>判断内建命令

<1>cd命令

由于一些命令是需要父进程自己执行的,所以我们就需要修改执行顺序。首先我们先以cd 命令为例,修改一下原来的shell。在此之前我们需要先了解一下chdir接口,这个接口是修改当前工作路径的。

path就是修改后的路径。使用这个命令的原因是因为cd 命令后可能是没有东西的,这会直接进入该用户的家目录。所以我们必需要通过这个接口来实现工作路径的切换。

在编写判断内建命令的函数时,一共分为两步,一是判断是否为内建命令,二是执行内建命令。如果是内建命令,我们需要执行并且在退出后跳过子进程执行的步骤。如果不是内建命令,那就直接退出函数,继续让子进程执行该命令。

下面演示一下代码(这里只截取部分代码,其实较上面的代码,就只是增加了Built-in-com()函数)

int Built_in_com()//用返回值判断是否为内建命令,如果是返回 1, 不是返回 0.
{int ret = 0;if(strcmp(argv[0],"cd") == 0){ret = 1;char* home = argv[1];if(!home) home = Home(); chdir(home);}return ret;
}
int main()
{while(1)//让shell持续运行{char command[SIZE];//1.获取用户指令并打出命令行提示符int ret = interactive(command);if(!ret){continue;}//2.切割命令行Split(command);//3.处理内建命令ret = Built_in_com();if(ret){continue;}//4.执行命令Execute();}// for(int i = 0; argv[i];i++)// {//     printf("argv[%d]:%s\n",i,argv[i]);// }return 0;
}

执行结果

我们可以发现,虽然命令行提示符的路径没有改变,但是pwd命令和cd命令确实是成功执行了。这里的命令行提示符是从环境变量中获取当前的工作路径的,命令行提示符路径没有改变,说明环境变量没有跟着chdir的改变而改变,所以我们在内建命令改变路径时,我们要手动对环境变量进行更新,以确保环境变量是正确的。

要修改环境变量,我们就不得不提到putenv这个接口了,这个接口常用于添加和修改环境变量。

这里的string参数就是要修改的环境变量参数,具体的参数形式:“USER=root”(例)。不过要获得修改后的环境变量参数,又要使用别的字符串函数。

要获得修改后的环境变量参数,就得先获取环境变量形式的字符串,而要获得这个字符串,我们可以通过很多种方式,可以使用strcat,strcpy等等,这里我使用snprintf。

printf就是把特定格式的内容写到显示器上,而sprintf就是把特定格式的内容写到一个str字符串里面,而snprintf就是把指定长度的内容写到一个str字符串里面。

我们可以定义一个全局变量数组pwd,里面存放修改后的环境变量字符串。通过snprintf我们就可以把对应的字符串写入pwd中,再putenv(pwd)即可。

下面演示一下代码(这里只对Built_in_com这个函数进行修改,其他的一律不做修改)

int Built_in_com()//用返回值判断是否为内建命令,如果是返回 1, 不是返回 0.
{int ret = 0;if(strcmp(argv[0],"cd") == 0){ret = 1;char* home = argv[1];if(!home) home = Home(); chdir(home);snprintf(pwd,SIZE,"PWD=%s",home);putenv(pwd);}return ret;
}

运行结果

除了cd .. 外,其他的命令都还正常,说明之前的代码总体逻辑是没有啥问题的,只不过cd .. 这个需要处理一下。这里出现 .. 路径是因为没有获得 “ .. ”代表的绝对路径。所以我们就需要通过特定的接口获得..路径的绝对路径 。我们可以通过getcwd函数获取“..”的绝对路径。

getcwd函数就是获取当前的工作路径,具体的参数含义如下:

  1. buf:这是一个指向字符数组的指针,用于存储获取到的当前工作目录的路径。getcwd函数会将路径字符串写入到这个数组中。

  2. size:这个参数指定了buf数组的大小,也就是它能够存储的字符数量。这个大小应该至少能够容纳当前工作目录的路径加上一个终止的空字符('\0')。                                           

既然可以通过此时,我们就可以直接把buf里面内容写到pwd中,再由pwd写入到环境变量表中。

下面演示一下代码

int Built_in_com()//用返回值判断是否为内建命令,如果是返回 1, 不是返回 0.
{int ret = 0;if(strcmp(argv[0],"cd") == 0){ret = 1;char* home = argv[1];if(!home) home = Home(); chdir(home);char word[512];getcwd(word,512);snprintf(pwd,SIZE,"PWD=%s",word);putenv(pwd);}return ret;
}

 运行结果

其他的内建命令

内建命令当然不止只有cd,还有很多,下面列举一些供大家参考

  • cd - 改变当前工作目录
  • echo - 显示消息或变量的值
  • exit - 退出当前shell
  • export - 设置环境变量
  • history - 显示或操作命令历史
  • kill - 发送信号到特定进程
  • pwd - 显示当前工作目录的路径
  • set - 设置或显示shell特性或位置参数
  • source - 在当前shell执行脚本
  • unset - 删除变量或函数
  • wait - 等待后台进程结束

下面在原来shell基础上,再添加几个内建命令

<2>export

export命令导入环境变量也是内建命令,所以我们需要添加进Built_in_com 这个函数中。这个命令会相对简单一点,我们可以直接写代码

下面演示一下代码

int Built_in_com()//用返回值判断是否为内建命令,如果是返回 1, 不是返回 0.
{int ret = 0;if(strcmp(argv[0],"cd") == 0){ret = 1;char* home = argv[1];if(!home) home = Home(); chdir(home);char word[512];getcwd(word,512);snprintf(pwd,SIZE,"PWD=%s",word);//注意这里数字的大小不能越界putenv(pwd);}else if(strcmp(argv[0],"export") == 0) // {ret = 1;if(argv[1]) putenv(argv[1]);}return ret;
}

运行结果

这个结果看似是非常正确的,但其实我们只要运行几次其他命令,再次查看环境变量。我们就会发现,我们新增的环境变量消失了。所以上述的代码其实是不完善的。这里的argv[ 1 ]是一个指针,指向command里面的一段内容,而我们每次输入新的命令,command就会被覆盖,而argv[ 1 ]指向的内容也会随之改变。 所以我们需要通过一个数组来存储特定环境变量,令其固定不变。这里为了方便演示,这里只用一个一维字符数组来存储一个新增的环境变量。这里严格意义上来说,是要通过一个环境变量表来存储环境变量的。

下面演示一下代码

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

由于运行结果过长,这里不变展示,读者可以自行测试。

<3>echo命令

echo命令也是一个内建命令,通常用于打印一些变量值,常见的就是“echo XXX”,向显示器打印XXX;”echo $环境变量名“,向显示器打印环境变量;“echo $?” 打印退出码。除此之外,echo还可以结合重定向进行操作,不过这里不做演示,这部分内容涉及文件系统的内容。这里主要演示上面所述的三个与echo有关的命令。

1.直接echo

echo后面不接内容,就是直接换行。

2.echo $? 

该命令会显示上一个进程的退出码,这个退出码最好就用全局变量来保存。

3.echo $环境变量

该命令会显示对应环境变量的内容,不过需要判断这个环境变量是否存在。

4.echo XXX

该命令会直接打印XXX到显示器上

下面演示一下代码(只展示修改被修改部分的代码)

void Execute()
{pid_t id = fork();if(id == 0){execvp(argv[0],argv);}int status = 0;pid_t rid = waitpid(id,&status,0);if(WIFEXITED(status)){lastcode = WEXITSTATUS(status);}// if(rid > 0)// {//     printf("wait success, pid: %d\n",rid);// }
}
int Built_in_com()//用返回值判断是否为内建命令,如果是返回 1, 不是返回 0.
{int ret = 0;if(strcmp(argv[0],"cd") == 0){ret = 1;char* home = argv[1];if(!home) home = Home(); chdir(home);char word[512];getcwd(word,512);snprintf(pwd,SIZE,"PWD=%s",word);//注意这里数字的大小不能越界putenv(pwd);}else if(strcmp(argv[0],"export") == 0){ret = 1;if(argv[1]) {strcpy(env,argv[1]);putenv(env);}}else if(strcmp(argv[0],"echo") == 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 // echo $环境变量名{char* n = getenv(argv[1]+1);if(n){printf("%s\n",n);}else{printf("The environment variable does not exist\n");}}}else//echo XXXX 这里暂不考虑其他情况,例如echo和重定向符号结合{printf("%s\n",argv[1]);}}}return ret;
}

 这里lastcode变量定义为全局变量。echo的其他搭配暂时不考虑。

运行结果

额外的配置:我们可以看见当我们使用ls命令时,打印出来的文件名是没有颜色的。如果我们想让文件名具有颜色,需要再Split函数里面进行修改,我们需要再命令行参数列表中加上一个“--color”字符即可。

下面演示一下代码

void Split(char* command)
{int i = 0;argv[i++] = strtok(command,SYM);//argv为全局变量,用于存放字符变量while(argv[i++] = strtok(NULL, SYM));//SYM为空格,这里是个宏if(strcmp(argv[0],"ls") == 0){argv[i - 1] = "--color";argv[i] = NULL;}
}

运行结果

<5>全部代码

#include<unistd.h>
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<string.h>#define SIZE 1024
#define argc 128
#define SYM  " "
char* argv[argc];
char pwd[SIZE];
char env[SIZE];
int lastcode;const char* HostName()
{putenv("HOSTNAME=iZuf6at4ih6u7gbg2vxumnZ");char* hostname = getenv("HOSTNAME");if(hostname){return hostname;}else{return "NONE";}
}
const char* UserName()
{char* username = getenv("USER");if(username){return username;}else{return "NONE";}
}
const char* Currentdir()
{char* dirname = getenv("PWD");if(dirname){return dirname;}else{return "NONE";}
}
char* Home()
{char* home = getenv("HOME");if(home){return home;}else{return NULL;}
}
int interactive(char* command)
{printf("[%s@%s -- %s]$",UserName(),HostName(),Currentdir());fgets(command,SIZE,stdin);command[strlen(command) - 1] = 0;return strlen(command);
}
void Split(char* command)
{int i = 0;argv[i++] = strtok(command,SYM);//argv为全局变量,用于存放字符变量while(argv[i++] = strtok(NULL, SYM));//SYM为空格,这里是个宏if(strcmp(argv[0],"ls") == 0){argv[i - 1] = "--color";argv[i] = NULL;}
}void Execute()
{pid_t id = fork();if(id == 0){execvp(argv[0],argv);}int status = 0;pid_t rid = waitpid(id,&status,0);if(WIFEXITED(status)){lastcode = WEXITSTATUS(status);}// if(rid > 0)// {//     printf("wait success, pid: %d\n",rid);// }
}
int Built_in_com()//用返回值判断是否为内建命令,如果是返回 1, 不是返回 0.
{int ret = 0;if(strcmp(argv[0],"cd") == 0){ret = 1;char* home = argv[1];if(!home) home = Home(); chdir(home);char word[512];getcwd(word,512);snprintf(pwd,SIZE,"PWD=%s",word);//注意这里数字的大小不能越界putenv(pwd);}else if(strcmp(argv[0],"export") == 0){ret = 1;if(argv[1]) {strcpy(env,argv[1]);putenv(env);}}else if(strcmp(argv[0],"echo") == 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 // echo $环境变量名{char* n = getenv(argv[1]+1);if(n){printf("%s\n",n);}else{printf("The environment variable does not exist\n");}}}else//echo XXXX 这里暂不考虑其他情况,例如echo和重定向符号结合{printf("%s\n",argv[1]);}}}return ret;
}
int main()
{while(1)//让shell持续运行{char command[SIZE];//1.获取用户指令并打出命令行提示符int ret = interactive(command);if(!ret){continue;}//2.切割命令行Split(command);//3.处理内建命令ret = Built_in_com();if(ret){continue;}//4.执行命令Execute();}// for(int i = 0; argv[i];i++)// {//     printf("argv[%d]:%s\n",i,argv[i]);// }return 0;
}

 如有需要,可自行拓展,另外上述代码仅供参考,不是唯一的写法。

以上就是全部内容,文中如有不对之处,还望各位大佬指正,谢谢!!!

这篇关于Linux学习之路 -- 进程篇 -- 自定义shell的编写的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/944555

相关文章

Java虚拟机之JVM整理学习笔记(四)

[b][color=green][size=large]前几篇我们学习了,JVM里面的运行结构,GC算法,以及各种垃圾收集器的优劣点,那么本篇我们来看下如何使用一些虚拟机性能监控工具,来监控和快速处理故障,当JVM出现一些故障时,我们通常从如下的几个方面进行着手分析,包括运行日志,异常堆栈,GC日志,线程快照(threaddump/javacore文件),堆转储快照(heapdump/hprof文

Java虚拟机之JVM整理学习笔记(三)

[b][color=green][size=large] 一,判断对象存活算法 JVM如何判断对象可以回收了? 主要的算法有: 1,引用计数算法,当多一个地方引用此类时,引用计数加一,否则就减一,算法很简单,实现也比较简单 缺点:很难解决对象之间相互引用的问题 2,可达性分析算法,通过一系列GC Roots路径,从节点开始搜索,来判断整个引用链是否还有关系。 在java语言中,

Java虚拟机之JVM整理学习笔记(二)

[b][color=olive][size=large]上篇文章,散仙整理了关于JVM的运行时的数据区以及各个区域,本篇我们就来看下各个区域发生异常代码的实战代码,以便于大家更容易在实际应用找到感觉。[/size][/color][/b] [b][color=green][size=large] 1,JAVA堆溢出代码,需要设置JVM参数 [/size][/color][/b]

linux 网络管理 实验

目录 网络管理主机名管理网络管理 网络管理 主机名管理 执行如下命令查看主机名。 [root@openEuler ~]# hostnameopenEuler[root@openEuler ~]# cat /etc/hostname #这个文件是主机名的配置文件openEuler 执行如下命令临时修改主机名。 [root@openEuler ~]# host

基于java 自定义注解Annotation设计简单ORM框架——进阶篇

目录 引言实例新建两个注解标注实体类拼接sql语句 总结 引言 一般Java规范编程(只是一种习惯,而不是强制)中,变量的命名方式一般采用驼峰式命名。比如userName,userImage。但是在数据库中一般不会采用驼峰式,而是采用下划线_的方式,比如user_name,user_image,这里就利用自定义注解的方式去进行一个转换。注解分为两种:JDK自带注解和自定义注解,这

如何在Linux下递归查看所有文件或目录?

[b][color=green][size=large] find命令,是个非常nice的命令,如果你还不了解这个命令,可以使用man find命令,来查看具体的介绍,find命令结合grep命令之后是非常强大的: (1)找出某个目录下所有的文件,并且是csv结尾的: [/size][/color][/b] [search@dnode1 2015-04-10]$ find ./

备忘几个有用的shell脚本

(1)在shell下,操作hadoop目录,批量命名或删除,最终的命令sed的正则贪婪替换,看下面的脚本: Java代码   # 遍历 hadoop 目录下的文件名     for line in `hadoop fs -ls /user/d1 | gawk '{ print $8 }' `   do    #echo $line;        #将2级目录下的文件,移动到上

数据结构与算法学习笔记十---链队列的表示和实现(C语言)

目录 前言 1.什么是链队 2.链队的表示和实现 1.定义 2.初始化 3.销毁 4.清空 5.空队列 6.队列长度 7.获取队头 8.入队 9.出队 10.遍历队列 11.完整代码 前言     本篇博客介绍链栈队列的表示和实现。 1.什么是链队     链队是采用链式存储结构实现的队列。通常链队使用单链表表示。     图1.链队的示意

机器学习-12-sklearn案例02-集成学习

总结 参考 菜菜的sklearn课堂——随机森林 傻子都能看懂的——详解AdaBoost原理 算法使用过程 #导入数据集模块from sklearn import datasets#分别加载iris和digits数据集iris = datasets.load_iris() #鸢尾花数据集# print(dir(datasets))# print(iris_dataset.ke

hive自定义udf实现md5功能

Hive自定义UDF实现md5算法 Hive发展至今,自身已经非常成熟了,但是为了灵活性,还是提供了各种各样的 插件的方式,只有你想不到的,没有做不到的,主流的开源框架都有类似的机制,包括Hadoop,Solr,Hbase,ElasticSearch,这也是面向抽象编程的好处,非常容易扩展。 最近在使用hive1.2.0的版本,因为要给有一列的数据生成md5签名,便于查重数据使用,看了