在这个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)
{
//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;
}