在如何構建一個Linux Shell(一)中,我們構建了一個簡單的Linux shell,該shell打印提示字符串,讀取輸入,然后將輸入回顯到屏幕上。現在這不是很令人印象深刻,不是嗎?在如何構建一個Linux Shell(二)中,我們將更新代碼,以使Shell能夠解析和執行簡單命令。首先讓我們看一下什么是簡單的命令。
什么是簡單命令?
一個簡單的命令 由單詞列表組成,這些單詞列表由空格字符(空格,制表符,換行符)分隔。第一個單詞是命令名,并且是必需的(否則,shell將沒有解析和執行命令!)。第二個和后續單詞是可選的。如果存在,它們形成的論點,我們希望shell傳遞到執行的命令。
例如,以下命令: ls -l 由兩個詞組成: ls (命令名稱),以及 -l(第一個也是唯一的參數)。同樣,命令:gcc -o shell main.c prompt.c(在第一部分中,我們用它來編譯我們的shell)由5個詞組成:一個命令名和一個4個參數的列表。
為了能夠執行簡單的命令,我們的外殼程序需要執行以下步驟:
掃描輸入,一次輸入一個字符,以查找下一個標記。我們稱此過程為詞法掃描,而執行此任務的外殼部分稱為詞法掃描器,或簡稱為掃描器。
提取輸入令牌。我們稱這種標記化輸入。
解析標記并創建抽象語法樹或AST。Shell負責執行此操作的部分稱為解析器。
執行AST。這是執行者的工作。
下圖顯示了Shell為了解析和執行命令而執行的步驟。您可以看到圖中包含的步驟比上面列表中顯示的步驟更多,這很好。隨著外殼的增長和變得越來越復雜,我們將在需要時添加其他步驟。
現在,讓我們詳細查看上述四個步驟,并查看在shell中實現這些功能所需的代碼。
掃描輸入
為了獲得下一個令牌,我們需要能夠一次掃描一個字符的輸入,以便我們可以識別可以作為令牌一部分的字符和作為定界符的字符。甲分隔符是一個標記的令牌(以及可能的另一令牌的開始)的端部。通常,分隔符是空格字符(空格,制表符,換行符),但也可以包含其他字符,例如; 和 &。
通常,掃描輸入意味著我們應該能夠:
.從輸入中檢索下一個字符。
.返回我們讀回的最后一個字符作為輸入。
.前瞻(或窺視)以檢查下一個字符,而無需實際檢索它。
.跳過空白字符。
我們將在一分鐘內定義執行所有這些任務的功能。但是首先,讓我們談談抽象輸入。
記住 read_cmd()函數,這是我們在本教程的第一部分中定義的?那就是我們用來讀取用戶輸入并將其作為malloc的字符串。我們可以將此字符串直接傳遞給我們的掃描儀,但這會使掃描過程有點麻煩。特別是,掃描器很難記住它給我們的最后一個字符,以便它可以越過該字符并給我們后面的字符。
為了簡化掃描儀的工作,我們通過將輸入字符串作為 struct source_s 結構,我們將在源文件中定義 source.h。繼續在源目錄中創建該文件,然后在您喜歡的文本編輯器中將其打開并添加以下代碼:
#ifndef SOURCE_H
#define SOURCE_H
#define EOF (-1)
#define ERRCHAR ( 0)
#define INIT_SRC_POS (-2)
struct source_s
{
char *buffer; /* the input text */
long bufsize; /* size of the input text */
long curpos; /* absolute char position in source */
};
char next_char(struct source_s *src);
void unget_char(struct source_s *src);
char peek_char(struct source_s *src);
void skip_white_spaces(struct source_s *src);
#endif
關注結構的定義,您可以看到 struct source_s 除了兩個以外,還包含指向輸入字符串的指針 long 包含有關字符串長度和我們當前在字符串中的位置(將從中獲取下一個字符)的信息的字段。
現在創建另一個名為 source.c,您應在其中添加以下代碼:
#include
#include "shell.h"
#include "source.h"
void unget_char(struct source_s *src)
{
if(src->curpos < 0)
{
return;
}
src->curpos--;
}
char next_char(struct source_s *src)
{
if(!src || !src->buffer)
{
errno = ENODATA;
return ERRCHAR;
}
char c1 = 0;
if(src->curpos == INIT_SRC_POS)
{
src->curpos = -1;
}
else
{
c1 = src->buffer[src->curpos];
}
if(++src->curpos >= src->bufsize)
{
src->curpos = src->bufsize;
return EOF;
}
return src->buffer[src->curpos];
}
char peek_char(struct source_s *src)
{
if(!src || !src->buffer)
{
errno = ENODATA;
return ERRCHAR;
}
long pos = src->curpos;
if(pos == INIT_SRC_POS)
{
pos++;
}
pos++;
if(pos >= src->bufsize)
{
return EOF;
}
return src->buffer[pos];
}
void skip_white_spaces(struct source_s *src)
{
char c;
if(!src || !src->buffer)
{
return;
}
while(((c = peek_char(src)) != EOF) && (c == ' ' || c == ' '))
{
next_char(src);
}
}
的 unget_char()函數將(我們從輸入中檢索到的)最后一個字符返回(或取消保護)到輸入源。它只是通過操縱源結構的指針來做到這一點。在本系列后面的部分中,您將看到此功能的好處。
的 next_char() 函數返回輸入的下一個字符并更新源指針,以便下一次調用 next_char()返回以下輸入字符。當我們到達輸入中的最后一個字符時,該函數將返回特殊字符EOF,我們在其中將其定義為-1 source.h 以上。
的 peek_char() 功能類似于 next_char()它返回輸入的下一個字符。唯一的區別是peek_char() 不會更新源指針,因此下一次調用 next_char()返回我們剛剛偷看的相同輸入字符。在本系列的后面部分,您將看到輸入偷看的好處。
最后, skip_white_spaces()函數將跳過所有空格字符。這將在完成讀取令牌后為我們提供幫助,并且在讀取下一個令牌之前希望跳過定界符空白。
標記輸入
現在我們已經有了掃描儀的功能,我們將使用這些功能來提取輸入令牌。我們將首先定義一個新結構,該結構將用于表示令牌。
繼續創建一個名為 scanner.h 在您的源目錄中,然后將其打開并添加以下代碼:
#ifndef SCANNER_H
#define SCANNER_H
struct token_s
{
struct source_s *src; /* source of input */
int text_len; /* length of token text */
char *text; /* token text */
};
/* the special EOF token, which indicates the end of input */
extern struct token_s eof_token;
struct token_s *tokenize(struct source_s *src);
void free_token(struct token_s *tok);
#endif
專注于結構定義, struct token_s 包含一個指向 struct source_s保留了我們的投入。該結構還包含一個指向令牌文本的指針,以及一個告訴我們該文本長度的字段(這樣我們就無需重復調用strlen() 在令牌的文本上)。
接下來,我們將編寫 tokenize()函數,它將從輸入中檢索下一個標記。我們還將編寫一些幫助程序功能,以幫助我們使用輸入令牌。
在源目錄中,創建一個名為 scanner.c,然后輸入以下代碼:
#include
#include
#include
#include
#include "shell.h"
#include "scanner.h"
#include "source.h"
char *tok_buf = NULL;
int tok_bufsize = 0;
int tok_bufindex = -1;
/* special token to indicate end of input */
struct token_s eof_token =
{
.text_len = 0,
};
void add_to_buf(char c)
{
tok_buf[tok_bufindex++] = c;
if(tok_bufindex >= tok_bufsize)
{
char *tmp = realloc(tok_buf, tok_bufsize*2);
if(!tmp)
{
errno = ENOMEM;
return;
}
tok_buf = tmp;
tok_bufsize *= 2;
}
}
struct token_s *create_token(char *str)
{
struct token_s *tok = malloc(sizeof(struct token_s));
if(!tok)
{
return NULL;
}
memset(tok, 0, sizeof(struct token_s));
tok->text_len = strlen(str);
char *nstr = malloc(tok->text_len+1);
if(!nstr)
{
free(tok);
return NULL;
}
strcpy(nstr, str);
tok->text = nstr;
return tok;
}
void free_token(struct token_s *tok)
{
if(tok->text)
{
free(tok->text);
}
free(tok);
}
struct token_s *tokenize(struct source_s *src)
{
int endloop = 0;
if(!src || !src->buffer || !src->bufsize)
{
errno = ENODATA;
return &eof_token;
}
if(!tok_buf)
{
tok_bufsize = 1024;
tok_buf = malloc(tok_bufsize);
if(!tok_buf)
{
errno = ENOMEM;
return &eof_token;
}
}
tok_bufindex = 0;
tok_buf[0] = ' 主站蜘蛛池模板: 无码人妻丰满熟妇啪啪网站 | 337p日本欧洲亚洲大胆艺术96 | 又大又黄又硬视频 | 久久久久人妻一区精品色欧美 | 人妻阿敏被老外玩弄系列 | 欧美一区二区三区视频在线观看 | 日本乱偷人妻中文字幕 | 中国人与黑人牲交FREE欧美 | 慈禧一级淫片免费放特级 | 亚洲精品网站在线观看你懂的 | 国产精品IGAO视频网 | 一级毛片一级毛片 | 久久夜色精品国产亚洲av | 天堂素人约啪 | 黄色片子免费观看 | 女人被躁到高潮嗷嗷叫游戏 | 亚洲精品国产一区二区三区在线观看 | 多人运动免费观看不用登录 | 女人被强╳到高潮喷水在线观看 | 9 9久热RE在线精品视频 | 亚洲成AV人在线观看网站 | 亚洲avav国产av综合av | 日韩精品无码人妻免费视频 | 男吃奶玩乳尖高潮视频 | 高清freesexmovies性tv出水 | 国产露脸无码A区久久 | 国产精品黑色丝袜在线观看 | 大地资源在线观看官网第三页 | 欧美性猛交XXXX乱大交 | 漂亮人妻洗澡被公强啪啪 | 人人澡人人澡人人 | 日本丰满老妇BBW | 一夲道av无码无卡免费 | 国产山东熟女48嗷嗷叫 | 无码中文av波多野结衣 | 亚洲阿V天堂网2019无码 | 免费看片成人 | 青青久在线视频免费观看 | 国产产区一二三产区区别在线 | 亚洲中文无码线在线观看 | 美女扒开下面让男生桶白浆 |