Unix/Linux 的 shell

Unix中一個可執行程序是一個機器指令及其數據序列。進程是程序運行時的內存空間和設置。

shell即“殼”,是一個解釋器。 一般作用是:

(1)運行程序

(2)管理輸入輸出

(3)可編程

一個shell的主循環執行下面的4步:

(1)用戶鍵入可執行文件名

(2)shell建立一個新的進程來運行這個程序

(3)shell將程序從磁盤載入

(4)程序在進程中運行直到結束

第一部分:爲了要寫一最簡單的shell,需要學會

(1)運行一個程序

(2)建立一個進程 fock()

(3)等待exit()

父進程如何等待子進程退出呢? 調用wait(&status)。這個過程是這樣的,一個進程創建一個子進程,自己相應的爲父進程。父進程調用wait後,父進程掛起直到子進程結束,子進程結束會調用exit(n) (n是0~255) 。然後喚醒父進程同時wait得到返回狀態。

wait()函數

wait(int *statuspir) 參數statuspir是子進程的運行結果。 返回值 -1遇到錯誤;pid 結束進程的id。假如參數statuspir不爲NULL,wait將退出狀態或者信號序列存儲到參數所指的整數中。

第二部分:shell腳本

vi time.sh

編輯腳本

#! /bin/sh

#date server

echo urrnet date is

date

echo my name is

whoami

其中#! /bin/sh 是說此腳本用/bin/sh解釋。#後面是註釋,但#!是特殊符號,表示它後面的是解釋此腳本的shell的路徑。echo 是輸出

執行此腳本用

 sh time.sh

腳本除了命令外還包括:
變量,用戶輸入,控制,環境。

我們的代碼實現解釋命令,變量,if-then 控制,環境。
以下是代碼

文件smsh4.c

/**
* smsh4.c    small-shell version 4
* small shell that supportscommand line parsing
* and if ..then .. else .fi logic(by calling process() )
*
* build: gcc -o smsh4 smsh4.c splitline.c execute.c process.c\
* controflow.c builtin.c varlib.c
*/
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<signal.h>
#include<sys/wait.h>
#include "smsh.h"

#define  DFL_PROMPT  "> "

void setup(void);


int main(void)
{
    char *cmdline, *prompt, **arglist;
    int  result;

    prompt = DFL_PROMPT;
    setup();

    while((cmdline = next_cmd(prompt, stdin)) != NULL)  
    {
        if((arglist = splitline(cmdline)) != NULL)
        {
            result = process(arglist);
            freelist(arglist);
        }
        free(cmdline);
    }

    return 0;
}


/**
* purpose: initialize shell
* returns: nothing. calls fatal() if trouble
*/
void setup(void)
{
    extern char **environ;

    VLenviron2table(environ);
    signal(SIGINT, SIG_IGN);
    signal(SIGQUIT, SIG_IGN);
}

void fatal(char *s1, char *s2, int n)
{
    fprintf(stderr, "Error: %s, %s\n", s1, s2);
    exit(n);
}

文件smsh.h

#ifndef _SMSH_H
#define _SMSH_H

#define   YES   1
#define   NO    0

char *next_cmd(char *prompt, FILE *fp);
char **splitline(char *line);
void freelist(char **list);
void *emalloc(size_t n);
void *erealloc(void *p, size_t n);
int  execute(char **argv);
void fatal(char *s1, char *s2, int n);
int  ok_to_execute(void);

int builtin_command(char **args, int *resultp);

#endif

文件splitline.c

/**
* splitline.c
* command reading and parsing functions for smsh
*
* char *next_cmd(char *prompt, FILE *fp); - get next command
* char **splitline(char *str); - parse a string
*/

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include "smsh.h"


/**
* purpose: read next command line fp
* return : dynamically allocated string holding command line
* errors : NULL at EOF (not really an error) 
*       call fatal from emalloc()
* notes: allocates space in BUFSIZ chunks
*/
char *next_cmd(char *prompt, FILE *fp)
{
    char *buf;         // the buffer
    int  bufspace = 0; // total size
    int  pos = 0;      // current position
    int  c;            // input char

    printf("%s", prompt);
    while((c = getc(fp)) != EOF)
    {
        /* need space? */
        if(pos + 1 >= bufspace)   // 1 for \0
        {
            if(bufspace == 0) // y: 1st time 
            {
                buf = emalloc(BUFSIZ);
            }
            else
            {
                buf = erealloc(buf, bufspace + BUFSIZ);
            }   
            bufspace += BUFSIZ;      // update size
        }

        if( c == '\n')  // end of command?
        {
            break;
        }   

        buf[pos++] = c; // no, add to buffer
    }

    if(c == EOF && pos == 0) // EOF and no input
    {
        return NULL; 
    }
    buf[pos] = '\0';
    return buf;
}

