6.12. Простой интерпретатор команд.
Данный раздел просто приводит исходный текст простого интерпретатора команд.
Функция match описана в главе "Текстовая обработка".
/* Примитивный интерпретатор команд. Распознает построчно
* команды вида: CMD ARG1 ... ARGn <FILE >FILE >>FILE >&FILE >>&FILE
* Сборка: cc -U42 -DCWDONLY sh.c match.c pwd.c -o sh
*/
#include <sys/types.h>/* определение типов, используемых системой */
#include <stdio.h> /* описание библиотеки ввода/вывода */
#include <signal.h> /* описание сигналов */
#include <fcntl.h> /* определение O_RDONLY */
#include <errno.h> /* коды системных ошибок */
#include <ctype.h> /* макросы для работы с символами */
#include <dirent.h> /* эмуляция файловой системы BSD 4.2 */
#include <pwd.h> /* работа с /etc/passwd */
#include <sys/wait.h> /* описание формата wait() */
char cmd[256]; /* буфер для считывания команды */
#define MAXARGS 256 /* макс. количество аргументов */
char *arg[MAXARGS]; /* аргументы команды */
char *fin, *fout; /* имена для перенаправления ввода/вывода */
int rout; /* флаги перенаправления вывода */
char *firstfound; /* имя найденной, но невыполняемой программы */
#define LIM ':' /* разделитель имен каталогов в path */
extern char *malloc(), *getenv(), *strcpy(), *getwd();
extern char *strchr(), *execat();
extern void callshell(), printenv(), setenv(), dowait(), setcwd();
extern struct passwd *getpwuid();
/* Предопределенные переменные */
extern char **environ; /* окружение: изначально смотрит на тот же
* массив, что и ev из main() */
extern int errno; /* код ошибки системного вызова */
char *strdup(s)char *s;
{ char *p; return(p=malloc(strlen(s)+1), strcpy(p,s)); }
/* strcpy() возвращает свой первый аргумент */
char *str3spl(s, p, q) char *s, *p, *q;
{ char *n = malloc(strlen(s)+strlen(p)+strlen(q)+1);
strcpy(n, s); strcat(n, p); strcat(n, q); return n;
}
int cmps(s1, s2) char **s1, **s2;
{ return strcmp(*s1, *s2); }
/* Перенаправить вывод */
#define APPEND 0x01
#define ERRTOO 0x02
int output (name, append, err_too, created) char *name; int *created;
{
int fd;
*created = 0; /* Создан ли файл ? */
if( append ){ /* >>file */
/* Файл name существует? Пробуем открыть на запись */
if((fd = open (name, O_WRONLY)) < 0) {
if (errno == ENOENT) /* Файл еще не существовал */
goto CREATE;
else
return 0; /* Не имеем права писать в этот файл */
}
/* иначе fd == открытый файл, *created == 0 */
}else{
CREATE: /* Пытаемся создать (либо опустошить) файл "name" */
if((fd = creat (name, 0666)) < 0 )
return 0; /* Не могу создать файл */
else *created = 1; /* Был создан новый файл */
}
if (append)
lseek (fd, 0l, 2); /* на конец файла */
/* перенаправить стандартный вывод */
dup2(fd, 1);
if( err_too ) dup2(fd, 2); /* err_too=1 для >& */
close(fd); return 1;
}
/* Перенаправить ввод */
int input (name) char *name;
{
int fd;
if((fd = open (name, O_RDONLY)) < 0 ) return 0;/* Не могу читать */
/* перенаправить стандартный ввод */
dup2(fd, 0); close(fd); return 1;
}
/* запуск команды */
int cmdExec(progr, av, envp, inp, outp, outflg)
char *progr; /* имя программы */
char **av; /* список аргументов */
char **envp; /* окружение */
char *inp, *outp; /* файлы ввода-вывода (перенаправления) */
int outflg; /* режимы перенаправления вывода */
{
void (*del)(), (*quit)();
int pid;
int cr = 0;
del = signal(SIGINT, SIG_IGN); quit = signal(SIGQUIT, SIG_IGN);
if( ! (pid = fork())){ /* ветвление */
/* порожденный процесс (сын) */
signal(SIGINT, SIG_DFL); /* восстановить реакции */
signal(SIGQUIT,SIG_DFL); /* по умолчанию */
/* getpid() выдает номер (идентификатор) данного процесса */
printf( "Процесс pid=%d запущен\n", pid = getpid());
/* Перенаправить ввод-вывод */
if( inp ) if(!input( inp )){
fprintf(stderr, "Не могу <%s\n", inp ); goto Err;
}
if( outp )
if(!output (outp, outflg & APPEND, outflg & ERRTOO, &cr)){
fprintf(stderr, "Не могу >%s\n", outp ); goto Err;
}
/* Заменить программу: при успехе
* данная программа завершается, а вместо нее вызывается
* функция main(ac, av, envp) программы, хранящейся в файле progr.
* ac вычисляет система.
*/
execvpe(progr, av, envp);
Err:
/* при неудаче печатаем причину и завершаем порожденный процесс */
perror(firstfound ? firstfound: progr);
/* Мы не делаем free(firstfound),firstfound = NULL
* потому что данный процесс завершается (и тем ВСЯ его
* память освобождается) :
*/
if( cr && outp ) /* был создан новый файл */
unlink(outp); /* но теперь он нам не нужен */
exit(errno);
}
/* процесс - отец */
/* Сейчас сигналы игнорируются, wait не может быть оборван
* прерыванием с клавиатуры */
dowait(); /* ожидать окончания сына */
/* восстановить реакции на сигналы от клавиатуры */
signal(SIGINT, del); signal(SIGQUIT, quit);
return pid; /* вернуть идентификатор сына */
}
/* Запуск программы с поиском по переменной среды PATH */
int execvpe(progr, av, envp) char *progr, **av, **envp;
{
char *path, *cp;
int try = 1;
register eacces = 0;
char fullpath[256]; /* полное имя программы */
firstfound = NULL;
if((path = getenv("PATH")) == NULL )
path = ".:/bin:/usr/bin:/etc";
/* имя: короткое или путь уже задан ? */
cp = strchr(progr, '/') ? "" : path;
do{ /* пробуем разные варианты */
cp = execat(cp, progr, fullpath);
retry:
fprintf(stderr, "пробуем \"%s\"\n", fullpath );
execve(fullpath, av, envp);
/* если программа запустилась, то на этом месте данный
* процесс заменился новой программой. Иначе - ошибка. */
switch( errno ){ /* какова причина неудачи ? */
case ENOEXEC: /* это командный файл */
callshell(fullpath, av, envp);
return (-1);
case ETXTBSY: /* файл записывается */
if( ++try > 5 ) return (-1);
sleep(try); goto retry;
case EACCES: /* не имеете права */
if(firstfound == NULL)
firstfound = strdup(fullpath);
eacces++; break;
case ENOMEM: /* программа не лезет в память */
case E2BIG:
return (-1);
}
}while( cp );
if( eacces ) errno = EACCES;
return (-1);
}
/* Склейка очередной компоненты path и имени программы name */
static char *execat(path, name, buf)
register char *path, *name;
char *buf; /* где будет результат */
{
register char *s = buf;
while(*path && *path != LIM )
*s++ = *path++; /* имя каталога */
if( s != buf ) *s++ = '/';
while( *name )
*s++ = *name++; /* имя программы */
*s = '\0';
return ( *path ? ++path /* пропустив LIM */ : NULL );
}
/* Запуск командного файла при помощи вызова интерпретатора */
void callshell(progr, av, envp) char *progr, **av, **envp;
{
register i; char *sh; char *newav[MAXARGS+2];
int fd; char first = 0;
if((fd = open(progr, O_RDONLY)) < 0 )
sh = "/bin/sh";
else{
read(fd, &first, 1); close(fd);
sh = (first == '#') ? "/bin/csh" : "/bin/sh";
}
newav[0] = "Shellscript"; newav[1] = progr;
for(i=1; av[i]; i++)
newav[i+1] = av[i];
newav[i+1] = NULL;
printf( "Вызываем %s\n", sh );
execve(sh, newav, envp);
}
/* Ожидать окончания всех процессов, выдать причины смерти. */
void dowait(){
int ws; int pid;
while((pid = wait( &ws)) > 0 ){
if( WIFEXITED(ws)){
printf( "Процесс %d умер с кодом %d\n",
pid, WEXITSTATUS(ws));
}else if( WIFSIGNALED(ws)){
printf( "Процесс %d убит сигналом %d\n",
pid, WTERMSIG(ws));
if(WCOREDUMP(ws)) printf( "Образовался core\n" );
/* core - образ памяти процесса для отладчика adb */
}else if( WIFSTOPPED(ws)){
printf( "Процесс %d остановлен сигналом %d\n",
pid, WSTOPSIG(ws));
}
}
}
/* Расширение шаблонов имен. Это упрощенная версия, которая
* расширяет имена только в текущем каталоге.
*/
void glob(dir, args, indx, str /* что расширять */, quote )
char *args[], *dir; int *indx; char *str;
char quote; /* кавычки, в которые заключена строка str */
{
static char globchars[] = "*?[";
char *p; char **start = &args[ *indx ];
short nglobbed = 0;
register struct dirent *dirbuf;
DIR *fd; extern DIR *opendir();
/* Затычка для отмены глоббинга: */
if( *str == '\\' ){ str++; goto noGlob; }
/* Обработка переменных $NAME */
if( *str == '$' && quote != '\'' ){
char *s = getenv(str+1);
if( s ) str = s;
}
/* Анализ: требуется ли глоббинг */
if( quote ) goto noGlob;
for( p=str; *p; p++ ) /* Есть ли символы шаблона? */
if( strchr(globchars, *p))
goto doGlobbing;
noGlob:
args[ (*indx)++ ] = strdup(str);
return;
doGlobbing:
if((fd = opendir (dir)) == NULL){
fprintf(stderr, "Can't read %s\n", dir); return;
}
while ((dirbuf = readdir (fd)) != NULL ) {
if (dirbuf->d_ino == 0) continue;
if (strcmp (dirbuf->d_name, ".") == 0 ||
strcmp (dirbuf->d_name, "..") == 0) continue;
if( match( dirbuf->d_name, str)){
args[ (*indx)++ ] = strdup(dirbuf->d_name);
nglobbed++;
}
}
closedir(fd);
if( !nglobbed){
printf( "%s: no match\n", str);
goto noGlob;
}else{ /* отсортировать */
qsort(start, nglobbed, sizeof (char *), cmps);
}
}
/* Разбор командной строки */
int parse(s) register char *s;
{
int i; register char *p;
char tmp[80]; /* очередной аргумент */
char c;
/* очистка старых аргументов */
for(i=0; arg[i]; i++) free(arg[i]), arg[i] = NULL;
if( fin ) free(fin ), fin = NULL;
if( fout ) free(fout), fout = NULL;
rout = 0;
/* разбор строки */
for( i=0 ;; ){
char quote = '\0';
/* пропуск пробелов - разделителей слов */
while((c = *s) && isspace(c)) s++;
if( !c ) break;
/* очередное слово */
p = tmp;
if(*s == '\'' || *s == '"' ){
/* аргумент в кавычках */
quote = *s++; /* символ кавычки */
while((c = *s) != '\0' && c != quote){
if( c == '\\' ){ /* заэкранировано */
c = *++s;
if( !c ) break;
}
*p++ = c; ++s;
}
if(c == '\0')
fprintf(stderr, "Нет закрывающей кавычки %c\n", quote);
else s++; /* проигнорировать кавычку на конце */
} else
while((c = *s) && !isspace(c)){
if(c == '\\') /* заэкранировано */
if( !(c = *++s))
break /* while */;
*p++ = c; s++;
}
*p = '\0';
/* Проверить, не есть ли это перенаправление
* ввода/вывода. В отличие от sh и csh
* здесь надо писать >ФАЙЛ <ФАЙЛ
* >< вплотную к имени файла.
*/
p = tmp; /* очередное слово */
if( *p == '>'){ /* перенаправлен вывод */
p++;
if( fout ) free(fout), rout = 0; /* уже было */
if( *p == '>' ){ rout |= APPEND; p++; }
if( *p == '&' ){ rout |= ERRTOO; p++; }
if( !*p ){
fprintf(stderr, "Нет имени для >\n");
fout = NULL; rout = 0;
} else fout = strdup(p);
} else if( *p == '<' ){ /* перенаправлен ввод */
p++;
if( fin ) free(fin); /* уже было */
if( !*p ){
fprintf(stderr, "Нет имени для <\n");
fin = NULL;
} else fin = strdup(p);
} else /* добавить имена к аргументам */
glob( ".", arg, &i, p, quote );
}
arg[i] = NULL; return i;
}
/* Установить имя пользователя */
void setuser(){
int uid = getuid(); /* номер пользователя, запустившего Шелл */
char *user = "mr. Nobody"; /* имя пользователя */
char *home = "/tmp"; /* его домашний каталог */
struct passwd *pp = getpwuid( uid );
if( pp != NULL ){
if(pp->pw_name && *pp->pw_name ) user = pp->pw_name;
if( *pp->pw_dir ) home = pp->pw_dir;
}
setenv("USER", user); setenv("HOME", home);
}
void setcwd(){ /* Установить имя текущего каталога */
char cwd[512];
getwd(cwd); setenv( "CWD", cwd );
}
void main(ac, av, ev) char *av[], *ev[]; {
int argc; /* количество аргументов */
char *prompt; /* приглашение */
setuser(); setcwd();
signal(SIGINT, SIG_IGN);
setbuf(stdout, NULL); /* отменить буферизацию */
for(;;){
prompt = getenv( "prompt" ); /* setenv prompt -->\ */
printf( prompt ? prompt : "@ ");/* приглашение */
if( gets(cmd) == NULL /* at EOF */ ) exit(0);
argc = parse(cmd);
if( !argc) continue;
if( !strcmp(arg[0], "exit" )) exit(0);
if( !strcmp(arg[0], "cd" )){
char *d = (argc==1) ? getenv("HOME"):arg[1];
if(chdir(d) < 0)
printf( "Не могу войти в %s\n", d );
else setcwd();
continue;
}
if( !strcmp(arg[0], "echo" )){
register i; FILE *fp;
if( fout ){
if((fp = fopen(fout, rout & APPEND ? "a":"w"))
== NULL) continue;
} else fp = stdout;
for(i=1; i < argc; i++ )
fprintf( fp, "%s%s", arg[i], i == argc-1 ? "\n":" ");
if( fp != stdout ) fclose(fp);
continue;
}
if( !strcmp(arg[0], "setenv" )){
if( argc == 1 ) printenv();
else if( argc == 2 ) setenv( arg[1], "" );
else setenv( arg[1], arg[2]);
continue;
}
cmdExec(arg[0], (char **) arg, environ, fin, fout, rout);
}
}
/* -----------------------------------------------------------*/
/* Отсортировать и напечатать окружение */
void printenv(){
char *e[40]; register i = 0; char *p, **q = e;
do{
p = e[i] = environ[i]; i++;
} while( p );
#ifdef SORT
qsort( e, --i /* сколько */, sizeof(char *), cmps);
#endif
while( *q )
printf( "%s\n", *q++ );
}
/* Сравнение имени переменной окружения с name */
static char *envcmp(name, evstr) char *name, *evstr;
{
char *p; int code;
if((p = strchr(evstr, '=')) == NULL ) return NULL; /* error ! */
*p = '\0'; /* временно */
code = strcmp(name, evstr);
*p = '='; /* восстановили */
return code==0 ? p+1 : NULL;
}
/* Установить переменную окружения */
void setenv( name, value ) char *name, *value;
{
static malloced = 0; /* 1, если environ перемещен */
char *s, **p, **newenv;
int len, change_at = (-1), i;
/* Есть ли переменная name в environ-е ? */
for(p = environ; *p; p++ )
if(s = envcmp(name, *p)){ /* уже есть */
if((len = strlen(s)) >= strlen(value)){
/* достаточно места */
strcpy(s, value); return;
}
/* Если это новый environ ... */
if( malloced ){
free( *p ); *p = str3spl(name, "=", value);
return;
}
/* иначе создаем копию environ-а */
change_at = p - environ; /* индекс */
break;
}
/* Создаем копию environ-а. Если change_at == (-1), то
* резервируем новую ячейку для еще не определенной переменной */
for(p=environ, len=0; *p; p++, len++ );
/* вычислили количество переменных */
if( change_at < 0 ) len++;
if((newenv = (char **) malloc( sizeof(char *) * (len+1)))
== (char **) NULL) return;
for(i=0; i < len+1; i++ ) newenv[i] = NULL; /* зачистка */
/* Копируем старый environ в новый */
if( !malloced ) /* исходный environ в стеке (дан системой) */
for(i=0; environ[i]; i++ ) newenv[i] = strdup(environ[i]);
else for(i=0; environ[i]; i++ ) newenv[i] = environ[i];
/* Во втором случае строки уже были спасены, копируем ссылки */
/* Изменяем, если надо: */
if( change_at >= 0 ){
free( newenv[change_at] );
newenv[change_at] = str3spl(name, "=", value);
} else { /* добавить в конец новую переменную */
newenv[len-1] = str3spl(name, "=", value);
}
/* подменить environ */
if( malloced ) free( environ );
environ = newenv; malloced++;
qsort( environ, len, sizeof(char *), cmps);
}
/* Допишите команды:
unsetenv имя_переменной - удаляет переменную среды;
exit N - завершает интерпретатор с
кодом возврата N (это целое число);
*/
© Copyright А. Богатырев, 1992-95
Си в UNIX
Назад | Содержание | Вперед