Go CLI التعليمي: Fortune clone

لقد كتبت دورتين تعليميتين لتطبيق CLI للبناءجلولكاتوغوكوساي. في كلتا اعتدتfortuneكمولد الإدخال.

في هذه المقالة ، سوف أكمل علم الأنبوب باستخدامgofortune.

ما هوحظ، أول؟كما تقول ويكيبيديا، Fortune هو برنامج بسيط يعرض رسالة شبه عشوائية من قاعدة بيانات للاقتباسات.

في الأساس ، مولد اقتباس عشوائي.

لها تاريخ طويل جدًا يعود إلى الإصدار 7 من نظام يونكس (1979). لا تزال قوية. تقوم العديد من توزيعات Linux بتثبيته مسبقًا ، ويمكنك تثبيته على OSX باستخدامbrew install fortune.

في بعض الأنظمة ، يتم استخدامه كرسالة ترحيب أو فراق عند استخدام الأصداف.

ويكيبيديا تقول أيضا

كثير من الناس يختارون ضخ الثروة فيالبقرالأمر ، لإضافة المزيد من الدعابة إلى الحوار.

هذا أنا! إلا أنني استخدمgocowsayيأمر.

يكفي مع المقدمة ،دعونا نبني استنساخ ثروة مع Go.

فيما يلي تفصيل لما سيفعله برنامجنا.

يعتمد موقع مجلد Fortunes على النظام والتوزيع ، كونه علامة بناء. يمكنني ترميزها أو استخدام متغير بيئة ولكن كتدريب ، سأفعل شيئًا قذرًا وأسألfortuneمباشرة ، من خلال تنفيذه مع-fالعلم ، والذي ينتج عنه:

يحتوي السطر الأول من الإخراج على مسار مجلد الثروات.

package main

import ( “fmt” “os/exec” )

func main() { out, err := exec.Command(“fortune”, “-f”).CombinedOutput() if err != nil { panic(err) }

<span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Println</span>(string(<span style="color:#a6e22e">out</span>))

}

هذا المقتطف يكرر الإخراج تمامًا كما حصلت عليه. يبدو أنfortune -fيكتب الإخراج إلى stderr ، ولهذا السبب كنت أستخدمCombinedOutput، للحصول على كليهماstdoutوstderr.

لكني أريد فقط السطر الأول. كيف افعلها؟ هذا يطبع كل إخراجstderrسطرا سطرا:

package main

import ( “bufio” “fmt” “os/exec” )

func main() { fortuneCommand := exec.Command(“fortune”, “-f”) pipe, err := fortuneCommand.StderrPipe() if err != nil { panic(err) } for outputStream.Scan() { fmt.Println(outputStream.Text()) } }

للحصول على السطر الأول فقط ، أزل حلقة for ، وفحص السطر الأول فقط:

package main

import ( “bufio” “fmt” “os/exec” )

func main() { fortuneCommand := exec.Command(“fortune”, “-f”) pipe, err := fortuneCommand.StderrPipe() if err != nil { panic(err) } fortuneCommand.Start() outputStream := bufio.NewScanner(pipe) outputStream.Scan() fmt.Println(outputStream.Text()) }

الآن دعنا نختار هذا الخط ونستخرج المسار.

في نظامي ، السطر الأول من الإخراج هو100.00% /usr/local/Cellar/fortune/9708/share/games/fortunes. لنقم بإنشاء سلسلة فرعية تبدأ من أول ظهور لـ/شار:

line := outputStream.Text()
path := line[strings.Index(line, "/"):]

الآن لدي طريق الثروات. يمكنني فهرسة الملفات الموجودة هناك. يوجد.datالملفات الثنائية والملفات النصية العادية. سأقوم بتجاهل الملفات الثنائية ، وoff/مجلد تمامًا ، والذي يحتوي على ثروات هجومية.

لنفهرس الملفات أولاً. انا استعمل الpath/filepathصفقةWalkطريقة لتكرار شجرة الملف بدءًا منroot. أنا استخدمه بدلا منioutil.ReadDir()لأنه قد يكون لدينا مجلدات ثروات متداخلة. في الWalkFunc visitأتجاهل ملفات dat باستخدام ملفاتfilepath.Ext()، أنا أتجاهل ملفات المجلد (على سبيل المثال/off، ولكن ليس الملفات الموجودة في المجلدات الفرعية) وجميع الثروات الهجومية ، الموجودة في مكان ملائم أسفل/off، وأقوم بطباعة قيمة كل ملف متبقي.

