Учебник по интерфейсу командной строки: Fortune Clone

Я написал два руководства по созданию приложений с интерфейсом командной строки.гололкотиGocowsay. В обоих я использовалfortuneкак входной генератор.

В этой статье я завершу триологию трубок с помощьюgofortune.

Чтоудача, первый?Как говорит Википедия, Fortune - это простая программа, отображающая псевдослучайное сообщение из базы данных котировок.

По сути, генератор случайных цитат.

Он имеет очень долгую историю, восходящую к Unix Version 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>)
}

}

Давайте поместим эти значения в срез, чтобы позже я мог выбрать случайное: я определяюfilesкусок строк, и я добавляю к нему в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: