0


【Linux】解锁Shell脚本编写秘籍,编程高手之路等你开启

目录

1. 打印命令行提示符

#include<stdlib.h>char*HostName()//获取主机名{char* ret =getenv("HOSTNAME");if(ret)return ret;elsereturn(char*)"None";}char*UserName()//获取用户名{char* ret =getenv("USER");if(ret)return ret;elsereturn(char*)"None";}char*CurrentWorkdir()//获取当前工作目录{char* ret =getenv("PWD");if(ret)return ret;elsereturn(char*)"None";}intmain(){//打印命令行提示符 //读取环境变量的内容 / 调用函数(gethostname获取主机名、getcwd获取当前工作目录)printf("[%s@%s %s]#",UserName(),HostName(),CurrentWorkdir());}

2. 获取用户输入的命令行字符串

  1. 可以使用scanf读取字符串吗?
  • 答:不能,scanf用于从标准输入(键盘)读取格式化输入,读取字符串时,scanf会在遇到空白字符(空格、换行符\n、制表符\t等)时停止读取,并将读取的字符串存储到字符数组中。
  1. char* fgets(char* str, int size, FILE* stream);
  • 功能:从指定的文件流stream中读取一行(直到遇到换行符\n、文件结束符EOF、已读取了n-1个字符为止),并将读取的字符串(包括换行符,如果有的话)存储在str指向的数组中,并在数组的末尾添加’\0’来标记字符串的结束。
  • 优点:可以限制读取的字符数,防止缓冲区溢出,并且可以读取包含空格的字符串。
  • 缺点:如果读取的行比指定的长度n还要长,则多余的字符会被留在输入缓冲区中。

文件流stream是程序用于输入和输出的基本抽象,stdin为标准输入流(键盘)、stdout为标准输出流(控制台或终端)、stderr为标准错误输出流。

#include<stdlib.h>#defineSIZE1024intInterative(char* in){printf("[%s@%s %s]#",UserName(),HostName(),CurrentWorkdir());fgets(in, SIZE,stdin);  
    in[strlen(in)-1]='\0';//移除换行字符\nreturnstrlen(in)-1;//处理空串""情况}intmain(){char CommandLine[SIZE];//存储用户输入的命令行字符串while(1){//打印命令行提示符+获取用户输入的命令行字符串                                      int n =Interative(CommandLine);if(n ==0)continue;//空串,用户继续输入}return0;}

问:为什么用fegets读取一行字符串时,需要单独处理换行字符’\n’?

  • 因为我们在键盘中输入时,会多按一个回车字符’\n’,但对于命令来说,这个字符是无任何作用的,所以对于命令行参数表而言,不能存储带有换行的命令或选项。如:ls回车,实际上就是执行ls命令。

3. 对命令行字符串进行解析(分割)

char* strtok(char* str,const char* delim);

  • 首次调用:它接受两个参数(待分解的字符串、分割符字符串),函数会在str中查找delim中的任意一个字符,一旦找到,就将该字符替换为字符串结束符’\0’,并返回指向当前令牌(分隔符之前的字符串)的指针。
  • 后续调用:在首次调用后,每次调用strtok,应将第一个参数设置为NULL,以便函数从上次分解的位置查找并分解字符串。
#include<stdlib.h>#include<string.h>#defineSIZE1024char* argv[SIZE];//相当于main函数的命令行参数表,末尾一定要以NULL结尾voidSplit(char* in){int i =0;
    argv[i++]=strtok(in, SP);//首次调用while(argv[i++]=strtok(NULL, SP));//后续调用if(strcmp("ls", argv[0])==0){
       argv[i-1]=(char*)"--color";//加上后,ls显示内容,颜色为auto                   
       argv[i]=NULL;//argv以NULL结尾,便于子进程在进程程序替换时参数的传递}}intmain(){char CommandLine[SIZE];while(1){//1.打印命令行提示符+获取用户输入的命令行字符串                                      int n =Interative(CommandLine);if(n ==0)continue;//空串,用户继续输入//2.分割命令行字符串Split(CommandLine);}return0;}

4. 处理内建命令

4.1. 内建命令

  1. 概念:是shell内部直接提供并实现的命令,不要shell创建子进程来调用一个独立的外部程序(exe函数)来执行,而是由shell本身解释并执行。
  2. 可用性和依赖性:内建命令随shell的安装而自动提供,不需要额外的安装。
  3. 常见的内建命令:cd、echo、export、pwd、exit等。

4.2. 外部命令

  1. 概念:由用户自己编写的程序或者从外部引入的程序,它作为独立的程序存在于文件系统中,需要shell调用OS来执行一系列操作(创建子进程、子进程进行程序替换、子进程执行外部命令)。
  2. 可用性和依赖性:通常需要单独安装,并依赖于特定的OS和环境。

4.3. cd

问1:为什么会出现bash的当前工作目录,以及提示符中的工作目录,并未发生修改?

  • cd会被当作为外部命令,bash创建子进程,由子进程进行程序替换,执行cd命令,子进程执行完毕,就会退出,父进程无任何影响,所以只改变了子进程的当前工作目录。

int chdir(const char* path);

  • 功能:改变当前工作目录,但不会更新环境变量PWD中的内容。

int snprintf(char* str,size_t size,const char* format,. . .);

  • 可以将多个变量值合并为单一的、格式化的字符串。

intBuiltinCmd(){int ret =0;//用于判断是否为内建命令,不是,则为0、是,则为1if(strcmp("cd", argv[0])==0){
        ret =1;char* target = argv[1];//需要切换的路径if(!target) target =getenv("HOME");//cd,表示切换到家目录中chdir(target);//改变当前工作目录,但不改变环境变量PWD的内容char tmp[SIZE];snprintf(tmp, SIZE,"PWD=%s", target);//多变一putenv(tmp);//修改或者新增环境变量                                              }return ret;}

char* getcwd(char* buf,size_t size);

#include<stdio.h>#include<stdlib.h>#include<string.h>#include<unistd.h>#defineSIZE1024char* argv[SIZE];//相当于main函数的命令行参数表,末尾一定要以NULL结尾intBuiltinCmd(){int ret =0;//用于判断是否为内建命令,不是,则为0、是,则为1if(strcmp("cd", argv[0])==0){
        ret =1;char* target = argv[1];//需要切换的路径if(!target) target =getenv("HOME");//cd,表示切换到家目录中chdir(target);//更改当前工作目录char tmp[SIZE];//存储获取到的当前工作目录的绝对路径char pwd[SIZE];//存储更改后的环境变量PWD的内容getcwd(tmp, SIZE);//获取当前工作目录的绝对路径snprintf(pwd, SIZE,"PWD=%s", tmp);//多变一,拼接putenv(pwd);//修改环境变量PWD的内容}return ret;}intmain(){char CommandLine[SIZE];while(1){//1.打印命令行提示符+获取用户输入的命令行字符串                                      int n =Interative(CommandLine);if(n ==0)continue;//空串,用户继续输入//2.分割命令行字符串Split(CommandLine);//3.处理内建命令
        n =BuiltinCmd();if(n)continue;}return0;}

4.5. export

intBuiltinCmd(){int ret =0;if(strcmp("export", argv[0])==0){
        ret =1;if(argv[1]){putenv(argv[1]);}}return ret;}

问:为什么第一次export新增环境变量成功,但再输入了其他指令,这个新增的环境变量就不见了?

  • 因为argv为字符指针数组,执行完export后,再输入其他指令,则argv中原有的内容就会被其他指令所覆盖。
char env[SIZE];//相当于环境变量表,存储新增的环境变量intBuiltinCmd(){if(strcmp("export", argv[0])==0)int ret =0;{
        ret =1;if(argv[1]){strcpy(env, argv[1]);//将新增的环境变量的内容,拷贝到环境变量表中putenv(env);}}return ret;}

4.6. echo

char env[SIZE];//相当于环境变量表,存储新增的环境变量intBuiltinCmd(){if(strcmp("echo", argv[0])==0){                                                       
        ret =1;if(!argv[1])//echo:打印一个空行printf("\n");elseif(argv[1][0]!='$')printf("%s\n", argv[1]);//echo aaaa:打印aaaaelse{if(argv[1][1]=='?'){printf("%d\n", exit_code);//echo $?:打印退出码
                exit_code =0;//便于在执行echo $?时,退出码=0}elseprintf("%s\n",getenv(argv[1]+1));//echo $USER:打印环境变量的内容}}return ret;}

5. 执行命令

1.Shell脚本编写,bash执行命令时,通常会创建子进程,让子进程执行程序替换。

  1. 原因:a. bash是命令行解释器,需要一直执行命令,若让父进程进行替换,执行命令,则父进程就会退出;b. 进程之间具有独立性,子进程进行替换,对父进程无影响。

5.1. 创建子进程进行程序替换

#include<stdlib.h>#include<string.h>#include<sys/types.h>#include<sys/wait.h>#defineSIZE1024#defineSP" "char* argv[SIZE];int exit_code;//全局变量,存储子进程的退出码,便于echo $?直接输出退出码voidExecute(){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 >0){
        exit_code =WEXITSTATUS(status);//正常退出,获取子进程的退出码}}intmain(){char CommandLine[SIZE];while(1){//1.打印命令行提示符+获取用户输入的命令行字符串                                      int n =Interative(CommandLine);if(n ==0)continue;//空串,用户继续输入//2.分割命令行字符串Split(CommandLine);//4.执行命令(创建子进程,进行替换)Execute();}return0;}

6. 重定向

  1. 为什么要宏定义多个整数,定义全局变量redir_type?
  • 重定向有三种类型:输出重定向>、追加重定向>>、输出重定向。
  • 我们需要在命令行参数中,查找是否有重定向,如果有重定向,就需要获取重定向的类型,从而在根据重定向的类型执行对应的指令。

所以我们用宏定义多个整数,来表示重定向的类型; 定义一个全局变量redir_type,用来记录获取到的重定向的类型

#defineNoneRedir-1#defineStdinRedir0#defineStdoutRedir1#defineAppenRedir2char* filename =NULL;int redir_type =-1;
  1. 通常来说,重定向由三部分组成:要执行的命令 重定向符号 文件名(如:ls -l > log.txt)。
  • 我们需要获取两部分内容:将要执行的命令进行分割,获取重定向文件。
char* filename =NULL;
  1. 执行流程:首先判断是否包含重定向(checkRedir);再把我们要执行的命令进行分分割(将重定向字符设置为’\0’,这样strtok只能切割要执行的命令);最后让子进程执行重定向操作。
#defineIsSpace(buf, pos)do{while(isspace(buf[pos])) pos      ++;}while(0)

while(0)目的:可以在使用宏定义的函数处,结尾加上分号(;)可以消除宏和函数之间的这部分差异,让宏看起来更像个函数。

//用宏定义多个整数,来表示重定向的类型#defineNoneRedir-1#defineStdinRedir0#defineStdoutRedir1#defineAppenRedir2char* filename =NULL;//记录文件名int redir_type =-1;//记录重定向类型//去除命令行参数中的空格(连续空格)#defineIsSpace(buf, pos)do{while(isspace(buf[pos])) pos++;}while(0)//检查是否包含重定向符号voidCheckRedir(char* in){
    filename =NULL;
    redir_type =-1;int pos =strlen(in)-1;//从命令行参数字符串后面往前找while(pos >=0){if(in[pos]=='>'){if(in[pos -1]=='>')//追加重定向{
                redir_type =2;                          
                in[pos -1]='\0';//便于将要执行的命令分割开
                pos++;//当前pos位置不是空格,isspace函数为假,直接返回IsSpace(in, pos);//跳过空格
                filename = in + pos;//获取文件名break;//记录完毕,就立即退出}else//输出重定向{
                 redir_type =1;
                 in[pos]='\0';
                 pos++;IsSpace(in, pos);             
                 filename = in + pos;break;}}elseif(in[pos]=='<')//输入重定向{
            redir_type =0;
            in[pos]='\0';
            pos++;IsSpace(in, pos);
            filename = in + pos;break;}else{ 
            pos--;}}}voidSplit(char* in){CheckRedir(in);//在分割之前,先判断是否包含重定向符号int i =0;
    argv[i++]=strtok(in, SP);//首次调用while(argv[i++]=strtok(NULL, SP));//后续调用if(strcmp("ls", argv[0])==0){
       argv[i-1]=(char*)"--color";//加上后,ls显示内容,颜色为auto                   
       argv[i]=NULL;//argv以NULL结尾,便于子进程在进程程序替换时参数的传递}}voidExecute(){pid_t id =fork();if(id ==0){//让子进程执行重定向int fd =-1;if(redir_type == StdinRedir){
            fd =open(filename, O_RDONLY);dup2(fd,0);//改变文件描述符的执行,从而实现输入、输出重定向}elseif(redir_type == StdoutRedir){
            fd =open(filename, O_WRONLY|O_CREAT|O_TRUNC,0666);dup2(fd,1);}elseif(redir_type == AppenRedir){
            fd =open(filename, O_WRONLY|O_CREAT|O_APPEND      ,0666);dup2(fd,1);}execvp(argv[0], argv);//程序替换,执行命令exit(1);//子进程退出}int status =0;pid_t rid =waitpid(id,&status,0);//父进程等待,进行回收子进程的资源if(rid >0){
        exit_code =WEXITSTATUS(status);//正常退出,获取子进程的退出码}}

7. 总代码

#include<stdio.h>#include<stdlib.h>#include<string.h>#include<unistd.h>#include<sys/types.h>#include<sys/wait.h>#include<ctype.h>#include<sys/stat.h>#include<fcntl.h>#defineSIZE1024#defineSP" "char* argv[SIZE];char env[SIZE];//相当于环境变量表,存储新增的环境变量int exit_code;//全局变量,存储子进程的退出码,便于echo $?直接输出退出码//以下都和重定向有关//用宏定义多个整数,来表示重定向的类型#defineNoneRedir-1#defineStdinRedir0#defineStdoutRedir1#defineAppenRedir2char* filename =NULL;//记录文件名int redir_type =-1;//记录重定向类型//去除命令行参数中的空格(连续空格)#defineIsSpace(buf, pos)do{while(isspace(buf[pos])) pos++;}while(0)char*HostName()//获取主机名{char* ret =getenv("HOSTNAME");if(ret)return ret;elsereturn(char*)"None";}char*UserName()//获取用户名{char* ret =getenv("USER");if(ret)return ret;elsereturn(char*)"None";}char*CurrentWorkdir()//获取当前工作目录{char* ret =getenv("PWD");if(ret)return ret;elsereturn(char*)"None";}intInterative(char* in){printf("[%s@%s %s]#",UserName(),HostName(),CurrentWorkdir());fgets(in, SIZE,stdin);  
    in[strlen(in)-1]='\0';//移除换行字符\nreturnstrlen(in)-1;//处理空串""情况}//检查是否包含重定向符号voidCheckRedir(char* in){
    filename =NULL;
    redir_type =-1;int pos =strlen(in)-1;//从命令行参数字符串后面往前找while(pos >=0){if(in[pos]=='>'){if(in[pos -1]=='>')//追加重定向{
                redir_type =2;                          
                in[pos -1]='\0';//便于将要执行的命令分割开
                pos++;//当前pos位置不是空格,isspace函数为假,直接返回IsSpace(in, pos);//跳过空格
                filename = in + pos;//获取文件名break;//记录完毕,就立即退出}else//输出重定向{
                 redir_type =1;
                 in[pos]='\0';
                 pos++;IsSpace(in, pos);             
                 filename = in + pos;break;}}elseif(in[pos]=='<')//输入重定向{
            redir_type =0;
            in[pos]='\0';
            pos++;IsSpace(in, pos);
            filename = in + pos;break;}else{ 
            pos--;}}}voidSplit(char* in){CheckRedir(in);//在分割之前,先判断是否包含重定向符号int i =0;
    argv[i++]=strtok(in, SP);//首次调用while(argv[i++]=strtok(NULL, SP));//后续调用if(strcmp("ls", argv[0])==0){
       argv[i-1]=(char*)"--color";//加上后,ls显示内容,颜色为auto                   
       argv[i]=NULL;//argv以NULL结尾,便于子进程在进程程序替换时参数的传递}}intBuiltinCmd(){if(strcmp("echo", argv[0])==0){                                                       
        ret =1;if(!argv[1])//echo:打印一个空行printf("\n");elseif(argv[1][0]!='$')printf("%s\n", argv[1]);//echo aaaa:打印aaaaelse{if(argv[1][1]=='?'){printf("%d\n", exit_code);//echo $?:打印退出码
                exit_code =0;//便于在执行echo $?时,退出码=0}elseprintf("%s\n",getenv(argv[1]+1));//echo $USER:打印环境变量的内容}}elseif(strcmp("cd", argv[0])==0){
        ret =1;char* target = argv[1];//需要切换的路径if(!target) target =getenv("HOME");//cd,表示切换到家目录中chdir(target);//更改当前工作目录char tmp[SIZE];//存储获取到的当前工作目录的绝对路径char pwd[SIZE];//存储更改后的环境变量PWD的内容getcwd(tmp, SIZE);//获取当前工作目录的绝对路径snprintf(pwd, SIZE,"PWD=%s", tmp);//多变一,拼接putenv(pwd);//修改环境变量PWD的内容}elseif(strcmp("export", argv[0])==0)int ret =0;{
        ret =1;if(argv[1]){strcpy(env, argv[1]);//将新增的环境变量的内容,拷贝到环境变量表中putenv(env);}}return ret;}voidExecute(){pid_t id =fork();if(id ==0){//让子进程执行重定向int fd =-1;if(redir_type == StdinRedir){
            fd =open(filename, O_RDONLY);dup2(fd,0);//改变文件描述符的执行,从而实现输入、输出重定向}elseif(redir_type == StdoutRedir){
            fd =open(filename, O_WRONLY|O_CREAT|O_TRUNC,0666);dup2(fd,1);}elseif(redir_type == AppenRedir){
            fd =open(filename, O_WRONLY|O_CREAT|O_APPEND      ,0666);dup2(fd,1);}execvp(argv[0], argv);//程序替换,执行命令exit(1);//子进程退出}int status =0;pid_t rid =waitpid(id,&status,0);//父进程等待,进行回收子进程的资源if(rid >0){
        exit_code =WEXITSTATUS(status);//正常退出,获取子进程的退出码}}intmain(){char CommandLine[SIZE];while(1){//1.打印命令行提示符+获取用户输入的命令行字符串                                      int n =Interative(CommandLine);if(n ==0)continue;//空串,用户继续输入//2.分割命令行字符串Split(CommandLine);//3.处理内建命令
        n =BuiltinCmd();if(n)continue;//4.执行命令(创建子进程,进行替换            Execute();}return0;}
标签: linux 运维 服务器

本文转载自: https://blog.csdn.net/m0_74808907/article/details/142133792
版权归原作者 奶芙c 所有, 如有侵权,请联系我们删除。

“【Linux】解锁Shell脚本编写秘籍,编程高手之路等你开启”的评论:

还没有评论