⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 shell.c

📁 Unix 第 6 版的 sh 手册和源码
💻 C
📖 第 1 页 / 共 2 页
字号:
/*
 * 这个 shell 是 UNIX v6 的 sh 在 POSIX 环境下的重新实现。
 * UNIX v6 是受 BSD 许可证保护的自由软件,其中 sh 的原作者是 Ken Thompson。
 * 余对这个 shell 的语法做了细微修改,对代码做了注释和重写。
 * 余对这个程序和相关文档不做任何担保,放弃一切权利,不承担任何责任和义务。
 *
 * 最近的修订∶ 2004-09-06    寒蝉退士(http://mhss.nease.net)
 * 做的工作主要有: K&R C -> ANSI C,unix v6/v7 -> POSIX,去掉了进程记帐和 ^,
 * 增加了 $?、umask 和 exec,去除了 goto 语句,增加了中文注释。
 */

/*
 * Copyright(C) Caldera International Inc. 2001-2002. All rights reserved. 
 *
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met: 
 *
 *     Redistributions of source code and documentation must retain the above
 *     copyright notice, this list of conditions and the following disclaimer.
 *     Redistributions in binary form must reproduce the above copyright notice, 
 *     this list of conditions and the following disclaimer in the documentation
 *     and/or other materials provided with the distribution. 
 *
 *     All advertising materials mentioning features or use of this software
 *     must display the following acknowledgement: 
 *
 *     This product includes software developed or owned by Caldera International, Inc. 
 *
 *     Neither the name of Caldera International, Inc. nor the names of other
 *     contributors may be used to endorse or promote products derived from this
 *     software without specific prior written permission. 
 * 
 * USE OF THE SOFTWARE PROVIDED FOR UNDER THIS LICENSE BY CALDERA INTERNATIONAL, INC. 
 * AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL CALDERA INTERNATIONAL, INC. BE LIABLE FOR 
 * ANY DIRECT, INDIRECT INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF 
 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
 * POSSIBILITY OF SUCH DAMAGE. 
 */

#include <limits.h>
#include <stdlib.h>
#include <signal.h>
#include <errno.h>
#include <setjmp.h>

#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>

#define	QUOTE 0x80 /* 引用标志位,限制了字符集为 7 位 ASCII */

/* 语法树节点字段定义 */
#define	DTYP 0 /* type: 节点类型 */
#define	DLEF 1 /* left: 左子节点或输入重定向文件描述符 */
#define	DRIT 2 /* right: 右子节点或输出重定向文件描述符 */
#define	DFLG 3 /* flag: 标志位,属性文法意义上的属性 */
#define	DSPR 4 /* space: 在简单命令的时候是一个空位,
                * parentheses: 在复合命令时指向子语法树 */
#define	DCOM 5 /* command: 命令和参数的字列表 */

/* 类型定义,用于 DTYP */
#define	TCOM 1 /* command: 简单命令 */
#define	TPAR 2 /* parentheses: 复合命令 */
#define	TFIL 3 /* filter: 过滤器 */
#define	TLST 4 /* list: 命令列表 */

/* 标志定义,用于 DFLG */
#define	FAND 0x01 /* and: & 后台执行 */
#define	FCAT 0x02 /* catenate: >> 添加方式的输出重定向 */
#define	FPIN 0x04 /* pipe in: 命令从管道获得输入 */
#define	FPOU 0x08 /* pipe out: 命令向管道发送输出 */
#define	FPAR 0x10 /* parentheses: 复合命令中最后一个命令 */
#define	FINT 0x20 /* interrupt: 忽略中断信号 */
#define	FPRS 0x40 /* print string: 打印子进程 pid */

char	*dolp = NULL; /* dollar p: 指向命令行参数的指针 */
char	**dolv; /* dollar v: 命令行参数列表 */
int 	dolc; /* dollar c: 命令行参数个数 */
char	pidp[11]; /* 保存 sh 自身进程 pid 的字符串 */
char	*promp; /* 提示符 */
char	*linep; /* 操纵行缓冲区的指针 */
char	*elinep;
char	**argp; /* 操纵字列表存储空间的指针 */
char	**eargp;
int 	*treep; /* 操纵语法树节点存储空间的指针 */
int 	*treeend;
int 	peekc = 0; /* 预读字符 */
int 	gflg = 0; /* 全局标志,两种用途:缓冲区溢出,或包含通配符 */
int 	error; /* 语法分析发现错误标志 */
int 	setintr = 0; /* 设置中断信号忽略的标志 */
char	*arginp = NULL; /* 指针指向包含要执行的命令的参数,执行之后退出 */
int 	onelflg = 0; /* one line flag: 读取一行命令执行并退出标志 */
uid_t	uid; /* sh 进程的真实 uid */
jmp_buf	jmpbuf; /* 跨函数跳转用来保存当前状态的缓冲区 */
#define	SIGFLG 0x80 /* 异常终止标志位 */
int 	exitstat = 0; /* 执行命令的终止状态 */
char	exitp[4]; /* 保存终止状态的字符串 */
char	*arg0; /* 本 shell 的绝对路径名 */

#define	LINSIZ 1000
#define	ARGSIZ 50
#define	TRESIZ ARGSIZ*2
char	line[LINSIZ]; /* 行缓冲区 */
char	*args[ARGSIZ]; /* 字列表存储空间 */
int 	trebuf[TRESIZ]; /* 语法树节点的存储空间 */


/* 诊断消息 */
char *mesg[] = {
	NULL,
	"Hangup", /* SIGHUP 1 */
	NULL, /* SIGINT 2 */
	"Quit", /* SIGQUIT 3 */
	"Illegal instruction", /* SIGILL 4 */
	"Trace trap", /* SIGTRAP 5 */
	"Abort", /* SIGABRT 6 */
	"Signal 7", /* SIGEMT 7 */
	"Floating point exception", /* SIGFPE 8 */
	"Killed", /* SIGKILL 9 */
	"Signal 10", /* SIGBUS 10 */	
	"Segmentation violation", /* SIGSEGV 11 */
	"Bad argument to system call", /* SIGSYS 12 */
	NULL, /* SIGPIPE 13 */
	"Alarm clock", /* SIGALRM 14 */
	"Software termination signal from kill", /* SIGTERM 15 */
	"Signal 16", /* 16 */
	"Child process terminated or stopped", /* SIGSTOP 17 */
};

void lexscan(void);
int  *parse(void);
int  *command_list(char **p1, char **p2);
int  *pipeline(char **p1, char **p2);
int  *command(char **p1, char **p2);
void execute(int *t, int *pf1, int *pf2);
int  redirect(int *t);
int  execcmd(int *t);
int  builtin(int *t);
int  texec(char *f, int *at);
void word(void);
int  *tree(int n);
void scan(int *at, int (*f)());
int  tglob(int c);
int  trim(int c);
int  getC(void);
int  readc(void);
void err(char *s);
void prs(char *as);
void putC(int c);
void prn(int n);
void sprn(int n, char *s);
int  any(int c, char *as);
int  equal(char *as1, char *as2);
int  pwait(pid_t i);
char *shpath(char* s);

int main(int argc, char **argv)
{
	register int f;	/* 文件描述符 */
	int *t; /* 语法树 */ 

	/* 关闭所有打开的文件 */
	for(f=STDERR_FILENO; f<sysconf(_SC_OPEN_MAX); f++)
		close(f);
	/* 复制标准输出到标准错误输出 */
	if((f=dup(STDOUT_FILENO)) != STDERR_FILENO)
		close(f);
	/* 获得进程标识(32位整数),并转换成字符串,临时借用 dolc 变量 */
	dolc = (int)getpid();
	sprn(dolc, pidp);

	/* 判断当前时候是否根用户,设置正确的提示符 */
	if((uid = getuid()) == 0) /* 根用户 */
		promp = "# ";
	else
		promp = "% ";
	setuid(uid);
	setgid(getgid());

	/* 取得 shell 的绝对路径名 */
	if (argv[0][0] == '/')
		arg0 = argv[0];
	else 
		arg0 = shpath(argv[0]);

	if(argc > 1) {	/* 有参数或选项 */
		promp = 0;	/* 设置为非交互模式 */
		if (*argv[1]=='-') { /* 有选项 */
			setintr++; /* 需要设置信号处理程序 */
			if (argv[1][1]=='c' && argc>2) /* -c 把下一个参数作为命令执行 */
				arginp = argv[2];
			else if (argv[1][1]=='t') /* -t: 从标准输入读入一行执行并退出 */
				onelflg = 2;
		} else { /* 有命令文件 */
			/* 在标准输入上打开包含要执行的命令的文件 */
			close(STDIN_FILENO);
			f = open(argv[1], O_RDONLY);
			if(f < 0) { /* 打不开指定文件 */
				prs(argv[1]);
				err(": cannot open");
			}
		}
	}
	if(setintr) {
		/* 设置中断信号处理程序为忽略信号 */
		signal(SIGQUIT, SIG_IGN);
		signal(SIGINT, SIG_IGN);
	}
	dolv = argv+1; /* 参数列表指针右移 */
	dolc = argc-1; /* 参数数目减少 */

	/* 主循环: 扫描、分析和执行命令行 */
	for(;;) {
		error = 0;
		gflg = 0;
		if(promp != 0)	/* 交互模式运行 */
			prs(promp);	
		lexscan();
		if(gflg != 0) {/* 发生命令行字符溢出 */
			err("Command line overflow");
			continue;
		}
		t = parse();
		if(error != 0) {/* 语法分析发现错误 */
			err("syntax error");
			continue;
		}
		execute(t,NULL,NULL);
	}
	return 0;
}

/* 词法扫描 */
void lexscan(void)
{
	register char *cp;
	register int c;
	/* 初始化行缓冲区、字列表空间和相关指针 */
	argp = args;
	eargp = args+ARGSIZ-5;
	linep = line;
	elinep = line+LINSIZ-5;

	/* 过滤掉注释行 */
	do c = getC();
	while (c == ' ' || c == '\t');
	if (c == '#')
		while ((c = getC()) != '\n');
	peekc = (char) c; /* 送回最后的换行符 */
	
	/* 把命令行扫描到字列表中 */
	do {
		cp = linep; /* cp 指向当前要读入的字在行缓冲区中的位置 */
		word(); /* 读入一个字到行缓冲区中 */
	} while(*cp != '\n'); /* 循环直到读到换行符 */
}

/* 语法分析 */
int * parse(void)
{
	/* 初始化语法树节点空间和相关指针 */
	treep = trebuf;
	treeend = &trebuf[TRESIZ];

	setjmp(jmpbuf);
	if (error) /* 分析出语法错误或没有空间分配语法树节点的了 */
		return NULL;
	/* args 指向是字列表的第一个元素,
	 * argp 指向是字列表的最后一个元素后面的一个元素 */
	return  command_list(args, argp);
}

/*
 * 从输入中读出一个字到行缓冲区中,并增加一个字列表元素
 */
void word(void)
{
	register int c, c1;
	
	/* 字列表的当前元素指针指向当前要读入的字在行缓冲区中的位置 */
	*argp++ = linep;

	/* 忽略字前空白 */
	do c = getC();
	while (c == ' ' || c == '\t');
	/* 处理 shell 的元字符和换行符 */
	if(any(c, ";&<>()|\n")) {	 
		*linep++ = (char) c; /* 把这个元字符或换行符写到行缓冲区中 */
		*linep++ = '\0'; /* 终结这个字符串 */
		return;	
	}
	/* 读到普通字符 */
	peekc = c; /* 送回这个普通字符 */
	for(;;) {
		c = getC();
		if(any(c, " \t;&<>()|\n")) { /* 读到空白、元字符或换行符 */
			peekc = (char) c; /* 送回这个字符 */
			*linep++ = '\0'; /* 终结这个字符串 */
			return;
		}
		if(c == '\'' || c == '"') {/* 读到单双引号 */
			c1 = c;
			while((c=readc()) != c1) {
				if(c == '\n') {
					error++;	/* 引用没有在本行完结 */
					peekc = (char) c; /* 送回这个字符 */
					return;
				}
				/* 对引号包围的字符设置引用标志位,并写到行缓冲区中 */
				*linep++ = (char) c|QUOTE;
			}
			continue;
		}
		*linep++ = (char) c; /* 把这个普通字符写入行缓冲区 */
	}
}

/* 分配指定大小的树结点 */
int * tree(int n)
{
	int *t;

	t = treep;
	treep += n;
	if (treep>treeend) { /* 语法树节点空间不够 */
		prs("Command line overflow\n");
		error++;
		longjmp(jmpbuf,1);
	}
	return t;
}
/* 从输入中读出一个字符 */
int getC(void)
{
	int c;

	if(peekc) { /* 已经预读了一个字符 */
		c = peekc;
		peekc = 0;
		return c;
	}
	if(argp > eargp) { /* 字列表空间溢出 */
		argp -= 10;
		while((c=getC()) != '\n'); /* 忽略多出来的所有字符 */
		argp += 10;
		err("Too many args");
		gflg++;
		return c;
	}
	if(linep > elinep) {  /* 行缓冲区空间溢出 */
		linep -= 10;
		while((c=getC()) != '\n'); /* 忽略多出来的所有字符 */
		linep += 10;
		err("Too many characters");
		gflg++;
		return c;
	}
	if(dolp == NULL) { /* 当前未处理 $ */
		c = readc();
		if(c == '\\') { /* 转义符 */
			c = readc();
			if(c == '\n') /* 行接续 */
				return ' ';
			return(c|QUOTE); /* 引用这个字符 */
		}
		if(c == '$') { /* 变量替换 */
			c = readc();
			if(c>='0' && c<='9') { /* 位置参数 */
				if(c-'0' < dolc)
					dolp = dolv[c-'0'];
			}
			if(c == '$') { /* 进程标识 */
				dolp = pidp;
			}
			if(c == '?') { /* 退出状态 */
				sprn(exitstat, exitp);
				dolp = exitp;
			}
		}
	}
	if (dolp != NULL) { /* 当前处理 $ */
		c = *dolp++;
		if(c != '\0')
			return c;
		dolp = NULL;
	}
	return c&~QUOTE; /* 清除引用位 */
}

int readc(void)
{
	char cc;
	int c;

	if (arginp) { /* 有 -c 选项,onelflg == 0 */
		/* 从 arginp 中读取字符 */
		if ((c = (int)*arginp++) == 0) { /* 没有要作为命令的参数 */
			arginp = NULL; 
			onelflg++; /* 设置下次执行本函数的时候退出 */
			c = '\n';
		}
		return c;
	}
	if (onelflg==1)
		exit(0);
	if(read(STDIN_FILENO, &cc, 1) != 1) /* 读一个字符 */
		exit(0);
	/* 有 -t 选项,onelflg == 2,从标准输入读入了换行符 */
	if (cc=='\n' && onelflg)
		onelflg--; /* 设置下次执行本函数的时候退出 */
	return (int)cc;
}

/* 
 *	command_list:
 *		empty
 *		pipeline
 *		pipeline & command_list
 *		pipeline ; command_list
 */
int * command_list(char **p1, char **p2)
{
	register char **p;
	int *t, *t1;
	int l;

	/* 忽略前导的列表分隔符 */
	while(p1 != p2 && any(**p1, ";&\n"))
		p1++;

	if (p1 == p2)
		return NULL; /* 空命令 */

	l = 0; /* 嵌套层数 */
	for(p=p1; p!=p2; p++)
		switch(**p) {

		case '(':
			l++;
			break;

		case ')':
			l--;
			if(l < 0)
				error++;
			break;

		/* 找到第一个列表分隔符 */
		case '&':
		case ';':
		case '\n':
			if(l == 0) {
				l = **p;
				t = tree(4);
				t[DTYP] = TLST; /* 类型是命令列表 */
				t[DLEF] = (int)pipeline(p1, p); 
				t[DFLG] = 0;  /* 命令列表节点不设置任何标志位 */
				if(l == '&') { /* 需要后台处理 */
					t1 = (int *)t[DLEF];
					/* 设置左子节点标志 */
					t1[DFLG] |= FAND|FPRS|FINT;	
					/* 
					 * 后台运行的这些标志位只设置到在管道线节点上,
					 * 所以在执行的时候需要通过继承来下传到各个命令节点上。
					 */
				}
				t[DRIT] = (int)command_list(p+1, p2); 
				return t;
			}
		}
	/* 没有找到列表分隔符,发生在命令来自参数的时候 */
	if(l == 0)
		return pipeline(p1, p2);
	error++;
	return NULL;
}

/* 
 *	pipeline:
 *		command
 *		command | pipeline
 */
int * pipeline(char **p1, char **p2)
{
	register char **p;
	int l, *t;

	l = 0; /* 嵌套层数 */
	for(p=p1; p!=p2; p++)
		switch(**p) {

		case '(':
			l++;
			break;

		case ')':
			l--;
			break;

		/* 找到第一个管道符号 */
		case '|':
			if(l == 0) {
				t = tree(4);
				t[DTYP] = TFIL;	/* 类型是管道线 */
				t[DLEF] = (int)command(p1, p);
				t[DRIT] = (int)pipeline(p+1, p2);
				t[DFLG] = 0; /* 标志位由上级的 command_list 设置 */
				return t;
			}
		}
	/* 没有找到管道符号,是管道线末端或简单命令 */
	return command(p1, p2);
}

/*
 *	command:
 *		( command_list ) [ < in  ] [ > out ]
 *		word word* [ < in ] [ > out ]
 */
int * command(char **p1, char **p2)
{
	register char **p;
	char **lp = NULL, **rp = NULL;
	int *t;
	int n = 0, l = 0, i = 0, o = 0, c, flg = 0;

	if(**p2 == ')')	/* 这个命令是在圆括号包围中的命令列表的最后一个命令 */
		flg |= FPAR; 

	for(p=p1; p!=p2; p++)
		switch(c = **p) {

		case '(':
			if(l == 0) {
				if(lp != NULL)
					error++;
				lp = p+1; /* 最外层圆括号内列表的第一个字 */
			}
			l++;
			break;

		case ')':
			l--;
			if(l == 0)
				rp = p; /* 最外层圆括号内的最后一个字后面的')' */
			break;

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -