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

Назад | Содержание | Вперед