{"id":21969178,"url":"https://github.com/celestialphineas/myshell","last_synced_at":"2026-05-05T10:38:13.708Z","repository":{"id":201954711,"uuid":"97572182","full_name":"celestialphineas/myshell","owner":"celestialphineas","description":"Example of a feature-rich (toy) Linux shell without control flow support.","archived":false,"fork":false,"pushed_at":"2017-07-31T08:06:43.000Z","size":246,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-01-28T02:13:33.577Z","etag":null,"topics":["linux","myshell","shell","toy-project","unix"],"latest_commit_sha":null,"homepage":"","language":"C","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/celestialphineas.png","metadata":{"files":{"readme":"readme","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null}},"created_at":"2017-07-18T08:13:34.000Z","updated_at":"2018-05-24T03:28:49.000Z","dependencies_parsed_at":null,"dependency_job_id":"f4039e09-25ce-4aef-9941-7cf610874392","html_url":"https://github.com/celestialphineas/myshell","commit_stats":null,"previous_names":["celestialphineas/myshell"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/celestialphineas%2Fmyshell","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/celestialphineas%2Fmyshell/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/celestialphineas%2Fmyshell/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/celestialphineas%2Fmyshell/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/celestialphineas","download_url":"https://codeload.github.com/celestialphineas/myshell/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":245027119,"owners_count":20549246,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["linux","myshell","shell","toy-project","unix"],"created_at":"2024-11-29T14:07:51.915Z","updated_at":"2025-10-30T05:46:04.752Z","avatar_url":"https://github.com/celestialphineas.png","language":"C","funding_links":[],"categories":[],"sub_categories":[],"readme":"myshell 自述文档\n===============\n\nversion 0.1.0                                   \\\nAuthor: Celestial Phineas @ ZJU (Yehang YIN)    \\\nContact: yehangyin AT outlook DOT com           \\\nLicense: MIT\n\n*该文档使用了Markdown语法。*\n\n本文系 myshell 中文用户手册，如果您需要获知本项目的整体说明，请参阅 readme.md。\n\nThis file is the user's manual for myshell in simplified Chinese. See readme.md\nfor a more general perspective and a few details of implementation, as well as\na list of references.\n\n概述\n----\n\nmyshell 是一个 Linux 上的命令行 shell 实现，设计初衷是实现不含控制流的 bash 的一个子集。\nshell 中文一般译为“壳层”，是用户访问操作系统的界面。称作“壳层”的原因是它提供了系统内核之上的\n一层，可与用户交互。bash的全称是 Bourne-Again SHell，最早的作者是 Brain Fox，具有完备的\n通配符匹配、管道、重定向、命令替换、变量替换、控制流和任务控制等功能。myshell 最终达成了上述\n功能的部分。\n\nmyshell 具有如下主要特性：\n- 管道、重定向\n- 变量设置与替换\n- 相当一部分 shell 展开\n    - 波浪线展开（解释 home `~`）\n    - 变量展开\n    - 数组\n    - 数组元素\n    - 字符串长度\n- 任务控制\n- 内建命令\n- 完善的命令提示符机制\n- 支持 shebang 的处理\n- ……\n\nmyshell 不支持的主要特性：\n- 命令替换 $()\n- 子壳 (subshell，即圆括号)\n- 控制流（if, while, for）\n- shell 级的通配符展开（*和?的匹配）\n\nmyshell 不支持或没有做过充足测试的次要特性：\n- 本地化，甚至不做汉化\n- Unicode 支持\n- ANSI-C 转义字符展开 $''\n- 历史命令\n- 光标移动\n\n构建\n----\n\nmyshell 使用 make 工具构建，您只需要在 myshell 目录下键入命令 `make` 即可编译。\n\n```\nusername@hostname:~/path/myshell$ make\n```\n\nmake 会自动完成编译过程。\n\n运行\n----\n\n在命令行中键入 myshell 可以交互模式运行 myshell。myshell 支持如下参数：\n\n```\nmyshell\nUsage: myshell [options] script ...\nGNU options:\n    --help      Show this help file\n    --version   Show version\nShell options:\n    -i          Run in interactive mode\n```\n\n将脚本文件作为参数传入即可令 myshell 运行脚本，脚本文件名后面的参数将被视作脚本的参数传入，可\n在脚本中用 $1 ~ $9 ~ ${10} ... 访问。\n\nmyshell 支持对 shebang 的处理，也就是说您可以在终端中直接运行脚本，如果脚本的第一行指定了\nmyshell 的完整路径，myshell 即可正确处理脚本的打开与运行。shebang 指脚本文件头出现的“#!”\n字符序列，其对应的二进制为 Unix 下约定的脚本文件魔数。在执行脚本文件时，会将该行剩余内容作为\n执行这一脚本文件的命令。\n\n下面是一些例子，命令提示符用“$”表示：\n\n```\n$ ./myshell\nusername@hostname:pwd/\nmyshell\u003e\n```\n\n```\n$ ./myshell script.sh\n\u003c运行脚本…\u003e\n```\n\n```\n$ cat script.sh\n#! ./myshell\necho hello world\n$ chmod 777 script.sh\n$ ./script\n\u003c运行脚本…\u003e\n```\n\n基本使用\n--------\n\n经过成功编译与运行，myshell 已经可以开始使用了。终端中打印出的彩色字符是 myshell 的命令提示\n符，您可以看到自己的用户名、计算机名和当前操作目录。myshell 的命令提示符尽可能与 bash 相一\n致，但是为了彰示区别，命令提示符去掉了“$”符号，另起一行输出紫色 `myshell\u003e`。\n\n### Hello world\n\n下面一个例子将演示您如何在 myshell 中打印“Hello world”。您只需输入命令 `echo Hello world`\n\n```\nusername@hostname:~/myshell\nmyshell\u003e echo Hello world\n```\n\n### 调用其他命令\n\n您可以尝试键入 `ls`、`ps` 和 `pwd` 看看会发生什么。\n\n### 清屏\n\n清屏的命令是 `clear`，尝试一下。你会发现终端的文本内容清空了或上滚了一屏（取决于您所使用图形终\n端的特性）。\n\n重定向\n--------\n\nmyshell 支持重定向。重定向可以将当前命令的输入或输出从终端定位到其他文件。在 Unix/Linux 中，\n所有输入输出设备都被视作文件，文件也可以用于输入输出。将键盘输入和屏幕输出重定向到其他文件中当\n然也是可行的。\n\n### 向一个文件写 Hello world\n\n尝试一下这个命令：`echo hello world \u003ehello.txt`，它会创建一个名为 `hello.txt` 的文件并\n向其中写入 “hello world”。\n\n### 向文件中追加\n\n对上面的例子做一个小小的修改：`echo hello world \u003e\u003ehello.txt`，看看会得到什么。\n\n### 从文件中输入\n\n完成上面两个例子，我们可以试试 `cat \u003chello.txt`，这个命令会将 `hello.txt` 文件中的文本内容\n打印出来。\n\n### 更复杂的例子\n\nmyshell 同样支持文件描述符的重定向（限制为标准输入输出和错误文件）和多个重定向。如果您装有\n`apt-get` 的话可以试试下面的例子。\n\n`apt-get` 有一个众所周知的彩蛋，键入 `apt moo` 会打印一头牛，我们可以重定向这个输出。\n（`apt` 是 `apt-get` 的一个链接）。\n\n`apt moo` 会直接打出这头牛。而 `apt moo 1\u003e/dev/null` 则会打印出一条警告信息：\n\n`WARNING: apt does not have a stable CLI interface. Use with caution in scripts.`\n\n上面的命令中，`1` 的含义是文件描述符为 1 的文件，即标准输出。\n\n`apt-get` 在运行时会检查当前输出是否为终端，如果不是则会发出这条警告，因为它有运行时中断的风\n险。这样的警告在使用管道重定向 `apt-get` 时尤为常见。命令中的 `/dev/null` 俗称为黑洞，是\nLinux 的一个空设备，没有任何反馈。\n\n如果我们尝试 `apt moo 1\u003e/dev/null 2\u003e/dev/null` 这条命令，整条命令会变得彻底悄无声息。\n\n我们还可以交换标准输出和标准错误文件，这样我们同样会得到 `apt-get` 的报错，但是报错会写到标准\n输出而不是标准错误上。命令为 `apt moo 2\u003e\u00261 1\u003e\u00262`。\n\n上面的例子都在 myshell 上测试通过了。\n\n管道\n----\n\n管道即将一个命令的输出作为下一个命令的输入。管道使用连接符 `|`。\n\n您可以尝试下面这个命令：`gerp --help | more`，它将 `grep` 的帮助信息输出到 `more` 程序\n的输入，使得您可以用浏览的方式查看该帮助文档。\n\n或者是 `ls | cat`，这个命令可以列出当前目录的所有文件并列出。\n\n环境变量\n--------\n\n在使用 shell 时，您会有一个当前操作目录(current working directory)，所有相对路径都会由系\n统在这个当前操作目录查找。\n\n### 打印当前操作目录\n\n我们可以在命令提示符处看到当前操作目录，除此之外，还有可以使用 myshell 命令打印当前操作目录。\n\n打印当前操作目录有两种方式，myshell 提供了一个名为 `pwd` 的内建命令用于打印当前操作目录。您只\n需要输入 `pwd` 即可。\n\nmyshell 提供了环境变量的替换，这意味着您同样可以使用 `${PWD}` 或 `$PWD` 得到当前目录。试试\n`echo $PWD` 或 `echo ${PWD}`。\n\n### 改变当前操作目录\n\n改变当前目录可以使用内建命令 `cd` (change directory)，您只需在 `cd` 后面加上要转到的目录\n路径就行了。当 `cd` 命令没有参数的时候，会默认改变当前目录到 `$HOME`（你可以试试 `echo $HOME` \n看看会打印出什么）。\n\n特别地，`cd ..` 可以跳转到上一级目录，这是 Unix/Linux 系统的一个特性。\n\n### 打印所有环境变量\n\n大多数 Linux 发行版都提供了 `env` 程序，可以打印当前程序的所有环境变量。myshell 不提供导出\n功能（不支持 export），也同样不覆写系统提供的 `env`，在 myshell 中输出的是原封不动的程序运\n行环境。但是 myshell 加赠了一个环境变量称作 `PARENT` 其值即 myshell 的程序位置。\n\n### 变量的定义与替换\n\nmyshell 支持常见的变量定义方式，并提供变量替换的功能。例如，`$@` 会输出当前正在运行的\nmyshell 的所有参数，`$#` 则会输出参数的数目。具体到变量可以使用 `${#var[@]}` 访问到名字为\n`var` 的数组中的元素个数。`${#var}` 则是这个数组第 0 个元素的字符串长度（变量皆视作长度为 1\n的数组，在绝大多数 shell 实现中，变量都被统一处理成字符串数组，字符串是唯一存在的东西）。\n访问变量的值我们刚刚已经接触过了，花括号加与不加在大多数情况中作用是一致的。不加花括号时，变量\n扩展将会按照贪婪的方式向后匹配字符，直至遇到不合法的变量名字符和符号 (token) 末尾为止。两者\n区别最显著之处在于 `$10` 和 `${10}` 是不一样的，前者只会替换 `$1`，而保留 `0` 作为字符，\n后者会将 `10` 视作一个整体去进行替换。\n\n变量的定义使用 `=`，需要注意的是，变量名与等号之间是不能有空白字符的。等号的右侧可以为任何字符\n串，如果右边的串不加引号的话，会匹配到空白字符为止。\n\n数组的定义使用 `var=(arg1 arg2 arg3)` 的形式，参数与参数之间使用空格隔开。\n\n当前程序参数的更改可以使用内建的 `set` 命令。myshell 只实现 `set` 的无选项子集。命令\n`set arg1 arg2 arg3` 会将 `$1`、`$2` 和 `$3` 分别设置为 `arg1`、`arg2` 和 `arg3`。\n\n尝试如下会话：\n\n```\n$ set arg1 arg2 arg3\n$ echo $@\n./myshell arg1 arg2 arg3\n```\n\n如果想避免诸如 `${10}` 的变量访问方式，您可以使用 `shift` 命令将参数向前移一位，接续上面的会\n话：\n\n```\n$ shift\n$ echo $@\narg1 arg2 arg3\n```\n\n以上特性均在 myshell 上测试通过。\n\n任务控制\n--------\n\nmyshell 支持任务控制，目前似乎还存在一些小问题。与 bash 任务控制不同的是，myshell 就是以任\n务为中心的，每个输入命令都对应于一个任务。编号原则与实现方式也和 bash 有所不同，myshell 中任\n务的编号是从开始运行之后顺次向下编的，第一条命令对应于任务 0，第二条对应于任务 1，依次类推。\n\n### 创建后台任务\n\n创建后台任务在命令后加 `\u0026` 即可。\n\n与 bash 行为不同，myshell 不将 `\u0026` 视作分割后台任务的字符，而视作连接后台进程流水线的连接\n符。`sleep 10 \u0026 sleep 10 \u0026` 将创建一个后台任务，这个后台任务的进程流水线拥有两个进程，每个\n都是等待 10 秒钟的操作。\n\n### 查看所有任务\n\n`jobs` 是一个内建命令，可以列出所有任务的任务号、完成状态和对应命令。你或许会看到 `jobs` 这个\n任务本身也赫然在列，这是由于 myshell 和 bash 的任务控制机制不同所致。\n\n### 切换前台后台任务\n\n前台任务和后台任务的区别在于是否占据终端的输入输出。myshell 中，您可以改变任务的前台后台状态。\n\n一般地，没有被创建为后台任务的命令会被自动创建为前台任务，前台任务中您可以使用 `Ctrl + Z` 发\n送停止信号，使任务被挂起。\n\n使用 `jobs` 可以列出任务，包括被挂起的任务，`fg [任务号]` 可以将任务重新放回前台\n`bg [任务号]` 则会对任务中的进程发送继续信号，让进程继续。\n\n写在最后\n--------\n\n如果您要写一个实用的 shell，绝大多数建议都是不要这样做，因为已经有很多足够好的解决方案，且有\n很多比 shell 好的解决方案，可以让 shell 少承担一些负担，比如各种脚本语言。虽说如此，自己写一\n个 shell 会极大提升对 Unix 和 Linux 的认知。\n\n这一文档以使用说明为主，有很多地方会流露出自己在编写过程中对 shell 的理解，希望这一文档，连同\n这一项目的其他文档和源代码会对您有所启发。","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcelestialphineas%2Fmyshell","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcelestialphineas%2Fmyshell","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcelestialphineas%2Fmyshell/lists"}