欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

使用Golang開發(fā)一個(gè)簡(jiǎn)易版shell

 更新時(shí)間:2024年02月28日 09:33:45   作者:波羅學(xué)  
這篇文章主要為大家詳細(xì)介紹了如何使用Golang開發(fā)一個(gè)簡(jiǎn)易版shell,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下

之前看到 Github 有個(gè) build-your-own-x 的倉(cāng)庫(kù),覺得挺有意思的,有不少有趣的實(shí)現(xiàn)。我就想著多嘗試實(shí)現(xiàn)些這樣的小項(xiàng)目,看看不同的領(lǐng)域。一方面提升我的編程能力,另外,也希望能發(fā)現(xiàn)一些不錯(cuò)的項(xiàng)目。

今天的項(xiàng)目在 build-your-own-x 中也能找到,即 build your own shell。這個(gè)項(xiàng)目能幫助學(xué)習(xí) Go 如何進(jìn)行如 IO 輸入輸出、如何發(fā)起進(jìn)程調(diào)用等操作。

核心流程

首先,我聲明這是個(gè)簡(jiǎn)陋的 shell,但能幫助我們更好理解 Shell。它支持如提示符打印、讀取用戶輸入、解析輸入內(nèi)容、執(zhí)行命令,另外還支持開發(fā)內(nèi)建命令。

如下是演示效果:

接下來,我將從零開始一步步復(fù)現(xiàn)我的整個(gè)開發(fā)過程。

框架搭建

我從創(chuàng)建一個(gè) Shell 結(jié)構(gòu)體開始,這是整個(gè) shell 程序的核心,它其中包含一個(gè) bufio.Reader 從標(biāo)準(zhǔn)輸入讀取用戶輸入。

type Shell struct {
  reader      *bufio.Reader
}

func NewShell() *Shell {
  return &Shell{
    reader: bufio.NewReader(os.Stdin),
  }
}

如上,通過 NewShell 構(gòu)造函數(shù)創(chuàng)建 Shell 實(shí)例。這個(gè)函數(shù)返回一個(gè)新的 Shell 實(shí)例,其中包含了初始化的 bufio.Reader。

為了方便擴(kuò)展,接下來添加了幾個(gè)方法,分別是:

• PrintPrompt用于打印提示符;

• ReadInput用于讀取用戶輸入;

• ParseInput用于解析輸入并分割成命令名和參數(shù);

• ExecuteCmd用于執(zhí)行命令。

定義如下:

func (s *Shell) PrintPrompt()
func (s *Shell) ReadInput() (string, error)
func (s *Shell) ParseInput(input string) (string, []string)
func (s *Shell) ExecuteCmd(cmdName string, cmdArgs []string) error

它們就是核心流程中最重要的四個(gè)方法,都是在 RunAndListen 方法中被調(diào)用,如下所示:

func (s *Shell) RunAndListen() error {
  for {
    s.PrintPrompt()

    input, err := s.ReadInput()
    if err != nil {
      fmt.Fprintln(os.Stderr, err)
      continue
    }

    cmdName, cmdArgs := s.ParseInput(input)

    if err := s.ExecuteCmd(cmdName, cmdArgs); err != nil {
      fmt.Fprintln(os.Stderr, err)
      continue
    }
  }
}

主函數(shù) main 的代碼不復(fù)雜,如下所示:

func main() {
    s := NewShell()
    _ = s.RunAndListen()
}

通過 NewShell 創(chuàng)建 Shell 示例,調(diào)用 RunAndListen 監(jiān)聽用戶輸入即可。

接下來,我開始介紹其中每一步的實(shí)現(xiàn)過程。

打印提示符

首先,打印提示符的代碼,非常簡(jiǎn)單,如下所示:

func (s *Shell) PrintPrompt() {
  fmt.Print("$ ")
}

單純的打印 $ 作為提示符,更復(fù)雜的場(chǎng)景可以加上路徑提示,如:

[~/demo/shell]$

修改后的代碼如下所示:

func (s *Shell) PrintPrompt() {
  // 獲取當(dāng)前工作目錄
  cwd, err := os.Getwd()
  if err != nil {
    // 如果無法獲取工作目錄,打印錯(cuò)誤并使用默認(rèn)提示符
    fmt.Println("Error getting current directory:", err)
    fmt.Print("$ ")
    return
  }

  // 獲取當(dāng)前用戶的HOME目錄
  homeDir, err := os.UserHomeDir()
  if err != nil {
    fmt.Println("Error getting home directory:", err)
    fmt.Print("$ ")
    return
  }

  // 如果當(dāng)前工作目錄以HOME目錄開頭,則用'~'替換掉HOME目錄部分
  if strings.HasPrefix(cwd, homeDir) {
    cwd = strings.Replace(cwd, homeDir, "~", 1)
  }

  // 打印包含當(dāng)前工作目錄的提示符
  fmt.Printf("[%s]$ ", cwd)
}

這是非常粗糙的拿到目錄并打印出來。

通常 Shell 的提示符是可以自定義,有興趣可以在這里擴(kuò)展個(gè)接口類型,用于不同提示符的格式化實(shí)現(xiàn)。

讀取用戶輸入

最簡(jiǎn)單的讀取用戶輸入的代碼,代碼如下:

func (s *Shell) ReadInput() (string, error) {
  input, err := s.reader.ReadString('\n')
  if err != nil {
    return "", err
  }

  return input, nil
}

按 \n 分割命令,分割出來的文本可以理解為一次執(zhí)行請(qǐng)求。

但實(shí)際情況是在使用 Shell 時(shí),我們會(huì)發(fā)現(xiàn)一些特殊符號(hào)是要處理,如引號(hào)。

例如:

[~/demo/shell]$ echo '
Hello World!
Nice to See you!
'

下面是一個(gè)簡(jiǎn)化的實(shí)現(xiàn):

func (s *Shell) ReadInput() (string, error) {
  var input []rune
  var inSingleQuote, inDoubleQuote bool

  for {
    r, _, err := s.reader.ReadRune()
    if err != nil {
      return "", err
    }

    // Check for quote toggle
    switch r {
    case '\'':
      inSingleQuote = !inSingleQuote
    case '"':
      inDoubleQuote = !inDoubleQuote
  }

    // Break on newline if not in quotes
    if r == '\n' && !inSingleQuote && !inDoubleQuote {
      break
    }

    input = append(input, r)
  }

  return string(input), nil
}

如上的代碼中,逐一讀取輸入內(nèi)容。程序中,通過判斷當(dāng)前是處于引號(hào)中,保證正確識(shí)別用戶輸入。

如果你讀過我之前一篇文章,熟練使用 bufio.Scanner 類型,也可以用它提供的自定義分割規(guī)則的方式,在這個(gè)場(chǎng)景下也可以使用。我的完整源碼 goshell 就是基于 Scanner 實(shí)現(xiàn)的。

另外,這個(gè)輸入不支持刪除,如果我輸出錯(cuò)了,只能退出重來,也是挺頭疼的。如果要實(shí)現(xiàn),要依賴于其他庫(kù)實(shí)現(xiàn)。

解析輸入

讀取完成,通過 ParseInput 方法解析成 cmdName 和 cmdArgs,代碼如下:

func (s *Shell) ParseInput(input string) (string, []string) {
  input = strings.TrimSuffix(input, "\n")
  input = strings.TrimSuffix(input, "\r")

  args := strings.Split(input, " ")

  return args[0], args[1:]
}

真正的 Shell 肯定比這個(gè)強(qiáng)大的多了。最容易想到的,一次 shell 執(zhí)行請(qǐng)求可能包含多個(gè)命令,甚至是 shell 腳本。

太復(fù)雜的能力實(shí)現(xiàn)起來太麻煩,我們可以支持一個(gè)最簡(jiǎn)單的能力,分號(hào)分割運(yùn)行多個(gè)命令。

$ cd /; ls

我們修改代碼,支持這個(gè)能力。

type CmdRequest struct {
  Name string
  Args []string
}

func (s *Shell) ParseInput(input string) []*CmdRequest {
  subInputs := strings.Split(input, ";")

  cmdRequests := make([]*CmdRequest, 0, len(subInputs))
  for _, subInput := range subInputs {
    subInput = strings.Trim(subInput, " ")
    subInput = strings.TrimSuffix(subInput, "\n")
    subInput = strings.TrimSuffix(subInput, "\r")
    args := strings.Split(subInput, " ")
    cmdRequests = append(cmdRequests, &CmdRequest{Name: args[0], Args: args[1:]})
  }

  return cmdRequests
}

上面代碼里,定義了一個(gè)新類型 CmdRequest,它用于保存從用戶輸入解析而來的命令名和命令參數(shù)。

由于修改了 ParseInput 的返回類型,RunAndListen 中的邏輯就要改動(dòng)了。

如下所示:

for {
  // ...

  cmdRequests := s.ParseInput(input)
  for _, cmdRequest := range cmdRequests {
    cmdName := cmdRequest.Name
    cmdArgs := cmdRequest.Args

    if err := s.ExecuteCmd(cmdName, cmdArgs); err != nil {
      fmt.Fprintln(os.Stderr, err)
      continue
    }
  }
}

到此,通過分號(hào)分割多命令也是支持的了。

命令執(zhí)行

最后一步就是執(zhí)行命令了。代碼如下所示:

func (s *Shell) ExecuteCmd(cmdName string, cmdArgs []string) error {
  cmd := exec.Command(cmdName, cmdArgs...)
  cmd.Stderr = os.Stderr
  cmd.Stdout = os.Stdout

  return cmd.Run()
}

我使用的是標(biāo)準(zhǔn)庫(kù) exec 包中的 Command 類型創(chuàng)建一個(gè)命令用于執(zhí)行外部命令。

這個(gè)命令的標(biāo)準(zhǔn)輸出和標(biāo)準(zhǔn)錯(cuò)誤都被設(shè)置為當(dāng)前進(jìn)程的對(duì)應(yīng)輸出,這樣命令的輸出就可以直接顯示給用戶。

最后,通過調(diào)用 cmd.Run() 執(zhí)行該命令即可。

退出功能

在初步測(cè)試中,我發(fā)現(xiàn) shell 還不支持退出。為了解決這個(gè)問題,我在 RunAndListen 循環(huán)中加入了對(duì) exit 命令的檢查。

for {
  cmdName := cmdRequest.Name
  cmdArgs := cmdRequest.Args

  if cmdName == "exit" {
    return nil
  }

  if err := s.ExecuteCmd(cmdName, cmdArgs); err != nil {
  }
}

如果用戶輸入的是exit,循環(huán)將終止,直接退出 shell。

內(nèi)建命令

現(xiàn)在,如果測(cè)試這個(gè)代碼,看起來運(yùn)轉(zhuǎn)一切正常。但如果仔細(xì)測(cè)試,會(huì)發(fā)現(xiàn)它還不支持 cd 的能力。

為什么 cd 不能用?

因?yàn)楦淖儺?dāng)前目錄要修改進(jìn)程的工作目錄,這種操作不能像其他外部命令那樣通過創(chuàng)建新進(jìn)程實(shí)現(xiàn)。因此,我引入了內(nèi)建命令的實(shí)現(xiàn),并實(shí)現(xiàn)第一個(gè)內(nèi)建命令了ChangeDirCommand

首先是搭建一個(gè)簡(jiǎn)單框架,定義一個(gè)接口:

type BuiltinCmder interface {
    Execute(args ...string) error
}

任何實(shí)現(xiàn)了這個(gè)接口的類型都可以作為內(nèi)建的命令。

在 Shell 類型新建了一個(gè)字段,名為 builtinCmds ,修改定義如下:

type Shell struct {
    reader      *bufio.Reader
    builtinCmds map[string]BuiltinCmder
}

并添加了一個(gè)方法,名為 RegisterBuiltinCmd

func (s *Shell) RegisterBuiltinCmd(cmdName string, cmd BuiltinCmder) {
    s.builtinCmds[cmdName] = cmd
}

在 Shell 的 ExecuteCmd 中新增了內(nèi)建命令的執(zhí)行:

func (s *Shell) ExecuteCmd(cmdName string, cmdArgs []string) error {
  if cmd, ok := s.builtinCmds[cmdName]; ok {
    return cmd.Execute(cmdArgs...)
  }

  cmd := exec.Command(cmdName, cmdArgs...)
  // ...
}

現(xiàn)在,只要實(shí)現(xiàn) ChangeDirCommand,并在 main 入口函數(shù)注冊(cè)這個(gè)內(nèi)建就行了。ChangeDirCommand 代碼和入口注冊(cè)代碼,如下所示:

type ChangeDirCommand struct{}

func (c *ChangeDirCommand) Execute(args ...string) error {
  if len(args) < 2 {
    return errors.New("Expected path argument")
  }
  return os.Chdir(args[1])
}

func main() {
    s := NewShell()

    s.RegisterBuiltinCmd("cd", &ChangeDirCommand{})

    _ = s.RunAndListen()
}

到此大功搞成,源碼地址:goshell

總結(jié)

通過開發(fā)這個(gè)簡(jiǎn)單的 shell,了解 Go 如何讀取如用戶輸入,解析與執(zhí)行用戶命令。對(duì) shell 的流程也有了一個(gè)大概了解。

未來,如果有想法,或許會(huì)繼續(xù)擴(kuò)展這個(gè) shell,添加更多內(nèi)建命令,可以將不同部分模塊化,如 Prompt, Reader, Parser 和 Command 都是可以繼續(xù)抽象以支持更多能力。

如果繼續(xù)它的開發(fā),期待學(xué)習(xí)到更多關(guān)于系統(tǒng)編程和 Go 語(yǔ)言的高級(jí)特性。而且shell 可不止這點(diǎn)能力,如果你了解 shell,使用過 bash 或是 zsh 等 shell 就知道它們是如何提高我們工作效率的了。

到此這篇關(guān)于使用Golang開發(fā)一個(gè)簡(jiǎn)易版shell的文章就介紹到這了,更多相關(guān)Go開發(fā)shell內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 詳解如何用Golang處理每分鐘100萬(wàn)個(gè)請(qǐng)求

    詳解如何用Golang處理每分鐘100萬(wàn)個(gè)請(qǐng)求

    在項(xiàng)目開發(fā)中,我們常常會(huì)遇到處理來自數(shù)百萬(wàn)個(gè)端點(diǎn)的大量POST請(qǐng)求,本文主要介紹了Golang實(shí)現(xiàn)處理每分鐘100萬(wàn)個(gè)請(qǐng)求的方法,希望對(duì)大家有所幫助
    2023-04-04
  • 淺談一下前端http與https有什么區(qū)別

    淺談一下前端http與https有什么區(qū)別

    這篇文章主要介紹了淺談一下前端http與https有什么區(qū)別,現(xiàn)今大部分的網(wǎng)站都已經(jīng)使用了 https 協(xié)議,那么https對(duì)比http協(xié)議有哪些不同呢,需要的朋友可以參考下
    2023-04-04
  • 在ubuntu下安裝go開發(fā)環(huán)境的全過程

    在ubuntu下安裝go開發(fā)環(huán)境的全過程

    Go語(yǔ)言是谷歌公司開發(fā)的編程語(yǔ)言,雖然安裝和配置go很簡(jiǎn)單,但是很多初學(xué)者在第一次安裝go環(huán)境時(shí)會(huì)遇到各種坑,下面這篇文章主要給大家介紹了關(guān)于在ubuntu下安裝go開發(fā)環(huán)境的相關(guān)資料,文中通過圖文介紹的非常詳細(xì),需要的朋友可以參考下
    2022-08-08
  • 深入解析golang?bufio

    深入解析golang?bufio

    這篇文章主要介紹了golang?bufio解析,golang的bufio庫(kù)使用緩存來一次性進(jìn)行大塊數(shù)據(jù)的讀寫,以此降低IO系統(tǒng)調(diào)用,提升性能,需要的朋友可以參考下
    2022-04-04
  • Golang爬蟲框架 colly的使用

    Golang爬蟲框架 colly的使用

    本文主要介紹了Golang爬蟲框架 colly的使用,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2022-07-07
  • go gin+token(JWT)驗(yàn)證實(shí)現(xiàn)登陸驗(yàn)證

    go gin+token(JWT)驗(yàn)證實(shí)現(xiàn)登陸驗(yàn)證

    本文主要介紹了go gin+token(JWT)驗(yàn)證實(shí)現(xiàn)登陸驗(yàn)證,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2021-12-12
  • Go語(yǔ)言Elasticsearch數(shù)據(jù)清理工具思路詳解

    Go語(yǔ)言Elasticsearch數(shù)據(jù)清理工具思路詳解

    這篇文章主要介紹了Go語(yǔ)言Elasticsearch數(shù)據(jù)清理工具思路詳解,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2021-10-10
  • 關(guān)于golang高并發(fā)的實(shí)現(xiàn)與注意事項(xiàng)說明

    關(guān)于golang高并發(fā)的實(shí)現(xiàn)與注意事項(xiàng)說明

    這篇文章主要介紹了關(guān)于golang高并發(fā)的實(shí)現(xiàn)與注意事項(xiàng)說明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧
    2021-05-05
  • gin框架Context如何獲取Get?Query?Param函數(shù)數(shù)據(jù)

    gin框架Context如何獲取Get?Query?Param函數(shù)數(shù)據(jù)

    這篇文章主要為大家介紹了gin框架Context?Get?Query?Param函數(shù)獲取數(shù)據(jù),有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-03-03
  • Go Slice進(jìn)行參數(shù)傳遞如何實(shí)現(xiàn)詳解

    Go Slice進(jìn)行參數(shù)傳遞如何實(shí)現(xiàn)詳解

    這篇文章主要為大家介紹了Go Slice進(jìn)行參數(shù)傳遞如何實(shí)現(xiàn)的過程詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-12-12

最新評(píng)論