func visit(path string, f os.FileInfo, err error) error {
	if strings.Contains(path, "/off/") {
		return nil
	}
	if filepath.Ext(path) == ".dat" {
		return nil
	}
	if f.IsDir() {
		return nil
	}
	files = append(files, path)
	return nil
}

func main() { fortuneCommand := exec.Command(“fortune”, “-f”) pipe, err := fortuneCommand.StderrPipe() if err != nil { panic(err) } fortuneCommand.Start() outputStream := bufio.NewScanner(pipe) outputStream.Scan() line := outputStream.Text() root := line[strings.Index(line, “/”):]

<span style="color:#a6e22e">err</span> = <span style="color:#a6e22e">filepath</span>.<span style="color:#a6e22e">Walk</span>(<span style="color:#a6e22e">root</span>, <span style="color:#a6e22e">visit</span>)
<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
	panic(<span style="color:#a6e22e">err</span>)
}

}

دعنا نضع هذه القيم في شريحة ، حتى أتمكن لاحقًا من اختيار واحدة عشوائية: أعرّف afilesشريحة من السلاسل وألحق ذلك فيvisit()وظيفة. في نهايةmain()أنا أطبع عدد الملفات التي حصلت عليها.

package main

import ( “bufio” “log” “os” “os/exec” “path/filepath” “strings” )

var files []string

func visit(path string, f os.FileInfo, err error) error { if err != nil { log.Fatal(err) } if strings.Contains(path, “/off/”) { return nil } if filepath.Ext(path) == “.dat” { return nil } if f.IsDir() { return nil } files = append(files, path) return nil }

func main() { fortuneCommand := exec.Command(“fortune”, “-f”) pipe, err := fortuneCommand.StderrPipe() if err != nil { panic(err) } fortuneCommand.Start() outputStream := bufio.NewScanner(pipe) outputStream.Scan() line := outputStream.Text() root := line[strings.Index(line, “/”):]

<span style="color:#a6e22e">err</span> = <span style="color:#a6e22e">filepath</span>.<span style="color:#a6e22e">Walk</span>(<span style="color:#a6e22e">root</span>, <span style="color:#a6e22e">visit</span>)
<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
	panic(<span style="color:#a6e22e">err</span>)
}

println(len(<span style="color:#a6e22e">files</span>))

}

أنا الآن أستخدم ملفالذهاب مولد الأرقام العشوائيةوظيفة لاختيار عنصر عشوائي من المصفوفة:

// Returns an int >= min, < max
func randomInt(min, max int) int {
	return min + rand.Intn(max-min)
}

func main() {

<span style="color:#75715e">//...

rand.Seed(time.Now().UnixNano()) i := randomInt(1, len(files)) randomFile := files[i] println(randomFile) }

يقوم برنامجنا الآن بطباعة اسم عشوائي لملف الثروة في كل مرة.

ما افتقده الآن هو فحص الثروات في ملف ، وطباعة ملف عشوائي. في كل ملف ، يتم الفصل بين علامات الاقتباس بامتداد%يجلس على خط من تلقاء نفسه. يمكنني بسهولة اكتشاف هذا النمط ومسح كل اقتباس في المصفوفة:

file, err := os.Open(randomFile)
if err != nil {
    panic(err)
}
defer file.Close()

b, err := ioutil.ReadAll(file) if err != nil { panic(err) }

quotes := string(b)

quotesSlice := strings.Split(quotes, “%”) j := randomInt(1, len(quotesSlice))

fmt.Print(quotesSlice[j])

هذا ليس فعالًا حقًا ، لأنني أقوم بمسح ملف الثروة بالكامل في شريحة ثم أختار عنصرًا عشوائيًا ، لكنه يعمل:

إذن ، ها هي النسخة النهائية من برنامجنا الأساسي للغايةfortuneاستنساخ. يفتقد الكثير من الأصلfortuneالأمر ، لكنها بداية.

