[译] 使用 Go 语言编写一个简单的 SHELL


本文摘自网络,作者,侵删。

翻译自:https://sj14.gitlab.io/post/2...

介绍

在本文中,我们将使用 Go 语言,编写一个最小的 UNIX(-like)操作系统 SHELL,它只需要大概 60 行代码。你需要稍微了解一些 Go 语言(知道如何编译简单的项目),以及简单使用 UNIX Shell。

UNIX 非常简单,简单到一个天才都能理解它的简单性 - Dennis Ritchie

当然,我并非天才,我也不太确定 Dennis Ritchie 所说的,是否也包括运行于用户空间的工具。Shell 只是完整操作系统的一小部分(相较于内核,它真的是一个简单的部分),但我希望在本文的结尾,你可以感到吃惊,吃惊于编写一个 SHELL,所用到的知识如此少。

什么是SHELL

给 SHELL 下定义有点困难。我认为 SHELL 可以理解为你所使用的操作系统,基本的用户界面。你可以在 SHELL 中输入命令,然后接收一些反馈输出。如果想了解更多信息,或者更明确的定义,请查阅 维基百科)。

一些 SHELL 的例子:

  • Bash)
  • Zsh
  • Gnome Shell
  • Windows Shell

有像 Windows 和 GNOME 这种图形界面 SHELL,但大多数 IT 相关人员(至少我是),当谈论起 SHELL,指的是基于文本的 SHELL(上面列表的头两项)。当然,也可以简化的定义为非图形界面 SHELL。

事实上,SHELL 的功能可以定义为输入命令,然后接收该命令的输出。想看个例子?运行 ls 命令,输出目录的内容。

Input:

ls
Output:

Applications            etc
Library                home
...

就是这样,十分简单。让我们开始吧!

输入循环

要执行一个命令,我们必须接收输入。而输入来自我们人类,使用键盘进行的。

键盘是我们的标准输入设备(os.Stdin),我们可以访问并读取它。当我们按下回车键的时候,会创建新的一行。这行新的文本以 \n 结尾。当敲击回车键的时候,所有存储在输入区的内容将被输入。

reader := bufio.NewReader(os.Stdin)
input, err := reader.ReadString('\n')

让我们将这些代码输入进我们的 main.go 文件,ReadingString 方法被嵌套在 for 循环中,所以我们可以反复输入命令。当读取输入,发生错误时,我们可以将错误信息输出到标准错误处理设备(os.Stderr)。如果我们使用 fmt.Println,但并没有指定输出设备,这个错误信息还是会输出到标准输出设备中(os.Stdout)。这并不会改变 SHELL 的功能,但是输出到单独的设备,可以方便过滤输出,以进行下一步处理。

func main() {
    reader := bufio.NewReader(os.Stdin)
    for {
        // Read the keyboad input.
        input, err := reader.ReadString('\n')
        if err != nil {
            fmt.Fprintln(os.Stderr, err)
        }
    }
}

执行命令

现在,我们打算执行输入的命令。增加一个名为 execInput 的新的函数,他接收输入的字符串作为参数。首先,我们移除输入结尾的换行符。接下来,通过 exec.Command(input) 来准备执行命令,设置参数,以及捕获输出的结果和错误。最后,通过 cmd.Run() 来执行。

func execInput(input string) error {
    // Remove the newline character.
    input = strings.TrimSuffix(input, "\n")

    // Prepare the command to execute.
    cmd := exec.Command(input)

    // Set the correct output device.
    cmd.Stderr = os.Stderr
    cmd.Stdout = os.Stdout

    // Execute the command and return the error.
    return cmd.Run()
}

原型

接着,在循环语句上面,添加一个美化作用的指示器(>),在循环语句下面,添加新的 execInput 函数,此时,主要功能就完成了。

func main() {
    reader := bufio.NewReader(os.Stdin)
    for {
        fmt.Print("> ")
        // Read the keyboad input.
        input, err := reader.ReadString('\n')
        if err != nil {
            fmt.Fprintln(os.Stderr, err)
        }

        // Handle the execution of the input.
        if err = execInput(input); err != nil {
            fmt.Fprintln(os.Stderr, err)
        }
    }
}

是时候执行一次测试了。使用 go run main.go 构建并运行咱们的 SHELL。你将看到输入标识符 >,此时可以接受输入。举个例子,我们可以执行 ls 命令。

> ls
LICENSE
main.go
main_test.go

不错,可以运行!咱们的程序此时可以执行 ls 命令,并输出当前目录的内容。你可以像退出其他程序一样,使用 ctrl+c,退出它。

参数

让我们命令后面加个参数,如 ls -l

> ls -l

执此时执行会报错:exec: "ls -l": executable file not found in $PATH

阅读剩余部分

相关阅读 >>

pprof最全功能

详解Go 语言中的方法

再见Go-micro!企业项目迁移Go-zero全攻略(一)

gin(6)-模板渲染

Golang判断map中key不存在的方法

Go - 常用签名算法的基准测试

Golang阿里云api请求鉴权

必须掌握的Golang23种设计模式之工厂方法模式

11 Golang函数详解

聊聊dubbo-Go-proxy的consulregistryload

更多相关阅读请进入《Go》频道 >>




打赏

取消

感谢您的支持,我会继续努力的!

扫码支持
扫码打赏,您说多少就多少

打开支付宝扫一扫,即可进行扫码打赏哦

分享从这里开始,精彩与您同在

评论

管理员已关闭评论功能...