Ввод из конвейера и файла
Проверить программу руками на одном вводе — минута. На пяти вводах — пять раз набирать одно и то же, и легко опечататься в самом наборе. Проверка курса за клавиатурой тоже не сидит. Ввод можно подать программе не с клавиатуры, а из другой команды или из файла — сама программа при этом не меняется ни на строку.
Конвейер: |
Ситуация. Нужно запустить программу приёмки на числе 40, не набирая его руками.
В scratch/main.go — программа «Сколько штук пришло?» с первой страницы.
stagiaire@lab:~/gocourse/day07/scratch$ echo 40 | go run .
Сколько штук пришло? Вы ввели: 40Как это читать. echo 40 печатает 40 и перевод строки. Знак | — конвейер: то, что левая команда напечатала бы на экран, становится вводом правой. Программе всё равно, набрал строку человек или её прислал echo: scanner.Scan() в обоих случаях читает символы до перевода строки.
Почему теперь всё в одной строке? С клавиатуры вы нажимали Enter, и терминал показывал переход на новую строку — это было эхо вашего набора, а не вывод программы. Из конвейера ничего не набирается и эха нет. Сама программа напечатала только Сколько штук пришло? и Вы ввели: 40.
Две строки ввода — через printf, в котором \n означает перевод строки. С программой «Товар / Склад»:
stagiaire@lab:~/gocourse/day07/scratch$ printf 'Болт М6\nСевер\n' | go run .
Товар: Склад: Принято: Болт М6 на склад Северecho 3 5 — это одна строка 3 5, а не две. Каждое значение — на своей строке: printf '3\n5\n'.
Файл: < и >
Ввод, который нужен часто, удобно держать в файле. Файл можно создать в редакторе, а можно одной командой:
stagiaire@lab:~/gocourse/day07/scratch$ printf '40\n' > input.txtЗнак > отправляет в файл то, что команда напечатала бы на экран. Файла не было — он появится; был — перезапишется без предупреждения.
Знак < делает обратное: подаёт содержимое файла программе на ввод.
stagiaire@lab:~/gocourse/day07/scratch$ go run . < input.txt
Сколько штук пришло? Вы ввели: 40Их можно совместить: ввод из файла, вывод в файл. На экране пусто, весь вывод — в out.txt:
stagiaire@lab:~/gocourse/day07/scratch$ go run . < input.txt > out.txt
stagiaire@lab:~/gocourse/day07/scratch$ cat out.txt
Сколько штук пришло? Вы ввели: 40Сводка:
команда1 | команда2— вывод первой команды становится вводом второй;команда < файл— ввод из файла вместо клавиатуры;команда > файл— вывод в файл вместо экрана, старое содержимое стирается.
<, > и | — это не Go. Их понимает bash, ещё до запуска программы: он открывает файлы и соединяет команды трубой. Программа об этом не знает. Для неё os.Stdin — всегда просто «откуда читать», а fmt.Println — «куда печатать». Поэтому одна и та же программа, без правок, работает и с клавиатурой, и с файлом, и в конвейере. Проверка курса пользуется тем же: подаёт вашей программе заранее заготовленные вводы.
Попробуйте сейчас: конвейер и файлы.
Цель: подать программе ввод через
|и<и отправить её вывод в файл через>.1. Наберите в
scratch/main.goпрограмму «Товар / Склад» — ту же, что на первой странице:✎ Наберите в файлscratch/main.gopackage main import ( "bufio" "fmt" "os" ) func main() { scanner := bufio.NewScanner(os.Stdin) fmt.Print("Товар: ") scanner.Scan() item := scanner.Text() fmt.Print("Склад: ") scanner.Scan() store := scanner.Text() fmt.Println("Принято:", item, "на склад", store) }2. В папке
scratchвыполните по очереди:▶ Выполнитеcd ~/gocourse/day07/scratch printf 'Болт М6\nСевер\n' | go run . printf 'Гайка М8\nЮг\n' > input.txt go run . < input.txt go run . < input.txt > out.txt cat out.txt echo Шайба | go run .3. В последнем запуске одна переменная осталась пустой. Какая и почему? Ответьте себе до того, как читать дальше: разбор — в первых строках следующего раздела, «
Scan()отвечает, прочитал ли строку».Готово, когда: первая команда напечатала
Товар: Склад: Принято: Болт М6 на склад Север,cat out.txtпоказал то же, чтоgo run . < input.txtпечатал на экран, а ваш ответ про пустую переменную сверен со следующим разделом (пункты проверкиt_pipe,t_infile,t_outfile).
Scan() отвечает, прочитал ли строку
Ситуация. В последнем опыте склад остался пустым, а программа молча напечатала «на склад» и пустое место. Нужно отличать «строку прочитали» от «ввод кончился».
scanner.Scan() не только читает, но и возвращает ответ типа bool: true, если строка прочитана, и false, если читать больше нечего. До сих пор этот ответ пропадал. Посмотрим на него:
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
scanner := bufio.NewScanner(os.Stdin)
ok := scanner.Scan()
fmt.Println(ok, "["+scanner.Text()+"]")
ok = scanner.Scan()
fmt.Println(ok, "["+scanner.Text()+"]")
}stagiaire@lab:~/gocourse/day07/scratch$ echo 7 | go run .
true [7]
false []echo 7 дал одну строку. Первый Scan() её прочитал — true. Второму читать нечего — false, и Text() отдаёт пустую строку.
Теперь то же с условием из дня 06:
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
scanner := bufio.NewScanner(os.Stdin)
if !scanner.Scan() {
fmt.Println("нет данных")
return
}
fmt.Println("строка:", scanner.Text())
}stagiaire@lab:~/gocourse/day07/scratch$ echo 7 | go run .
строка: 7
stagiaire@lab:~/gocourse/day07/scratch$ echo | go run .
строка:
stagiaire@lab:~/gocourse/day07/scratch$ go run . < /dev/null
нет данныхКак это читать. if !scanner.Scan() — «если строку прочитать не удалось». Scan() выполняется прямо внутри условия: строка читается здесь, второй вызов перед Text() не нужен. return внутри main завершает программу — строки ниже не выполняются.
echo без аргументов печатает пустую строку. Это всё-таки строка: Scan() вернул true, Text() — "". Пустая строка и отсутствие ввода — два разных случая. /dev/null — системный «пустой файл»: чтение из него заканчивается сразу. С клавиатуры то же самое даёт Ctrl+D, нажатый на пустой строке.
Пробелы по краям: strings.TrimSpace
Ситуация. Человек поставил пробел перед числом, файл пришёл с табуляцией в конце строки. Глазами этого не видно, а в строке эти символы есть.
Напечатаем строку в квадратных скобках и её длину:
package main
import (
"bufio"
"fmt"
"os"
"strings"
)
func main() {
scanner := bufio.NewScanner(os.Stdin)
scanner.Scan()
line := scanner.Text()
fmt.Printf("[%s] %d\n", line, len(line))
clean := strings.TrimSpace(line)
fmt.Printf("[%s] %d\n", clean, len(clean))
}stagiaire@lab:~/gocourse/day07/scratch$ printf ' 40 \t\n' | go run .
[ 40 ] 6
[40] 2Как это читать. В строке шесть байт: два пробела, 4, 0, пробел и табуляция. strings.TrimSpace возвращает новую строку без пробелов, табуляций и переводов строк по краям; то, что внутри, не трогает: из 1 000 получится 1 000. Скобки в Printf нужны только затем, чтобы края стали видны.
Файлы, сохранённые в Windows, заканчивают строки двумя символами — \r\n. scanner сам убирает \r вместе с переводом строки:
stagiaire@lab:~/gocourse/day07/scratch$ printf '40\r\n' | go run .
[40] 2
[40] 2Правило на весь курс: строку, прочитанную с ввода, сначала пропускайте через strings.TrimSpace, потом работайте с ней. На следующей странице будет видно, что без этого ломается.
Что может пойти не так
| Что видите | Что это значит | Что делать |
|---|---|---|
bash: input.txt: No such file or directory, программа не запустилась |
файла для < нет в текущей папке |
ls — где файл; создать его или указать путь |
go run . input.txt — программа ждёт |
имя файла после точки программа не читает, она ждёт клавиатуру | Ctrl+C, потом go run . < input.txt |
после echo 3 5 вторая переменная пустая |
3 5 — одна строка |
printf '3\n5\n' |
запустили go run . > out.txt, вопроса нет, программа молчит |
приглашение ушло в файл вместе с остальным выводом, а программа ждёт клавиатуру | набрать ввод и Enter вслепую или подать ввод через < |
из out.txt пропало старое содержимое |
> перезаписывает файл |
нужный файл — под другим именем |
[ 40 ] — в строке лишние пробелы |
пробелы пришли с вводом | strings.TrimSpace |
второй scanner ничего не прочитал, хотя строк две |
создано два scanner из os.Stdin: первый забрал ввод с запасом |
один scanner на всю программу |
Попробуйте сейчас: пробелы по краям и пустой ввод.
Цель: увидеть, что
strings.TrimSpaceубирает только края, и отличить ввод, которого нет, от пустой строки.1. Наберите в
scratch/main.goпрограмму сstrings.TrimSpace:✎ Наберите в файлscratch/main.gopackage main import ( "bufio" "fmt" "os" "strings" ) func main() { scanner := bufio.NewScanner(os.Stdin) scanner.Scan() line := scanner.Text() fmt.Printf("[%s] %d\n", line, len(line)) clean := strings.TrimSpace(line) fmt.Printf("[%s] %d\n", clean, len(clean)) }Проверьте её на разных краях:
▶ Выполнитеcd ~/gocourse/day07/scratch printf ' 40 \t\n' | go run . printf '1 000\n' | go run . go run . < nosuch.txt2. Потом добавьте в неё проверку
if !scanner.Scan()из программы выше и запустите с< /dev/null:▶ Выполнитеgo run . < /dev/nullГотово, когда: на первый ввод программа напечатала длину 6, а потом
[40] 2; у1 000пробел внутри остался; наnosuch.txtbash ответилNo such file or directory, и программа не запустилась; с< /dev/nullсработала веткаif !scanner.Scan().
Дальше: строка в число — strconv.Atoi — course next