package main

import ( “bufio” “fmt” “io/ioutil” “log” “math/rand” “os” “os/exec” “path/filepath” “strings” “time” )

var files []string

// Returns an int >= min, < max func randomInt(min, max int) int { return min + rand.Intn(max-min) }

func visit(path string, f os.FileInfo, err error) error { if err != nil { log.Fatal(err) } if strings.Contains(path, “/off/”) { return nil } if filepath.Ext(path) == “.dat” { return nil } if f.IsDir() { return nil } files = append(files, path) return nil }

func main() { fortuneCommand := exec.Command(“fortune”, “-f”) pipe, err := fortuneCommand.StderrPipe() if err != nil { panic(err) } fortuneCommand.Start() outputStream := bufio.NewScanner(pipe) outputStream.Scan() line := outputStream.Text() root := line[strings.Index(line, “/”):]

<span style="color:#a6e22e">err</span> = <span style="color:#a6e22e">filepath</span>.<span style="color:#a6e22e">Walk</span>(<span style="color:#a6e22e">root</span>, <span style="color:#a6e22e">visit</span>)
<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
	panic(<span style="color:#a6e22e">err</span>)
}

<span style="color:#a6e22e">rand</span>.<span style="color:#a6e22e">Seed</span>(<span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Now</span>().<span style="color:#a6e22e">UnixNano</span>())
<span style="color:#a6e22e">i</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">randomInt</span>(<span style="color:#ae81ff">1</span>, len(<span style="color:#a6e22e">files</span>))
<span style="color:#a6e22e">randomFile</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">files</span>[<span style="color:#a6e22e">i</span>]

<span style="color:#a6e22e">file</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">os</span>.<span style="color:#a6e22e">Open</span>(<span style="color:#a6e22e">randomFile</span>)
<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
	panic(<span style="color:#a6e22e">err</span>)
}
<span style="color:#66d9ef">defer</span> <span style="color:#a6e22e">file</span>.<span style="color:#a6e22e">Close</span>()

<span style="color:#a6e22e">b</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">ioutil</span>.<span style="color:#a6e22e">ReadAll</span>(<span style="color:#a6e22e">file</span>)
<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
	panic(<span style="color:#a6e22e">err</span>)
}

<span style="color:#a6e22e">quotes</span> <span style="color:#f92672">:=</span> string(<span style="color:#a6e22e">b</span>)

<span style="color:#a6e22e">quotesSlice</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">strings</span>.<span style="color:#a6e22e">Split</span>(<span style="color:#a6e22e">quotes</span>, <span style="color:#e6db74">"%"</span>)
<span style="color:#a6e22e">j</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">randomInt</span>(<span style="color:#ae81ff">1</span>, len(<span style="color:#a6e22e">quotesSlice</span>))

<span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Print</span>(<span style="color:#a6e22e">quotesSlice</span>[<span style="color:#a6e22e">j</span>])

}

في الختام ، أتحركvisitكوسيطة دالة مضمنة منfilepath.Walkوتتحركfilesليكون متغيرًا محليًا بالداخلmain()بدلاً من متغير الملف العام:

package main

import ( “bufio” “fmt” “io/ioutil” “log” “math/rand” “os” “os/exec” “path/filepath” “strings” “time” )

// Returns an int >= min, < max func randomInt(min, max int) int { return min + rand.Intn(max-min) }

func main() { var files []string

<span style="color:#a6e22e">fortuneCommand</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">exec</span>.<span style="color:#a6e22e">Command</span>(<span style="color:#e6db74">"fortune"</span>, <span style="color:#e6db74">"-f"</span>)
<span style="color:#a6e22e">pipe</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">fortuneCommand</span>.<span style="color:#a6e22e">StderrPipe</span>()
<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
	panic(<span style="color:#a6e22e">err</span>)
}
<span style="color:#a6e22e">fortuneCommand</span>.<span style="color:#a6e22e">Start</span>()
<span style="color:#a6e22e">outputStream</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">bufio</span>.<span style="color:#a6e22e">NewScanner</span>(<span style="color:#a6e22e">pipe</span>)
<span style="color:#a6e22e">outputStream</span>.<span style="color:#a6e22e">Scan</span>()
<span style="color:#a6e22e">line</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">outputStream</span>.<span style="color:#a6e22e">Text</span>()
<span style="color:#a6e22e">root</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">line</span>[<span style="color:#a6e22e">strings</span>.<span style="color:#a6e22e">Index</span>(<span style="color:#a6e22e">line</span>, <span style="color:#e6db74">"/"</span>):]

<span style="color:#a6e22e">err</span> = <span style="color:#a6e22e">filepath</span>.<span style="color:#a6e22e">Walk</span>(<span style="color:#a6e22e">root</span>, <span style="color:#66d9ef">func</span>(<span style="color:#a6e22e">path</span> <span style="color:#66d9ef">string</span>, <span style="color:#a6e22e">f</span> <span style="color:#a6e22e">os</span>.<span style="color:#a6e22e">FileInfo</span>, <span style="color:#a6e22e">err</span> <span style="color:#66d9ef">error</span>) <span style="color:#66d9ef">error</span> {
    <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
        <span style="color:#a6e22e">log</span>.<span style="color:#a6e22e">Fatal</span>(<span style="color:#a6e22e">err</span>)
    }
	<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">strings</span>.<span style="color:#a6e22e">Contains</span>(<span style="color:#a6e22e">path</span>, <span style="color:#e6db74">"/off/"</span>) {
		<span style="color:#66d9ef">return</span> <span style="color:#66d9ef">nil</span>
	}
	<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">filepath</span>.<span style="color:#a6e22e">Ext</span>(<span style="color:#a6e22e">path</span>) <span style="color:#f92672">==</span> <span style="color:#e6db74">".dat"</span> {
		<span style="color:#66d9ef">return</span> <span style="color:#66d9ef">nil</span>
	}
	<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">f</span>.<span style="color:#a6e22e">IsDir</span>() {
		<span style="color:#66d9ef">return</span> <span style="color:#66d9ef">nil</span>
	}
	<span style="color:#a6e22e">files</span> = append(<span style="color:#a6e22e">files</span>, <span style="color:#a6e22e">path</span>)
	<span style="color:#66d9ef">return</span> <span style="color:#66d9ef">nil</span>
})
<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
	panic(<span style="color:#a6e22e">err</span>)
}

<span style="color:#a6e22e">rand</span>.<span style="color:#a6e22e">Seed</span>(<span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Now</span>().<span style="color:#a6e22e">UnixNano</span>())
<span style="color:#a6e22e">i</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">randomInt</span>(<span style="color:#ae81ff">1</span>, len(<span style="color:#a6e22e">files</span>))
<span style="color:#a6e22e">randomFile</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">files</span>[<span style="color:#a6e22e">i</span>]

<span style="color:#a6e22e">file</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">os</span>.<span style="color:#a6e22e">Open</span>(<span style="color:#a6e22e">randomFile</span>)
<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
	panic(<span style="color:#a6e22e">err</span>)
}
<span style="color:#66d9ef">defer</span> <span style="color:#a6e22e">file</span>.<span style="color:#a6e22e">Close</span>()

<span style="color:#a6e22e">b</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">ioutil</span>.<span style="color:#a6e22e">ReadAll</span>(<span style="color:#a6e22e">file</span>)
<span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
	panic(<span style="color:#a6e22e">err</span>)
}

<span style="color:#a6e22e">quotes</span> <span style="color:#f92672">:=</span> string(<span style="color:#a6e22e">b</span>)

<span style="color:#a6e22e">quotesSlice</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">strings</span>.<span style="color:#a6e22e">Split</span>(<span style="color:#a6e22e">quotes</span>, <span style="color:#e6db74">"%"</span>)
<span style="color:#a6e22e">j</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">randomInt</span>(<span style="color:#ae81ff">1</span>, len(<span style="color:#a6e22e">quotesSlice</span>))

<span style="color:#a6e22e">fmt</span>.<span style="color:#a6e22e">Print</span>(<span style="color:#a6e22e">quotesSlice</span>[<span style="color:#a6e22e">j</span>])

}

أستطيع الآنgo build; go installوعلم التثليثgofortune gocowsayوgololcatقد اكتمل:


المزيد من دروس Go: