这是有关如何构建Linux Shell的教程的第四部分。 您可以通过以下链接阅读本教程的前面部分:第一部分 , 第二 部分,第三部分 。

注意 :您可以从此GitHub存储库下载第IV部分的完整源代码。

第四部分简介

在这一部分中,我们将向我们的外壳添加符号表 。 符号表是一种数据结构, 编译器和解释 器使用该结构将变量存储为表中的条目。 每个条目都包含一个键(变量的名称 )和一个关联的值(变量的 )。 键通常是唯一的,也就是说,我们不能有两个共享相同键的条目(即,不能有两个共享相同变量名的变量)。

通常,Linux Shell在启动时会填充其符号表。 填充符号表后,编译器或解释器可以轻松地在表中搜索变量以检索该变量的值。 我们还可以执行类型检查 ,执行作用域规则 (例如,使变量仅对声明其的函数可见),并将shell变量导出到外部命令。

为了填充符号表,外壳程序读取环境变量列表,该环境变量列表从其父进程(通常是登录用户的进程或登录进程的子​​进程)传递到外壳程序。 Shell将每个变量(及其值)添加到符号表中。 然后,我们可以使用适当的内置实用程序随意编辑,删除或导出shell变量(我们将在本系列的稍后部分中讨论)。

为什么我们需要符号表?

简而言之,符号表使我们能够定义外壳变量,修改它们的值,在执行变量扩展时使用不同外壳变量的值以及将变量导出到外部命令。 在本系列后面的内容中,当我们讨论位置和特殊外壳参数时,符号表也将变得很方便。

每当您要求外壳程序回显导出或未设置外壳程序变量的值时,您实际上就是在要求外壳程序访问和/或修改其符号表。 所有外壳程序都有某种符号表实现,尽管某些外壳程序可能具有不同的名称。

例如,假设您调用了以下命令:

echo $PATH

哪个应该给你类似的输出:

/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

您可能知道, echo命令与您在屏幕上看到的输出无关,只是echo将路径打印出来了。 这究竟是谁理解,外壳 $PATH代表一个shell变量名。

也是外壳程序用实际路径值替换了$PATH单词,然后将其传递给echoecho命令仅回显外壳程序传递的参数,这是您看到的在屏幕上显示的可执行路径。

因此,为了能够定义,修改,取消设置和导出Shell变量,我们首先需要实现符号表。 让我们看看接下来如何做。

实施符号表

有多种方法可以实现符号表,常见的方法是链表 , 哈希表和二进制搜索树 。 每种方法都有优点和缺点,我们没有时间或空间来详细讨论每种方法。 为了我们的目的,我们将使用链表,链表是最容易实现的,并且在访问速度和内存使用方面都相当不错。

注:如果你想使用比学习任何其他的外壳,你应该考虑改变符号表执行到使用哈希表或二进制树可以找到哈希表实现的例子。 这里 )。

现在,让我们来破解该代码。 在您的源目录中,创建一个名为symtab的子目录(从终端仿真器调用mkdir symtab )。 导航到该目录( cd symtab )并创建一个名为symtab.h的文件。 将以下代码添加到刚创建的头文件中:

# ifndef SYMTAB_H
# define SYMTAB_H# include "../node.h"# define MAX_SYMTAB 256/* the type of a symbol table entry's value */
enum symbol_type_e
{SYM_STR ,SYM_FUNC,
};/* the symbol table entry structure */
struct symtab_entry_s
{char     *name;enum      symbol_type_e val_type;char     *val;unsigned  int flags;struct    symtab_entry_s * next ;struct    node_s * func_body ;
};/* the symbol table structure */
struct symtab_s
{int    level;struct symtab_entry_s * first , * last ;
};/* values for the flags field of struct symtab_entry_s */
# define FLAG_EXPORT (1 << 0) /* export entry to forked commands *//* the symbol table stack structure */
struct symtab_stack_s
{int    symtab_count;struct symtab_s * symtab_list [ MAX_SYMTAB ];struct symtab_s * global_symtab , * local_symtab ;
};struct symtab_s       * new_symtab ( int level) ;
struct symtab_s       * symtab_stack_push ( void ) ;
struct symtab_s       * symtab_stack_pop ( void ) ;
int rem_from_symtab (struct symtab_entry_s *entry, struct symtab_s *symtab) ;
struct symtab_entry_s * add_to_symtab ( char *symbol) ;
struct symtab_entry_s * do_lookup ( char *str, struct symtab_s *symtable) ;
struct symtab_entry_s * get_symtab_entry ( char *str) ;
struct symtab_s       * get_local_symtab ( void ) ;
struct symtab_s       * get_global_symtab ( void ) ;
struct symtab_stack_s * get_symtab_stack ( void ) ;
void init_symtab ( void ) ;
void dump_local_symtab ( void ) ;
void free_symtab (struct symtab_s *symtab) ;
void symtab_entry_setval (struct symtab_entry_s *entry, char *val) ; # endif

symbol_type_e枚举定义了我们的符号表条目的类型。 我们将使用SYM_STR类型来表示shell变量,并使用SYM_FUNC来表示函数(在本系列的后面部分中,我们将处理shell函数)。

struct symtab_entry_s结构代表我们的符号表条目。 该结构包含以下字段:

  • name =>此条目表示的shell变量(或函数)的名称。
  • val_type => SYM_STR用于shell变量, SYM_FUNC用于shell函数。
  • val =>字符串值(仅适用于shell变量)。
  • flags =>表示我们将分配给变量和函数的不同属性,例如export和readonly标志(我们将在本系列的后面部分处理这些标志)。
  • next =>指向下一个符号表条目的指针(因为我们将表实现为单链接列表)。
  • func_body =>用于表示外壳函数,函数主体的抽象语法树AST (我们在本教程的第一部分中讨论了AST)。

struct symtab_s结构表示单个符号表。 首先,我们将使用一个符号表,在其中定义所有的shell变量。 稍后,当我们讨论外壳函数并开始使用脚本文件时,我们将需要定义更多的符号表。

第零个符号表将是全局表 ,在其中我们将定义全局变量(shell可以访问的全局变量,以及由它执行的所有函数和脚本)。

符号表中排名第一的符号表是本地表 ,我们将在其中定义我们的本地变量(这些变量只能由声明了它们的shell函数或脚本访问)。 通过以这种方式级联符号表,我们有效地实现了变量作用域 。

我们的struct symtab_s结构包含以下字段:

  • 对于全局符号表, level => 0,对于本地符号表, level为1或更高。
  • firstlast =>分别指向表的链表中的第一个和最后一个条目的指针。

现在,要能够如上所述层叠符号表,我们需要定义并实现符号表栈 。 堆栈Last-In-First-OutLIFO数据结构,其中添加(或压入 )的最后一项是删除(或弹出 )的第一项。 struct symtab_stack_s结构表示我们的符号表堆栈。 该结构包含以下字段:

  • symtab_count =>当前在堆栈中的符号表的数量。
  • symtab_list =>指向堆栈符号表的指针数组。 第零项指向全局符号表,而symtab_count-1项指向最后一个(或本地)符号表。 堆栈最多可以容纳MAX_SYMTAB项,我们在头文件的开头将其定义为256。
  • global_symtablocal_symtab =>分别指向全局和本地符号表的指针(为了易于访问)。

我们将在本课程的稍后部分实现堆栈。 现在,我们将从编写使用符号表所需的功能开始。

符号表功能

创建symtab.c文件(在symtab子目录中),然后添加以下代码开始:

# include <stdlib.h>
# include <stdio.h>
# include <string.h>
# include "../shell.h"
# include "../node.h"
# include "../parser.h"
# include "symtab.h"struct symtab_stack_s symtab_stack ;
int    symtab_level;void init_symtab ( void )
 {symtab_stack.symtab_count = 1 ;symtab_level = 0 ;struct symtab_s * global_symtab = malloc ( sizeof ( struct symtab_s ));if (!global_symtab){fprintf ( stderr , "fatal error: no memory for global symbol table\n" );exit (EXIT_FAILURE);}memset (global_symtab, 0 , sizeof (struct symtab_s));symtab_stack.global_symtab  = global_symtab;symtab_stack.local_symtab   = global_symtab;symtab_stack.symtab_list[ 0 ] = global_symtab;global_symtab->level        = 0 ;
}

首先,我们有两个全局变量:

  • symtab_stack =>指向符号表堆栈的指针(每个外壳仅需要一个堆栈)。
  • symtab_level =>当前堆栈中的级别(如果正在使用全局符号表,则为0,否则为非零)。

init_symtab()函数初始化符号表堆栈,然后为全局符号表分配内存并进行初始化。

接下来,添加以下功能:

struct symtab_s * new_symtab ( int level)
 {struct symtab_s * symtab = malloc ( sizeof ( struct symtab_s ));if (!symtab){fprintf ( stderr , "fatal error: no memory for new symbol table\n" );exit (EXIT_FAILURE);}memset (symtab, 0 , sizeof (struct symtab_s));symtab->level = level;return symtab;
}

每当我们要创建新的符号表时(例如,当我们要执行shell函数时),我们都会调用new_symtab()函数。

接下来,添加以下功能:

void free_symtab (struct symtab_s *symtab)
 {if (symtab == NULL ){return ;}struct symtab_entry_s * entry = symtab -> first ;while (entry){if (entry->name){free (entry->name);}if (entry->val){free (entry->val);}if (entry->func_body){free_node_tree(entry->func_body);}struct symtab_entry_s * next = entry -> next ;free (entry);entry = next;}free (symtab);
}

处理free_symtab()符号表后,我们将调用free_symtab()函数,并希望释放符号表及其条目所使用的内存。

接下来,我们将定义一个调试功能:

void dump_local_symtab ( void )
 {struct symtab_s * symtab = symtab_stack . local_symtab ;int i = 0 ;int indent = symtab->level * 4 ;fprintf ( stderr , "%*sSymbol table [Level %d]:\r\n" , indent, " " , symtab->level);fprintf ( stderr , "%*s===========================\r\n" , indent, " " );fprintf ( stderr , "%*s  No               Symbol                    Val\r\n" , indent, " " );fprintf ( stderr , "%*s------ -------------------------------- ------------\r\n" , indent, " " );struct symtab_entry_s * entry = symtab -> first ;while (entry){fprintf ( stderr , "%*s[%04d] %-32s '%s'\r\n" , indent, " " ,i++, entry->name, entry->val);entry = entry->next;}fprintf ( stderr , "%*s------ -------------------------------- ------------\r\n" , indent, " " );
}

此功能打印本地符号表的内容。 当我们的外壳启动时,本地和全局符号表将引用同一表。 只有在Shell要运行Shell函数或脚本文件时,我们的本地表才与全局表不同。 (在本课程的后续部分,我们将编写一个内置实用程序,该实用程序将调用dump_local_symtab()来帮助我们可视化shell全局符号表的内容)。

现在,让我们定义一些函数来帮助我们处理符号表条目。 在同一文件( symtab.c )中,添加以下功能:

struct symtab_entry_s * add_to_symtab ( char *symbol)
 {if (!symbol || symbol[ 0 ] == '\0' ){return NULL ;}struct symtab_s * st = symtab_stack . local_symtab ;struct symtab_entry_s * entry = NULL ;if ((entry = do_lookup(symbol, st))){return entry;}entry = malloc ( sizeof (struct symtab_entry_s));if (!entry){fprintf ( stderr , "fatal error: no memory for new symbol table entry\n" );exit (EXIT_FAILURE);}memset (entry, 0 , sizeof (struct symtab_entry_s));entry->name = malloc ( strlen (symbol)+ 1 );if (!entry->name){fprintf ( stderr , "fatal error: no memory for new symbol table entry\n" );exit (EXIT_FAILURE);}strcpy (entry->name, symbol);if (!st->first){st->first      = entry;st->last       = entry;}else{st->last->next = entry;st->last       = entry;}return entry;
}

此功能将新条目添加到本地符号表。 请记住,在本课开始时,我曾说过每个条目必须具有唯一的键,这是我们为shell变量或函数指定的名称。 为了确保这种唯一性,我们首先通过调用do_lookup() (稍后将对其进行定义do_lookup()来检查是否存在具有给定名称的条目。

如果存在具有给定名称的条目,我们仅返回现有条目,而无需添加新条目。 否则,我们将添加条目,设置其名称,并调整符号表的指针。 最后,我们返回新添加的条目。

下一个函数执行相反的工作,即删除键与给定名称匹配的符号表条目:

int rem_from_symtab (struct symtab_entry_s *entry, struct symtab_s *symtab)
 {int res = 0 ;if (entry->val){free (entry->val);}if (entry->func_body){free_node_tree(entry->func_body);}free (entry->name);if (symtab->first == entry){symtab->first = symtab->first->next;if (symtab->last == entry){symtab->last = NULL ;}res = 1 ;}else{struct symtab_entry_s *e = symtab->first;struct symtab_entry_s * p = NULL ;while (e && e != entry){p = e;e = e->next;}if (e == entry){p->next = entry->next;res = 1 ;}}free (entry);return res;
}

此函数释放条目使用的内存,并调整链接列表指针以从符号表中删除条目。

要执行查找(即搜索具有给定名称的变量),我们需要在同一文件中定义以下函数:

struct symtab_entry_s * do_lookup ( char *str, struct symtab_s *symtable)
 {if (!str || !symtable){return NULL ;}struct symtab_entry_s * entry = symtable -> first ;while (entry){if ( strcmp (entry->name, str) == 0 ){return entry;}entry = entry->next;}return NULL ;
}

此函数从第一个条目开始搜索给定的符号表。 如果条目的键与我们要查找的变量名匹配,则该函数返回该条目。

否则,该函数将按照链接列表指针依次查看每个条目,直到找到键与所需名称匹配的条目为止。 如果找不到匹配项,则返回NULL

接下来,添加以下功能:

struct symtab_entry_s * get_symtab_entry ( char *str)
 {int i = symtab_stack.symtab_count -1 ;do{struct symtab_s * symtab = symtab_stack . symtab_list [ i ];struct symtab_entry_s * entry = do_lookup ( str , symtab );if (entry){return entry;}} while (--i >= 0 );return NULL ;
}

此函数搜索其键与给定名称匹配的符号表条目。 起初这似乎是多余的,因为我们已经定义了do_lookup()函数来搜索本地符号表。 此处的区别是get_symtab_entry()从本地符号表开始搜索整个堆栈。 目前,这种区别并不重要,因为我们的局部和全局符号表是指一个和同一表。

只有当我们谈论shell函数和脚本文件时,您才会欣赏到该函数的功能(请紧紧抓住它!)。

最后,添加以下功能:

void symtab_entry_setval (struct symtab_entry_s *entry, char *val)
 {if (entry->val){free (entry->val);}if (!val){entry->val = NULL ;}else{char *val2 = malloc ( strlen (val)+ 1 );if (val2){strcpy (val2, val);}else{fprintf ( stderr , "error: no memory for symbol table entry's value\n" );}entry->val = val2;}
}

此功能释放用于存储旧条目值的内存(如果存在)。 然后,它将创建新值的副本,并将其存储在符号表条目中。

符号表功能就是这样。 现在让我们编写一些函数来帮助我们处理符号表堆栈。

符号表堆栈功能

将以下代码添加到相同的源文件symtab.c

void symtab_stack_add (struct symtab_s *symtab)
 {symtab_stack.symtab_list[symtab_stack.symtab_count++] = symtab;symtab_stack.local_symtab = symtab;
}struct symtab_s * symtab_stack_push ( void )
 {struct symtab_s * st = new_symtab (++ symtab_level );symtab_stack_add(st);return st;
}struct symtab_s * symtab_stack_pop ( void )
 {if (symtab_stack.symtab_count == 0 ){return NULL ;}struct symtab_s * st = symtab_stack . symtab_list [ symtab_stack . symtab_count -1];symtab_stack.symtab_list[--symtab_stack.symtab_count] = NULL ;symtab_level--;if (symtab_stack.symtab_count == 0 ){symtab_stack.local_symtab  = NULL ;symtab_stack.global_symtab = NULL ;}else{symtab_stack.local_symtab = symtab_stack.symtab_list[symtab_stack.symtab_count -1 ];}return st;
}struct symtab_s * get_local_symtab ( void )
 {return symtab_stack.local_symtab;
}struct symtab_s * get_global_symtab ( void )
 {return symtab_stack.global_symtab;
}struct symtab_stack_s * get_symtab_stack ( void )
 {return &symtab_stack;
}

以下是上述功能的快速分解:

  • symtab_stack_add()将给定的符号表添加到堆栈中,并将新添加的表分配为本地符号表。
  • symtab_stack_push()创建一个新的空符号表,并将其压入堆栈顶部。
  • symtab_stack_pop()删除(或弹出)堆栈顶部的符号表,并根据需要调整堆栈指针。
  • get_local_symtab()get_global_symtab()返回指向本地和全局符号表的指针。
  • get_symtab_stack()返回指向符号表堆栈的指针。

初始化符号表栈

还记得本教程的开头我曾告诉过您,shell需要初始化其全局符号表并将变量从环境列表添加到表中吗? 好了,在这一部分中,我们将初始化符号表堆栈和全局符号表。 在本系列的下一部分中,我们将阅读环境变量列表并将其添加到符号表中。

继续,在您的源目录中创建一个名为initsh.c的源文件,并添加以下代码:

# include <string.h>
# include "shell.h"
# include "symtab/symtab.h"extern char **environ;void initsh ( void )
 {init_symtab();struct symtab_entry_s * entry ;char **p2 = environ;while (*p2){char *eq = strchr (*p2, '=' );if (eq){int len = eq-(*p2);char name[len+ 1 ];strncpy (name, *p2, len);name[len] = '\0' ;entry = add_to_symtab(name);if (entry){symtab_entry_setval(entry, eq+ 1 );entry->flags |= FLAG_EXPORT;}}else{entry = add_to_symtab(*p2);}p2++;}entry = add_to_symtab( "PS1" );symtab_entry_setval(entry, "$ " );entry = add_to_symtab( "PS2" );symtab_entry_setval(entry, "> " );
}

此函数初始化符号表堆栈(包括全局符号表)并扫描环境列表,并将每个环境变量(及其值)添加到表中。 最后,该函数添加了两个用于存储提示字符串的变量PS1PS2 (我们在第一部分中讨论了提示字符串)。 不要忘记将函数原型添加到shell.h头文件中:

void initsh ( void ) ;

接下来,我们需要在进入REPL循环之前从main()函数中调用此函数。 为此,将以下行添加到main() ,就在循环体之前:

initsh();

我们需要做的最后一件事是更新提示字符串打印功能,以便它们将使用我们刚添加到initsh()的全局符号表中的PS1PS2变量。 我们还将编写第一个内置实用程序dump

接下来,将这些更改应用于我们的代码。

更新提示打印功能

打开prompt.c文件。 删除print_prompt1()主体中的行,并将其替换为以下代码:

void print_prompt1 ( void )
 {   struct symtab_entry_s * entry = get_symtab_entry (" PS1 ");if (entry && entry->val){fprintf ( stderr , "%s" , entry->val);}else{fprintf ( stderr , "$ " );}
}

新代码检查是否存在名称为PS1的符号表条目。 如果存在,我们将使用该条目的值来打印第一个提示字符串。 否则,我们将使用默认的内置值$

print_prompt2()函数进行的代码更改是相似的,因此在此不再显示,但是您可以在此页面顶部提供的GitHub repo链接中进行检查。

不要忘记在文件顶部的#include "shell.h"行之后添加以下include指令:

# include "symtab/symtab.h"

定义我们的第一个内置实用程序

内置实用程序 (又称内置命令或内部命令)是一个命令,其代码被编译为Shell可执行文件本身的一部分,即Shell不需要执行外部程序 ,也不需要派生新进程即可执行命令。

实际上,我们每天使用的许多命令(例如cdechoexportreadonly都是内置实用程序。 您可以在此POSIX标准链接中了解有关shell内置实用程序的更多信息。

在本教程的过程中,我们将添加不同的内置实用程序来扩展我们的shell。 我们将首先定义一个结构,该结构将帮助我们存储有关不同内置实用程序的信息。

打开您的shell.h头文件,并在#endif指令之前的末尾添加以下代码:

/* shell builtin utilities */
int dump ( int argc, char **argv) ;/* struct for builtin utilities */
struct builtin_s
{char *name;    /* utility name */int (*func)( int argc, char **argv); /* function to call to execute the utility */
};/* the list of builtin utilities */
extern struct builtin_s builtins [];/* and their count */
extern int builtins_count;

函数原型声明了我们的第一个内置实用程序dumpstruct builtin_s结构定义了我们的内置实用程序,并具有以下字段:

  • name =>内置实用程序名称,我们将使用它来调用该实用程序。
  • func =>函数指针,指向在我们的Shell中实现内置实用程序的函数。

我们将使用builtins[]数组存储有关内置实用程序的信息。 该数组包含builtins_count个元素。

现在创建一个新的子目录,并将其命名为builtins 。 这是我们定义所有内置实用程序的地方。 接下来,创建文件builtins.c并向其中添加以下代码:

# include "../shell.h"struct builtin_s builtins [] =
{   { "dump"    , dump       },
};int builtins_count = sizeof (builtins)/ sizeof (struct builtin_s);

接下来,我们将添加一个名为dump的内置实用程序,该实用程序将用于转储或打印符号表的内容,因此我们知道幕后发生的事情(我主要是编写此实用程序的,因此我们的讨论不会听起来过于抽象和理论化)。

builtins子目录,创建文件dump.c和下面的代码添加到它:

# include "../shell.h"
# include "../symtab/symtab.h"int dump ( int argc, char **argv)
 {dump_local_symtab();return 0 ;
}

简单吧? 此函数实现了我们的dump内置实用程序,该实用程序将打印本地符号表的内容。

更新执行器

接下来,我们需要告诉执行者我们新的内置实用程序,以便当我们输入命令dump ,shell将执行我们的dump()函数,而不是搜索名称为dump的外部命令。

为此,我们需要修复do_simple_command()函数。

在源目录中,打开executor.c源文件,然后导航到do_simple_command()函数的定义。 现在找到两行:

argv[argc] =NULL ;
pid_t child_pid = 0 ;

在这两行之间插入新行,然后输入以下代码:

int i = 0 ;for ( ; i < builtins_count; i++){if ( strcmp (argv[ 0 ], builtins[i].name) == 0 ){builtins[i].func(argc, argv);free_argv(argc, argv);return 1 ;}}

现在,我们的外壳将检查要执行的命令的名称是否为内置实用程序的名称,如果是,它将调用实现内置实用程序的函数并返回。

就是这样! 现在让我们编译和测试我们的shell。

编译外壳

让我们编译一下shell。 打开您喜欢的终端模拟器,导航到源目录,并确保其中有14个文件和2个子目录:

现在,使用以下命令编译shell:

gcc -o shell executor.c initsh.c main.c node.c parser.c prompt.c scanner.c source.c builtins/builtins.c builtins/dump.c symtab/symtab.c

如果一切顺利, gcc将不会输出任何内容,并且在当前目录中应该有一个名为shell的可执行文件:

(如果您从GitHub repo下载了源文件,则只需从源目录运行make ,它将负责编译shell)。

现在,通过运行./shell调用Shell,并尝试使用我们新的内置实用程序dump

从输出中可以看到,我们的全局符号表(级别0)包含许多变量(在上面的示例中为67),它们对应于从父进程传递给我们的shell环境变量。 最后两个条目代表我们的PS1PS2变量。 尝试输入其他命令,看看我们的shell如何解析和执行简单命令。

下一步是什么

现在可能还不很明显,但是我们在这一部分所做的是巨大的成就。 我们的符号表将使我们能够在shell中做重要的事情,例如执行单词扩展,将变量导出到外部命令,定义shell函数等等。

在下一部分中,我们将讨论单词扩展过程,以及如何向shell添加单词扩展功能。

敬请关注!

先前发布在 https://medium.com/@mohammedisam2000/lets-build-a-linux-shell-part-iv-cefdd8f58138

翻译自: https://hackernoon.com/building-a-linux-shell-part-iv-h21o3uwl

构建Linux Shell [第四部分]相关推荐

  1. Linux Shell高级技巧(四)

    十九.将文件的输出格式化为指定的宽度: 在这个技巧中,不仅包含了如何获取和文件相关的详细信息,如行数,字符等,而且还可以让文件按照指定的宽度输出.这种应用在输出帮助信息.License相关信息时还是比 ...

  2. linux shell学习四

    Shell分支语句 case 值 in 模式1)command1command2command3;; 模式2)command1command2command3;; *)command1command2 ...

  3. Linux Shell高级技巧(目录)

    为了方便我们每个人的学习,这里将给出Linux Shell高级技巧五篇系列博客的目录以供大家在需要时参阅和查找. Linux Shell高级技巧(一)  http://www.cnblogs.com/ ...

  4. linux实验三shell程序设计,实验三 LINUX SHELL编程

    精选文库 -实验三LINUX SHELL 编程 四.实验内容 本实验包含两个具体的SHELL程序编写任务,较为全面地涉及了SHELL 程序的设计方法和技术.内容如下: 1.创建以下菜单程序: USER ...

  5. Linux shell之重定向

    文章目录 前言 一.输出重定向 二.输入重定向 三.重定向原理 总结 前言 我们有些时候想要将命令的输出保存起来,而不仅仅是让它输出到显示器上(终端上).将命令的输出保存再文件中后方便分析输出的内容. ...

  6. Linux Shell脚本入门教程系列之(十四) Shell Select教程

    本文是Linux Shell脚本系列教程的第(十四)篇,更多Linux Shell教程请看:Linux Shell脚本系列教程 在上一篇文章:Linux Shell系列教程之(十三)Shell分支语句 ...

  7. Linux Shell脚本入门教程系列之(四)Shell注释

    本文是Linux Shell脚本系列教程的第(四)篇,更多shell教程请看:Linux Shell脚本系列教程 与许多的编程语言一样,Shell中也有注释符号,继上一篇之后,今天就为大家来介绍下Sh ...

  8. 【Linux】Linux学习(四)Shell编程

    Linux学习(四)Shell编程 Shell概述 Shell脚本入门 变量 系统预定义变量 自定义变量 特殊变量 运算符 条件判断 流程控制(重点) if判断 case语句 for循环 while循 ...

  9. 手把手教你北邮操作系统小学期实验四——Linux Shell小型化1

    接上一章 手把手教你北邮操作系统小学期实验三--ARM/ucLinux-SkyEye的安装,配置与集成4 此实验是队友做的. 1. 实验目的: Shell 是一种 Linux 系统与用户的交互方式,是 ...

最新文章

  1. .NET支持的泛型约束
  2. 白话Elasticsearch72_利用HDFS备份与恢复ES生产集群的数据
  3. minio扩展现有的分布式集群:扩大集群规模,增加磁盘数量
  4. dotnet不是内部或外部的命令,也不是可运行的程序或批处理文件
  5. 分布式统一配置平台-Disconf.Net
  6. matlab 模糊pid mimo 对应,双关节机械手的模糊PID控制系统设计.doc
  7. 拦截器获取请求参数post_spring boot拦截器中获取request post请求中的参数
  8. JavaScript 误区
  9. 向量叉积分配律简单证明
  10. 电脑使用android手机摄像头,电脑怎么使用安卓手机摄像头 电脑使用手机摄像头的方法...
  11. 服务器虚拟化百科,硬件虚拟化 硬件虚拟化的意思解释|硬件虚拟化是什么意思 -我酷百科...
  12. 工人滹沱河剧集ujiuedgfwer
  13. 模型预测控制(MPC)解析(七):约束控制问题的表述
  14. mysql 历史数据迁移,MySQL 历史数据表迁移方法
  15. mysql中SQL语句查询表字段名、注释、字段类型
  16. 信息系统基础知识---信息系统工程
  17. 教师计算机基础知识考试,教师计算机基础知识考核方案.doc
  18. FastDB 高效率的内存数据库系统
  19. cassandra install troubleshooting
  20. 版本管理工具Subversion Edge的备份恢复与数据迁移方案

热门文章

  1. 矿泉水瓶勿重复使用易得癌病
  2. Android强行进阶:为何大厂APP如微信、支付宝、淘宝、手Q等只适配了armeabi-v7a/armeabi?
  3. 步进电机算法s曲线的原理与实现
  4. 每日工作记录——ERROR:Simulator:793 - Unable to elaborate instantiated module work
  5. android相机曝光度调节,手机摄影很难?这有份超全的安卓相机操作指南,专业模式一点就透...
  6. MS office二级错题记录【3】
  7. es拼音分词 大帅哥_elasticsearch实现中文分词和拼音分词混合查询+CompletionSuggestion...
  8. 如何快准狠地找到相关领域的经典文献?
  9. C#-财务管理系统(成本核算)
  10. [教程]BaiduPCS-Go