Go에서 GNU Coreutils를 재생성하여 기술 향상
재미를 위한 바퀴 재발명
프로그래밍 기술을 습득하기 위한 가장 인기 있는 조언 중 하나는 이미 존재하는 도구를 다시 구현하라는 것입니다. 예를 들어 CRUD API를 수행하는 방법을 배우고 싶다면 "To do"앱으로 연습했을 것입니다. 이것의 요점은 새롭고 획기적인 것을 창조하는 것이 아니라 잘 알려진 아이디어를 사용하여 여러분이 알고 있는 것을 개선하는 것입니다. 당신이 본 다른 예는 websocket, 날씨 앱, Hacker News 리더에 대해 배우기 위해 채팅 서버를 구현하는 것과 같은 것입니다.
오늘 저는 제가 더 흥미롭게 생각하는 GNU Coreutils 유틸리티 재구현에 대해 이야기하고 있습니다. 내가 이것을 하는 첫 번째 사람은 아니지만, 나는 당신이 선택한 언어로 훨씬 더 나은 기술을 가지고 이 연습을 통해 나올 것이라고 확신합니다.
그렇다면 Coreutils는 무엇입니까? Linux 또는 Mac을 사용하는 경우 아마도 많은 것을 사용했을 것입니다. 경우에 따라 Windows에서도 가능합니다. Take a look at this link for more information .
다양한 방식으로 이 프로젝트에 접근할 수 있습니다. 제 경우에는 coreutils의 요점이 정말 작고 정말 단순하다는 것입니다. 하나의 작업만 수행하지만 잘 수행합니다. C의 원래 저장소에서 대부분의 도구는 하나의 단일 파일입니다. 저도 같은 생각으로 시작하겠습니다.
보다시피 이것은 진지한 구조화된 연습이라기보다는 매우 자유로운 운동에 가깝습니다. 추가 라이브러리를 사용할지 여부에 대해서도 매우 유연합니다. 예를 들어 Go의
flag
패키지는 설정하기가 약간 지루하다는 것을 깨달았습니다. 다른 플래그 패키지가 훨씬 좋습니다. 또한 -wl
, to get as close as possible to the real Coreutils functionality 대신 -w -l
와 같은 인수를 연결할 수 있는 인수를 찾아야 합니다.화장실
내가 선택한 첫 번째 도구는 word counter WC 입니다. 가장 어려운 것도, 가장 쉬운 것도 아닙니다. 나에게 요구 사항은 다음과 같습니다.
그래서 내 거친 스키마:
그리고 여기 코드가 있습니다. 앞에서 언급했듯이 유일한 외부 종속성은 내 개인 취향이지만 쉽게 피할 수 있습니다 (특히 최근에!). 그러나 이것을 초기 연습으로 삼는다면 결과는 나쁘지 않다고 생각합니다.
따라할 수 있도록 코드에 주석을 달았습니다.
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를 구현할 시간입니다!
Reference
이 문제에 관하여(Go에서 GNU Coreutils를 재생성하여 기술 향상), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://dev.to/rubenwap/upskill-yourself-by-recreating-gnu-coretools-in-go-9f5텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)