前言
本文是参考Writing a simple shell in Go所实现的一个 toy-demo ,主要用于本人的一个留档记录。
开始实践
获取输入
键盘是我们的标准输入设备,通过创建一个 reader 来访问它。而当我们使用键盘输入命令后,需要通过回车键去确认执行。为了获取我们输入的命令,我们通过 ReadString 方法去获取以 \n
结尾的行内容。
1 2
| reader := bufio.NewReader(os.Stdin) input, err := reader.ReadString('\n')
|
执行命令
在获取到输入的命令后,我们需要执行这条命令。我们通过设置一个 execInput 函数去执行具体的处理逻辑,如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| func execInput(input string) error { input = strings.TrimSuffix(input, "\n")
cmd := exec.Command(input)
cmd.Stderr = os.Stderr cmd.Stdout = os.Stdout
return cmd.Run() }
|
初步实现命令行
通过在 main 方法中设置 for 循环使得命令行可以持续工作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| func main() { reader := bufio.NewReader(os.Stdin)
for { fmt.Printf("> ")
input, err := reader.ReadString('\n') if err != nil { fmt.Fprintln(os.Stderr, err) }
if err = execInput(input); err != nil { fmt.Fprintln(os.Stderr, err) } } }
func execInput(input string) error { input = strings.TrimSuffix(input, "\n")
cmd := exec.Command(input)
cmd.Stderr = os.Stderr cmd.Stdout = os.Stdout
return cmd.Run() }
|
自定义命令行提示符
go 提供了三个方法去获取相关的内容。
- 获取机器名称:
hostname, err := os.Hostname()
- 获取当前用户名称:
currentUser, err := exec.Command("whoami").Output()
- 获取当前工作目录:
cwd, err := os.Getwd()
获取相关信息后自行进行拼接即可。
命令参数
当前的命令行并不支持执行携带参数的命令,因为我们现在的程序会将命令与参数当成一个整体去执行,这当然会出现问题。因此就需要我们在 execInput 内进行进一步的优化实现,具体如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| func execInput(input string) error { input = strings.TrimSuffix(input, "\n")
args := strings.Split(input, " ")
cmd := exec.Command(args[0], args[1:]...)
cmd.Stderr = os.Stderr cmd.Stdout = os.Stdout
return cmd.Run() }
|
在 args[0] 内存放着我们的命令,而 args[1:] 存放着相关的参数。
内置命令
在我们执行部分命令,如 cd 命令时,我们会发现我们无法正确执行它,因为这种命令是 shell 的内置命令,我们无法直接通过 $PATH 去访问到它的具体执行程序,这就需要我们自己在 execInput 函数内实现,如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| func execInput(input string) error { input = strings.TrimSuffix(input, "\n")
args := strings.Split(input, " ")
switch args[0] { case "cd": if len(args) < 2 { return errors.New("path required") } return os.Chdir(args[1]) case "exit": os.Exit(0) }
cmd := exec.Command(args[0], args[1:]...)
cmd.Stderr = os.Stderr cmd.Stdout = os.Stdout
return cmd.Run() }
|
完整示例代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77
| package main
import ( "bufio" "errors" "fmt" "os" "os/exec" "strings" )
func main() { reader := bufio.NewReader(os.Stdin)
hostname, err := os.Hostname() if err != nil { panic(err) } currentUser, err := exec.Command("whoami").Output() if err != nil { panic(err) }
for { cwd, err := os.Getwd() if err != nil { fmt.Fprintln(os.Stderr, err) } fmt.Printf("%s@%s~%s> ", hostname, currentUser, cwd)
input, err := reader.ReadString('\n') if err != nil { fmt.Fprintln(os.Stderr, err) }
if err = execInput(input); err != nil { fmt.Fprintln(os.Stderr, err) } } }
func execInput(input string) error { input = strings.TrimSuffix(input, "\n")
args := strings.Split(input, " ")
switch args[0] { case "cd": if len(args) < 2 { return errors.New("path required") } return os.Chdir(args[1]) case "exit": os.Exit(0) }
cmd := exec.Command(args[0], args[1:]...)
cmd.Stderr = os.Stderr cmd.Stdout = os.Stdout
return cmd.Run() }
|