📄 shell.c
字号:
/*
* 这个 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 + -