Go에서 GNU Coreutils를 재생성하여 기술 향상

34605 단어 gotutorial

재미를 위한 바퀴 재발명



프로그래밍 기술을 습득하기 위한 가장 인기 있는 조언 중 하나는 이미 존재하는 도구를 다시 구현하라는 것입니다. 예를 들어 CRUD API를 수행하는 방법을 배우고 싶다면 "To do"앱으로 연습했을 것입니다. 이것의 요점은 새롭고 획기적인 것을 창조하는 것이 아니라 잘 알려진 아이디어를 사용하여 여러분이 알고 있는 것을 개선하는 것입니다. 당신이 본 다른 예는 websocket, 날씨 앱, Hacker News 리더에 대해 배우기 위해 채팅 서버를 구현하는 것과 같은 것입니다.

오늘 저는 제가 더 흥미롭게 생각하는 GNU Coreutils 유틸리티 재구현에 대해 이야기하고 있습니다. 내가 이것을 하는 첫 번째 사람은 아니지만, 나는 당신이 선택한 언어로 훨씬 더 나은 기술을 가지고 이 연습을 통해 나올 것이라고 확신합니다.

그렇다면 Coreutils는 무엇입니까? Linux 또는 Mac을 사용하는 경우 아마도 많은 것을 사용했을 것입니다. 경우에 따라 Windows에서도 가능합니다. Take a look at this link for more information .



다양한 방식으로 이 프로젝트에 접근할 수 있습니다. 제 경우에는 coreutils의 요점이 정말 작고 정말 단순하다는 것입니다. 하나의 작업만 수행하지만 잘 수행합니다. C의 원래 저장소에서 대부분의 도구는 하나의 단일 파일입니다. 저도 같은 생각으로 시작하겠습니다.
  • 매뉴얼 페이지를 읽는 기능을 이해합니다. 요구 사항.
  • 나만의 기준 외에는 가이드라인 없이 자유롭게 해당 기능을 구현합니다. 프로젝트 기획은 내가 한다!
  • 내 도구의 출력을 실제 coreutil 도구의 출력과 비교합니다. QA 부분입니다.

  • 보다시피 이것은 진지한 구조화된 연습이라기보다는 매우 자유로운 운동에 가깝습니다. 추가 라이브러리를 사용할지 여부에 대해서도 매우 유연합니다. 예를 들어 Go의 flag 패키지는 설정하기가 약간 지루하다는 것을 깨달았습니다. 다른 플래그 패키지가 훨씬 좋습니다. 또한 -wl , to get as close as possible to the real Coreutils functionality 대신 -w -l 와 같은 인수를 연결할 수 있는 인수를 찾아야 합니다.

    화장실



    내가 선택한 첫 번째 도구는 word counter WC 입니다. 가장 어려운 것도, 가장 쉬운 것도 아닙니다. 나에게 요구 사항은 다음과 같습니다.
  • 모든 종류의 카운트에 대한 개별 함수
  • 플래그 구문 분석
  • Stdin에서 입력을 받고 인수로 전달된 파일을 가져오는 기본 함수
  • 전달된 플래그에 따라 계수 함수가 이전 단계의 텍스트를 인수로 사용하여 실행됩니다
  • .
  • 결과가 화면에 인쇄됨

  • 그래서 내 거친 스키마:



    그리고 여기 코드가 있습니다. 앞에서 언급했듯이 유일한 외부 종속성은 내 개인 취향이지만 쉽게 피할 수 있습니다 (특히 최근에!). 그러나 이것을 초기 연습으로 삼는다면 결과는 나쁘지 않다고 생각합니다.

    따라할 수 있도록 코드에 주석을 달았습니다.

    package main
    
    import (
        "bufio"
        "bytes"
        "fmt"
        "github.com/urfave/cli"
        "io/ioutil"
        "log"
        "os"
        "strings"
        "unicode/utf8"
    )
    
    /*
    We define the four basic counting functions. 
    The interesting thing with Go is that unlike 
    languages like Python, len(string) will give you 
    the byte count, not the character count. 
    For characters, you need to count runes. 
    */
    
    func byteCounts(text string) int {
        return len(text)
    }
    
    func lineCounts(text string) int {
        return len(strings.Split(text, "\n"))
    }
    
    func characterCounts(text string) int {
        return utf8.RuneCountInString(text)
    }
    
    func wordCounts(text string) int {
        return len(strings.Fields(text))
    }
    
    /* 
    We initialize the CLI App with the definition 
    of the flags. I think that the way this library 
    handles flags is pretty clean and elegant. 
    But feel free to use the `flag` package too. 
    */
    
    func wc() *cli.App {
        app := cli.NewApp()
        app.Name = "WC"
        app.Usage = "The wc utility displays the number of lines, words, and bytes contained in each input file"
        app.Flags = []cli.Flag{
            cli.BoolFlag{
                Name:  "c",
                Usage: "The number of bytes in each input file is written to the standard output",
            },
            cli.BoolFlag{
                Name:  "l",
                Usage: "The number of lines in each input file is written to the standard output",
            },
            cli.BoolFlag{
                Name:  "m",
                Usage: "The number of characters in each input file is written to the standard output",
            },
            cli.BoolFlag{
                Name:  "w",
                Usage: "The number of words in each input file is written to the standard output",
            },
        }
    
    // We define what actually happens when running
    
        app.Action = func(c *cli.Context) error {
    
            var buf bytes.Buffer
            var m map[string]int
            m = make(map[string]int)
    
    /* 
    We check for text passed to Stdin, just so we 
    can invoke the app by doing `echo "hello" | wc -m` 
    */
    
            scanner := bufio.NewScanner(os.Stdin)
            for scanner.Scan() {
                buf.WriteString(scanner.Text())
            }
    
            if err := scanner.Err(); err != nil {
                log.Println(err)
            }
    
    /* 
    Maybe some filenames have been passed as argument. 
    We have to retrieve the text from those and 
    store it in a buffer 
    */
    
            for i := range c.Args() {
                fmt.Print(i)
                content, err := ioutil.ReadFile(c.Args().Get(i))
                if err != nil {
                    log.Fatal(err)
                }
                buf.WriteString(string(content))
            }
    
    // Now, depending on passed flags, we run the counting
    
            if c.Bool("c") {
                m["clen"] = byteCounts(buf.String())
            }
    
            if c.Bool("l") {
                m["llen"] = lineCounts(buf.String())
            }
    
            if c.Bool("m") {
                m["mlen"] = characterCounts(buf.String())
            }
    
            if c.Bool("w") {
                m["wlen"] = wordCounts(buf.String())
            }
    
            if c.Bool("c") && c.Bool("m") {
                m["mlen"] = 0
            }
    
    // Printing the final values
    
            for _, value := range m {
                if value != 0 {
                    fmt.Print("\t", value)
                }
            }
            fmt.Println("")
    
            return nil
        }
        return app
    }
    
    func main() {
        app := wc()
        app.Run(os.Args)
    }
    
    


    완벽하지 않고 기능이 실제 도구와 100% 일치하지는 않지만 이 작업을 수행하면서 몇 가지를 배웠습니다. 그것이 목표였습니다!

    재미를 위해 여기 고루틴/채널 활성화 버전이 있습니다. 당신의 선택을 선택!

    package main
    
    import (
        "bufio"
        "bytes"
        "fmt"
        "io/ioutil"
        "log"
        "os"
        "strings"
        "sync"
        "unicode/utf8"
    
        "github.com/urfave/cli"
    )
    
    var wg sync.WaitGroup
    
    func byteCounts(text string) int {
        return len(text)
    }
    
    func lineCounts(text string) int {
        return len(strings.Split(text, "\n"))
    }
    
    func characterCounts(text string) int {
        return utf8.RuneCountInString(text)
    }
    
    func wordCounts(text string) int {
        return len(strings.Fields(text))
    }
    
    func wc() *cli.App {
        app := cli.NewApp()
        app.Name = "WC"
        app.Usage = "The wc utility displays the number of lines, words, and bytes contained in each input file"
        app.Flags = []cli.Flag{
            cli.BoolFlag{
                Name:  "c",
                Usage: "The number of bytes in each input file is written to the standard output",
            },
            cli.BoolFlag{
                Name:  "l",
                Usage: "The number of lines in each input file is written to the standard output",
            },
            cli.BoolFlag{
                Name:  "m",
                Usage: "The number of characters in each input file is written to the standard output",
            },
            cli.BoolFlag{
                Name:  "w",
                Usage: "The number of words in each input file is written to the standard output",
            },
        }
        app.Action = func(c *cli.Context) error {
    
            var buf bytes.Buffer
            var m map[string]int
            m = make(map[string]int)
    
            scanner := bufio.NewScanner(os.Stdin)
            for scanner.Scan() {
                buf.WriteString(scanner.Text())
            }
    
            if err := scanner.Err(); err != nil {
                log.Println(err)
            }
    
            for i := range c.Args() {
                fmt.Print(i)
                content, err := ioutil.ReadFile(c.Args().Get(i))
                if err != nil {
                    log.Fatal(err)
                }
                buf.WriteString(string(content))
            }
    
            bytesChan := make(chan int)
            linesChan := make(chan int)
            charactersChan := make(chan int)
            wordChan := make(chan int)
    
            wg.Add(4)
            go func(text string) {
                bytesChan <- byteCounts(text)
                wg.Done()
            }(buf.String())
            go func(text string) {
                linesChan <- lineCounts(text)
                wg.Done()
            }(buf.String())
            go func(text string) {
                charactersChan <- characterCounts(text)
                wg.Done()
            }(buf.String())
            go func(text string) {
                wordChan <- wordCounts(text)
                wg.Done()
            }(buf.String())
    
            for i := 0; i < 4; i++ {
                select {
                case msg1 := <-bytesChan:
                    m["c"] = msg1
                    m["m"] = 0
                case msg2 := <-linesChan:
                    m["l"] = msg2
                case msg3 := <-charactersChan:
                    m["m"] = msg3
                    m["c"] = 0
                case msg4 := <-wordChan:
                    m["w"] = msg4
                }
            }
    
            wg.Wait()
            for key, value := range m {
                if c.Bool(key) && value != 0 {
                    fmt.Print("\t", key, ": ", value)
                }
            }
            fmt.Println("")
    
            return nil
        }
    
        return app
    }
    
    func main() {
        app := wc()
        app.Run(os.Args)
    }
    
    


    최종 버전 실행



    실행 파일을 빌드할 수 있습니다.

    go build -o gwc wc/wc.go
    

    여기서 gwc는 원하는 실행 파일 이름(실제 파일wc과 혼동하지 않기 위해)이고 wc/wc.go는 스크립트를 보관하는 경로입니다. Mac 및 Linux에서는 경로에 추가하지 않는 한 gwc로 호출해야 하는 ./gwc 파일을 얻게 됩니다. Windows에서는 gwc.exe 를 받아야 합니다.

    그리고 우리는 그것을 실행할 수 있습니다:

    echo "hello dev.to" | ./gwc -c 
    

    출력12
    Windows를 사용하는 경우 명령줄에서 stdin 텍스트 전달이 어떻게 작동하는지 잘 모르겠으므로 다음 대체 예를 시도해 볼 수 있습니다.

    텍스트가 포함된 파일을 만들고 다음을 수행합니다.

    gwc.exe -c myfile.txt
    

    이제 남은 97개의 coreutils를 구현할 시간입니다!

    좋은 웹페이지 즐겨찾기