编写自主shell
#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include<stdlib.h>
#include<errno.h>
int argc = 0;
char* argv[32];//这是一个指针数组,用来存储每个命令字符串的首地址
int str_to_env(char* buff)//该函数实现字符串转变成环境变量
{
if (buff == NULL)return -1;
char*ptr = buff;//ptr指针去找空格,空格的下一位不是空格的话,说明此刻ptr所走过的字符串是一个单词
char*head = ptr;//用来标记ptr每次的起点,从而确定这个命令单词的区间
argc = 0;
while ((*ptr) != '\0')
{
if ((*ptr) == ' ' && (*(ptr + 1)) != ' ')
{
*ptr = '\0';
argv[argc] = head;
argc++;
head = ptr + 1;
}
ptr++;
}
//此时head指向最后一个字符串的第一个字符,ptr指向'\0'
argv[argc] = head;
//当把所有指令字符串都拷贝完成后,应该让argv数组以NULL结尾
argv[++argc] = NULL;
return 0;
}
int exec_cmd()
{
int pid = -1;
pid = fork();
if (pid<0)
{
perror("fork error");
return -1;
}
else if (pid == 0)
{
//int execvp(const char *file, char *const argv[]);
execvp(argv[0], argv);//exec带p默认在当前路径下找替换文件
exit(0);
}
int statu;
wait(&statu);
//判断子进程是否是代码运行完毕退出
if (WIFEXITED(statu)) {
//获取到子进程的退出码,转换为文本信息打印
printf("%s\n", strerror(WEXITSTATUS(statu)));
}
return 0;
}
int main()
{
while (1){
printf("shell>>");
char buff[1024] = { 0 };
scanf("%[^\n]%*c", buff);
//%[^\n] 获取输入数据直到遇到\n为止
//%*c 清空缓冲区
printf("%s\n", buff);
str_to_env(buff);
exec_cmd();
}
return 0;
}
封装fork/wait等操作
编写函数 process_create(pid_t* pid, void* func, void* arg), func回调函数就是子进程执行的入口函数, arg是传递给func回调函数的参数.
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
int process_create(int (*func)(),const char* file,char *argv[])
{
pid_t pid = fork();
if(pid == -1){
perror("fork error!");
exit(0);
}
if(pid == 0){
int ret = func(file,argv);
if(ret == -1){
perror("func error!");
exit(1);
}
}else{
int st ;
pid_t ret = wait(&st);
if(ret == -1){
perror("wait error!");
exit(2);
}
}
return 0;
}
int main()
{
char *argv1[] = {"ls"};
char *argv2[] = {"ls","-al","/etc/passwd",0};
process_create(execvp,*argv1,argv2);
return 0;
}
调研popen/system, 理解这两个函数和fork的区别.
system()会调用fork()产生子进程,由子进程来调用/bin/sh-c string来执行参数string字符串所代表的命令,此命令执行完后随即返回原调用的进程。在调用system()期间SIGCHLD 信号会被暂时搁置,SIGINT和SIGQUIT 信号则会被忽略。
调用/bin/sh来执行参数指定的命令,/bin/sh 一般是一个软连接,指向某个具体的shell。
system()函数执行了三步操作:
#include <stdlib.h>
int system(const char *command);
- fork一个子进程;
- 在子进程中调用exec函数去执行command;
- 在父进程中调用wait去等待子进程结束。
函数返回值:
1>如果 exec 执行成功,即 command 顺利执行,则返回 command 通过 exit 或 return 的返回值。(注意 :command 顺利执行不代表执行成功,当参数中存在文件时,不论这个文件存不存在,command 都顺利执行)
2>如果exec执行失败,也即command没有顺利执行,比如被信号中断,或者command命令根本不存在, 返回 127
3>如果 command 为 NULL, 则 system 返回非 0 值.
4>对于fork失败,system()函数返回-1。
判断一个 system 函数调用 shell 脚本是否正常结束的方法的条件应该是
- status != -1
2.(WIFEXITED(status) 非零且 WEXITSTATUS(status) ==
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<errno.h>
int main()
{
int ret=-1;
ret=system("ls -la");
if(ret=-1){
perror("system error");
exit(-1);
}
if(WIFEXITED(ret)!=0)//(正常退出,有返回信息)
{
//将退出信息打印成字符信息
printf("normal exit:%c",strerror(WIFEXITSTATUS(ret)));
}else {
printf("cause signal:%c",strerror(WIFEXITSTATUS(ret)));
}
return 0;
}
popen函数
popen()函数:创建一个管道用于进程间通信,并调用shell,因为管道被定义为单向的 所以 type 参数 只能定义成 只读或者 只写, 不能是 两者同时, 结果流也相应的 是只读 或者 只写.
函数原型:
#include <stdio.h>
FILE *popen(const char *command, const char *type);
int pclose(FILE *stream);
函数功能:popen()会调用fork()产生子进程,然后从子进程中调用/bin/sh -c来执行参数command的指令。这个进程必须由 pclose 关闭。
command参数:
command 参数 是 一个 字符串指针, 指向的是一个 以 null 结束符 结尾的字符串, 这个字符串包含 一个 shell 命令. 这个命令 被送到 /bin/sh 以 -c 参数 执行, 即由 shell 来执行.
type 参数 也是 一个 指向 以 null 结束符结尾的 字符串的指针
参数type可使用“r”代表读取,“w”代表写入。
依照此type值,popen()会建立管道连到子进程的标准输出设备或标准输入设备,然后返回一个文件指针。
随后进程便可利用此文件指针来读取子进程的输出设备或是写入到子进程的标准输入设备中。
返回值:
若成功则返回文件指针,否则返回NULL,错误原因存于errno中
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<errno.h>
int main()
{
FILE*file=NULL;
file=popen("cat file.txt","r");
if(file==NULL){
perror("popen error");
return -1;
}
char buff[1024]={0};
while(fgets(buff,1024,file)!=NULL){
fprintf(stdout, "%s", buff);
}
pclose(file);
return 0;
}
区别
1.system 在执行期间,调用进程会一直等待 shell 命令执行完成(waitpid),但是 popen 无需等待 shell 命令执行完成就返回了。可以理解为,system为串行执行,popen 为并行执行。
2.popen 函数执行完毕后必须调用 pclose 来对所创建的子进程进行回收,否则会造成僵尸进程的情况。
3.popen 没有屏蔽 SIGCHLD ,如果我们在调用时屏蔽了 SIGCHLD ,如果在 popen 和 pclose 之间调用进程又创建了其他子进程并调用进程注册了 SIGCHLD 来处理子进程的回收工作,那么这个回收工作会一直阻塞到 pclose 调用。