#define is_delim(x) ((x) == ' '|| (x) == '\t')

/**
* purpose: split a line into array of white-space tokens
* returns: a NULL-terminated array of pointers to copies of
*          the tokens or NULL if line if no tokens in the line
* action : traverse the array , locate strings, make copies
* note   : strtok() could work, but we may want to add quotes later
*/
char **splitline(char *line)
{
    char *newstr();
    char **args;
    int  spots = 0;    // spots in table
    int  bufspace = 0; // bytes in table
    int  argnum   = 0; // slots used
    char *cp = line;   // pos in string
    char *start;
    int  len;

    if(line == NULL)   // handle spacial case
    {
        return NULL; 
    }

    args = emalloc(BUFSIZ);  // initialize array
    bufspace = BUFSIZ;
    spots = BUFSIZ / sizeof(char *);

    while(*cp != '\0')
    {
        while(is_delim(*cp))   // skip leading spaces
        {
            cp++;
        }
        if(*cp == '\0')        // quit at end-o-string
        {
            break;
        }

        // make sure the array has room(+1 for NULL)
        if(argnum + 1 >= spots) 
        {
            args = erealloc(args, bufspace + BUFSIZ);
            bufspace += BUFSIZ;
            spots += (BUFSIZ / sizeof(char *));
        }
        // mark start, then find end of word
        start = cp;
        len = 1;
        while(* ++cp != '\0' && ! is_delim(*cp) )
        {
            len++;
        }
        args[argnum++] = newstr(start, len);
    }
    args[argnum] = NULL;
    return args;    
}

/**
* purpose: constructor for strings
* returns:  a string, nuver NULL
*/
char *newstr(char *s, int len)
{
    char *rv = emalloc(len + 1);

    rv[len] = '\0';
    strncpy(rv, s, len);
    return rv;
}


/**
* purpose: free the list returned by spitline
* returns: nothing
* action: free all strings in list and then free the list 
*/
void freelist(char **list)
{
    char **cp = list;
    while(*cp)
    {
        free(*cp++);
    }
    free(list);
}

void *emalloc(size_t n)
{
    void *rv;
    if( (rv = malloc(n)) == NULL)
    {
        fatal("out of memory", "", 1);
    }
    return rv;
}

void *erealloc(void *p, size_t n)
{
    void *rv;
    if( (rv = realloc(p, n)) == NULL)
    {
        fatal("realloc() failed", "", 1);
    }
    return rv;
}

文件execute.c

/**
* execute.c
* code used by small shell to execute commands
*/

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<signal.h>
#include<sys/wait.h>
#include "varlib.h"


/**
* purpose: run a program passing it arguments
* return: status returned via wait, or -1 on error
* errors: -1 on fork() or wait() errors
*/
int execute(char **argv)
{
    extern char **environ;
    int pid;
    int child_info = -1;

    if(argv[0] == NULL)  // nothing succeeds
    {
        return 0;
    }

    if((pid = fork()) == -1)
    {
        perror("fork");
    }
    else if(pid == 0)
    {
        environ = VLtable2environ();  // new line
        signal(SIGINT, SIG_DFL);
        signal(SIGQUIT, SIG_DFL);
        execvp(argv[0], argv);
        perror("cannot execute command");
        exit(1);
    }
    else
    {
        if(wait(&child_info) == -1)
        {
            perror("wait");
        }
    }

    return child_info;  
}

文件process.c

/**
* process.c
* command processing layer
*
* The process(char **arglist) function is called by the main loop
* It sits in front of the execute() function.The layer handles
* two main classes of processing:
* a) built -in functions (e.g exit(), set, = , read, ..)
* b) control structures(e.g. if, while, for)
*/

#include<stdio.h>
#include "smsh.h"

int is_control_command(char *s);
int do_control_command(char **args);
int ok_to_execute(void);

/**
* purpose: process user command
* returns: result of processing command
* details: if a built - in then call appropriate function, 
*          if not, execute()
* errors: arise from subroutines, handled there 
*/
int process(char **args)
{
    int rv = 0;

    if(args[0] == NULL)
    {
        rv = 0;
    }
    else if( is_control_command(args[0]) )
    {
        rv = do_control_command(args);
    }
    else if(ok_to_execute() )
    {
        if( ! builtin_command(args, &rv))
        {
            rv = execute(args);
        }
    }
    return rv;
}
/**
* "if" processing is done with two state variables
* if_state and if_result
*/

#include<stdio.h>
#include "smsh.h"

enum states{NEUTRAL, WANT_THEN, THEN_BLOCK};
enum results{SUCCESS, FALL};

static int if_state = NEUTRAL;
static int if_result = SUCCESS;
static int last_stat = 0;

int syn_err(char *msg);


/**
* purpose: determine the shell should execute a command
* returns: 1 for yes, 0 for no
* details: if in THEN_BLOCK and if_result was SUCCESS the yes
*          if in THEN_BLOCK and if_result was FALL then no
*          if in WANT_THEN then syntax error(sh is different)
*/
int ok_to_execute(void)
{
    int rv = 1;  // default is positive

    if(if_state == WANT_THEN)
    {
        syn_err("then expected");
        rv = 0;
    }
    else if(if_state == THEN_BLOCK && if_result == SUCCESS)
    {
        rv = 1;
    }
    else if(if_state == THEN_BLOCK && if_result == FALL)
    {
        rv = 0;
    }
    return rv;
}

/**
* purpose: boolean to report if the command is a shell control command
* returns: 0 or 1;
*/
int is_control_command(char *s)
{
    return (strcmp(s, "if") == 0 || strcmp(s, "then") == 0 
        || strcmp(s, "fi") == 0);
}

/**
* purpose: Process "if", "then", "fi" - change state or detect error
* returns: 0 if ok, -1 for syntax error
*/
int do_control_command(char **args)
{
    char *cmd = args[0];
    int  rv = -1;

    if(strcmp(cmd, "if") == 0)
    {
        if(if_state != NEUTRAL)
        {
            rv = syn_err("if unexpected");
        }
        else
        {
            last_stat = process(args + 1);
            if_result = (last_stat == 0) ? SUCCESS : FALL;
            if_state  = WANT_THEN;
            rv = 0;
        }
    }
    else if(strcmp(cmd, "then") == 0)
    {
        if(if_state != WANT_THEN)
        {
            rv = syn_err("then unexpeccted");
        }
        else
        {
            if_state = THEN_BLOCK;
            rv = 0;
        }
    }
    else if(strcmp(cmd, "fi") == 0)
    {
        if(if_state != THEN_BLOCK)
        {
            rv = syn_err("fi unexpected");
        }
        else
        {
            if_state = NEUTRAL;
            rv = 0;
        }
    }
    else
    {
        fatal("internal error processing:", cmd, 2);
    }
    return rv;
}

/**
* purpose: handles syntax errors in control structures
* details: resets state to NEUTRAL
* returns: -1 in interactive. Should call fatal in scripts
*/

int syn_err(char *msg)
{
    if_state = NEUTRAL;
    fprintf(stderr, "syntax error: %s\n", msg);
    return -1;
}

文件builtin.c

/**
* builtin.c
* contains the switch and the function for builtin command
*/

#include<stdio.h>
#include<string.h>
#include<ctype.h>
#include"smsh.h"
#include"varlib.h"

int assign(char *);
int akname(char *);


/**
* purpose: run a bulitin command
* returns: 1 if args[0] is builtin, 0 if not
* details: test args[0] agsinst all known built ins.
* Call functions
*/
int builtin_command(char **args, int *resultp)
{
    int rv = 0;

    if(strcmp(args[0], "set") == 0)
    {
        VLlist();
        *resultp = 0;
        rv = 1;
    }
    else if(strchr(args[0], '=') != NULL)
    {
        *resultp = assign(args[0]);
        if(*resultp != -1)
        {
            rv = 1;
        }
    }
    else if(strcmp(args[0], "export") == 0)
    {
        if(args[1] != NULL && okname(args[1]))
        {
            *resultp = VLexport(args[1]);
        }
        else
        {
            *resultp = 1;
        }
        rv = 1;
    }
    return rv;
}

/**
* purpose: execute name = val AND ensure that name is legal
* returns: -1 for illegal lval, or result of VLstore
* waring: modifies the string, but retores it to normal
*/
int assign(char *str)
{
    char *cp;
    int rv;

    cp = strchr(str, '=');
    *cp = '\0';
    rv = okname(str) ? VLstore(str, cp + 1) : -1;
    *cp = '=';
    return rv;
}

/**
* purpose: determines if a string is a legal variable name
* returns: 0 for no, 1 for yes
*/
int okname(char *str)
{
    char *cp;

    for(cp = str; *cp != '\0'; cp++)
    {
        if( (isdigit(*cp) && cp == str) 
            || !(isalnum(*cp)) || *cp == '_')
        {
            return 0;
        }
    }
    return (cp != str);  // no empty strings, either
}

文件varlib.c

/**
* varlib.c
* a simple storage to store name = value paris
* with facility to mark items as part of the environment
*
* interface:
* VLstore(name, value)   returns 1 for ok, 0 for no
* VLlookup(name)         returns string or NULL not there
* VLlist()               prints out current table
*
* environment-related function
* VLexport(name)        adds name to list of env vars
* VLtable2environ()     copy from table to environ
* VLenviron2table()     copy from environ to table
*
*/


#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include"varlib.h"




static struct var tab[MAXVARS];

/* private methods */
static char *new_string(char * name, char *val);
static struct var *find_item(char *name, int first_blank);

/**
* traverse list, if found, repalce it, else add at end
* since there is no delete, a blank one is a free one
* return 1 if trouble, 0 if ok (like a command)
*/
int VLstore(char *name, char *val)
{
    struct var *itemp;
    char  *s;
    int   rv = 1;

    /* find spot to put it  and make new string */
    if( (itemp = find_item(name, 1)) != NULL &&
        (s = new_string(name, val)) != NULL)
    {
        if(itemp->str)  // bas a val?
        {
            free(itemp->str);  // y: remove it
        }
        itemp->str = s;
        rv = 0;    // ok
    }
    return rv;
}

/**
* returns new string of form name = value or NULL on error
*/
static char *new_string(char *name, char *val)
{
    char *retval;

    retval = (char *)malloc(strlen(name) + strlen(val) + 2);
    if(retval != NULL)
    {
        sprintf(retval, "%s=%s", name, val);
    }
    return retval;
}

/**
* returns value of var or empty string if not there
*/
char *VLlookup(char *name)
{
    struct var *itemp;

    if( (itemp = find_item(name, 0)) != NULL )
    {
        return (itemp->str + 1 + strlen(name) ); 
    }
    return "";
}

/**
* marks a var for export, adds it if not there
* returns 1 for no, 0 for ok
*/
int VLexport(char *name)
{
    struct var *itemp;
    int rv = 1;

    if( (itemp = find_item(name, 0)) != NULL)   
    {
        itemp->global = 1;
        rv = 0;
    }
    else if(VLstore(name, "") == 1)
    {
        rv = VLexport(name);
    }
    return rv;
}

/**
* searches table for an item
* returns ptr to struct or NULL if not found
* 
*/
static struct var *find_item(char *name, int first_blank)
{
    int i;
    int len = strlen(name);
    char *s;

    for(i = 0; i < MAXVARS && tab[i].str != NULL; ++i)
    {
        s = tab[i].str;
        if(strncmp(s, name, len) == 0 && s[len] == '=')
        {
            return &tab[i];
        }
    }
    if(i < MAXVARS && first_blank)
    {
        return &tab[i];
    }
    return NULL;
}


/**
* performs the shell's set command
* Lists the contens of the varible table, marking each
* exported variable with the symble '*'
*/
void VLlist(void)
{
    int i;
    for(i = 0; i < MAXVARS && tab[i].str != NULL; ++i)
    {
        if(tab[i].global)
        {
            printf("* %s\n", tab[i].str);
        }
        else
        {
            printf(" %s\n", tab[i].str);
        }
    }
}



/**
* initialize the variable table by loading array of strings
* return 1 for ok, 0 for not ok
*/
int VLenviron2table(char *env[])
{
    int i;
    char *newstring;

    for(i = 0; env[i] != NULL; ++i)
    {
        if(i == MAXVARS)
        {
            return 0;
        }
        newstring = (char *)malloc(1 + strlen(env[i]));
        if(newstring == NULL)
        {
            return 0;
        }


        strcpy(newstring, env[i]);
        tab[i].str = newstring;
        tab[i].global = 1;

    }

    while(i < MAXVARS)           // we don't need this
    {
        tab[i].str = NULL;   // static globals are nummed
        tab[i++].global = 0; // by default
    }
    return 1;
}

/**
* build an array of pointers suitable for making a new environment note,
* you need to free() this when done to avoid memory leaks
*/
char **VLtable2environ(void)
{
    int    i;   
    int    j; 
    int    n; 
    char   **envtab;    // array of pointers

    /* first, count the number of globle variables */
    for(i = 0; i < MAXVARS && tab[i].str != NULL; ++i)
    {
        if(tab[i].global == 1)
        {
            n++;
        }
    }

    /* then, allocate space for that many variable */
    envtab = (char **)malloc( (n + 1) * sizeof(char *) );
    if(envtab == NULL)
    {
        return NULL;
    }

    /* then, load the array with pointers */
    for(i = 0, j = 0; i < MAXVARS && tab[i].str != NULL; ++i)
    {
        if(tab[i].global == 1)
        {
            envtab[j++] = tab[i].str;
        }
    }
    envtab[j] = NULL;
    return envtab;
}

文件varlib.h

/**
* varlib.h
*/
#ifndef _VARLIB_H
#define _VARLIB_H


#define  MAXVARS  200  // a linked list would be nicer

struct var
{
    char *str;     // name = val string
    int  global;   // a boolean
};


int VLstore(char *name, char *val);
char *VLlookup(char *name);
int  VLexport(char *name);
void VLlist(void);
int  VLenviron2table(char **env);
char **VLtable2environ(void);

#endif

好了我們編譯下

gcc -o smsh4 smsh4.c splitline.c execute.c process.c controflow.c builtin.c varlib.c

參考Unix/Linux編程實踐教程

down

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章