在这个shell编写中,我们遇到了很多我们之前很少使用的函数
1.getenv/putenv
1.getenv
头文件:#include<stdlib.h>
函数原型: char * getenv(const char* name);
函数说明:getenv()用来取得参数name环境变量的内容。
函数参数:name为环境变量的名称,如果该变量存在则会返回指向该内容的指针。环境变量的格式为name=value。
返回值:若环境变量存在,返回环境变量值的指针,否则返回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){//childexecvp(Gargc[0], Gargc);exit(errno);}else{//fatherint status = 0;pid_t rid = waitpid(id, &status, 0);if(rid > 0){//father dolastcode = 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 variableschar 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 ourselvesMakeCommandLineAndPrint();//2. Get the user command stringchar UserCommandLine[SIZE];int n = GetUserCommandLine(UserCommandLine, sizeof(UserCommandLine));if(n <= 0) return 1;//3. Command-line string splittingSplitCommandLine(UserCommandLine, sizeof(UserCommandLine));//4. Check if the command has built-in commandsn = CheckBuildin();if(n) continue; //5. Execute the commandExecuteCommand();} return 0;
}