linux之自主shell编写

时间:2024-04-05 22:44:58

在这个shell编写中,我们遇到了很多我们之前很少使用的函数

1.getenv/putenv

     1.getenv

        头文件:#include<stdlib.h>

函数原型: char * getenv(const char* name);

函数说明:getenv()用来取得参数name环境变量的内容。

函数参数:name为环境变量的名称,如果该变量存在则会返回指向该内容的指针。环境变量的格式为namevalue

返回值:若环境变量存在,返回环境变量值的指针,否则返回NULL

     2.putenv

        头文件:#include<stdlib.h>

函数原型: int putenv(const char* str);

函数说明:putenv用来改变或者增加环境变量的内容

参数:str的格式为name=value,若环境原先存在,则环境变量值会依参数str改变,若不存在,则增加该环境变量

返回值:成功返回0,错误返回-1.

     3.拓展

        setenv函数

头文件:#include<stdlib.h>

函数原型: int setenv(const char* name, const char* value, int overwrite)

函数说明:setenv用来改变或者增加环境变量

参数:name为环境变量名称字符串。 value则为变量内容,overwrite用来决定是否要改变已存在的环境变量。如果overwrite不为0,而该环境变量原已有内容,则原内容会被改为参数value所指的变量内容。如果overwrite为0,且该环境变量已有内容,则参数value会被忽略。

返回值:成功返回0,错误返回-1.

2.snprintf

        1.描述

        snprintf() 是一个 C 语言标准库函数,用于格式化输出字符串,并将结果写入到指定的缓冲区,与 sprintf() 不同的是,snprintf() 会限制输出的字符数,避免缓冲区溢出。

        C 库函数 int snprintf(char *str, size_t size, const char *format, ...) 设将可变参数(...)按照 format 格式化成字符串,并将字符串复制到 str 中,size 为要写入的字符的最大数目,超过 size 会被截断,最多写入 size-1 个字符。

        与sprintf()函数不同的是,snprintf() 函数提供了一个参数 size,可以防止缓冲区溢出。如果格式化后的字符串长度超过了 size-1,则 snprintf() 只会写入 size-1 个字符,并在字符串的末尾添加一个空字符(\0)以表示字符串的结束。

        2.函数原型

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

        3.参数意义 

  • str -- 目标字符串,用于存储格式化后的字符串的字符数组的指针。
  • size -- 字符数组的大小。
  • format -- 格式化字符串。
  • ... -- 可变参数,可变数量的参数根据 format 中的格式化指令进行格式化。

      4.返回值

        snprintf() 函数的返回值是输出到 str 缓冲区中的字符数,不包括字符串结尾的空字符 \0。如果 snprintf() 输出的字符数超过了 size 参数指定的缓冲区大小,则输出的结果会被截断,只有 size - 1 个字符被写入缓冲区,最后一个字符为字符串结尾的空字符 \0。

        需要注意的是,snprintf() 函数返回的字符数并不包括字符串结尾的空字符 \0,因此如果需要将输出结果作为一个字符串使用,则需要在缓冲区的末尾添加一个空字符 \0。

3.strtok

        1.描述

        C 库函数 char *strtok(char *str, const char *delim) 分解字符串 str 为一组字符串,delim 为分隔符。

        2.函数原型

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

        3.参数意义 

  • str -- 要被分解成一组小字符串的字符串。
  • delim -- 包含分隔符的 C 字符串。

        4.返回值 

        该函数返回被分解的第一个子字符串,如果没有可检索的字符串,则返回一个空指针。 

4.chdir

        1.描述

                修改当前进程的路径

        2.函数原型

 int chdir(const char *path);

5.strerror

        1.描述

        C 库函数 char *strerror(int errnum) 从内部数组中搜索错误号 errnum,并返回一个指向错误消息字符串的指针。strerror 生成的错误字符串取决于开发平台和编译器。

         2.函数原型:

char *strerror(int errnum)

         3.参数意义 

  • errnum -- 错误号,通常是 errno

        4.返回值 

该函数返回一个指向错误字符串的指针,该错误字符串描述了错误 errnum。 

6.fgets

        1.描述:

        C 库函数 char *fgets(char *str, int n, FILE *stream) 从指定的流 stream 读取一行,并把它存储在 str 所指向的字符串内。当读取 (n-1) 个字符时,或者读取到换行符时,或者到达文件末尾时,它会停止,具体视情况而定。

        2.函数原型:

char *fgets(char *str, int n, FILE *stream)

        3.参数意义

  • str -- 这是指向一个字符数组的指针,该数组存储了要读取的字符串。
  • n -- 这是要读取的最大字符数(包括最后的空字符)。通常是使用以 str 传递的数组长度。
  • stream -- 这是指向 FILE 对象的指针,该 FILE 对象标识了要从中读取字符的流。

        4.返回值

        如果成功,该函数返回相同的 str 参数。如果到达文件末尾或者没有读取到任何字符,str 的内容保持不变,并返回一个空指针。

如果发生错误,返回一个空指针。

7.宏函数

#define SKipPath(p) do{ p+= (strlen(p) - 1); while(*p != '/') --p;}while(0)

8.代码

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

#define SIZE 512
#define ZERO '\0'
#define SEP " " 
#define NUM 32
#define SKipPath(p) do{ p+= (strlen(p) - 1); while(*p != '/') --p;}while(0)

char cwd[SIZE * 2];
char *Gargc[NUM];
int lastcode = 0;


void Die()
{
  exit(1);
}

const char *GetHome()
{
  const char *home = getenv("HOME");
  if(home == NULL) return "/";
  return home;
}

const char *GetUserName()
{
  const char* name = getenv("USER");
  if(name == NULL) return "None";
  return name;
}

const char *GetHostName()
{
  const char* hostname = getenv("HOSTNAME");
  if(hostname == NULL) return "None";
  return hostname;
}

const char *GetPwd()
{
  const char* pwd = getenv("PWD");
  if(pwd == NULL) return "None";
  return pwd;
}


//Command line output
void MakeCommandLineAndPrint()
{
  char CommandLine[SIZE];
  int size = sizeof(CommandLine);
  const char *name = GetUserName();
  const char *hostname = GetHostName();
  const char *pwd = GetPwd();

  SKipPath(pwd);
  snprintf(CommandLine, size, "[%s@%s %s]> ", name, hostname, strlen(cwd) == 1 ? "/" : pwd + 1); 

  printf("%s",CommandLine);
  fflush(stdout);
}

//User Command line input
int GetUserCommandLine(char *UserCommandLine, size_t size)
{
  char *s = fgets(UserCommandLine, size, stdin);
  if(s == NULL) return -1;
  s[strlen(UserCommandLine) - 1] = ZERO;
  return strlen(UserCommandLine);
}


void SplitCommandLine(char *UserCommandLine, size_t size)
{
  (void)size;
  Gargc[0] = strtok(UserCommandLine, SEP);
  int index = 1;
  while((Gargc[index++] = strtok(NULL, SEP)));
}


void ExecuteCommand()
{
  pid_t id =fork();
  if(id < 0) Die();
  else if(id == 0)
  {
    //child
    execvp(Gargc[0], Gargc);
    exit(errno);
  }
  else
  {
    //father
    int status = 0;
    pid_t rid = waitpid(id, &status, 0);
    if(rid > 0)
    {
      //father do
      lastcode = WEXITSTATUS(status);
      if(lastcode != 0) printf("%s:%s:%d\n",Gargc[0],strerror(lastcode), lastcode);
    }
  }
}

void cd()
{
  const char* path = Gargc[1];
  if(path == NULL)
    path = GetHome();
  // path == Gargc[1]
  chdir(path);

  //Refresh the environment variables
  char tmp[SIZE * 2];
  getcwd(tmp, sizeof(tmp));

  snprintf(cwd, sizeof(cwd), "PWD=%s", tmp);
  putenv(cwd);
}

int CheckBuildin()
{
  int yes = 0;
  const char *enter_cmd = Gargc[0];
  if(strcmp(enter_cmd, "cd") == 0)
  {
    yes = 1;
    cd();
  }
  else if(strcmp(enter_cmd, "echo") == 0 && strcmp(Gargc[1], "$") == 0)
  {
    yes = 1;
    printf("%d\n",lastcode);
    lastcode = 0;
  }
  return yes;
}

int main()
{
  int quit = 0;
  while(!quit)
  {
    //1. We need a command line that we output ourselves
    MakeCommandLineAndPrint();
    
    //2. Get the user command string
    char UserCommandLine[SIZE];
    int n = GetUserCommandLine(UserCommandLine, sizeof(UserCommandLine));
    if(n <= 0) return 1;

    //3. Command-line string splitting
    SplitCommandLine(UserCommandLine, sizeof(UserCommandLine));

    //4. Check if the command has built-in commands
    n = CheckBuildin();
    if(n) continue; 

    //5. Execute the command
    ExecuteCommand();
  }  
  return 0;  
}