Файл по имени
До сих пор ввод приходил от терминала: < input.txt или конвейер. Выгрузка же лежит файлом с известным именем, и программа открывает его сама — целиком или строка за строкой. У каждого способа своя ловушка.
os.ReadFile: весь файл сразу
Ситуация. В каталоге лежит orders.csv, программа должна пройти его строки.
package main
import (
"fmt"
"os"
"strings"
)
func main() {
data, err := os.ReadFile("orders.csv")
if err != nil {
fmt.Fprintln(os.Stderr, "ошибка:", err)
os.Exit(1)
}
lines := strings.Split(string(data), "\n")
fmt.Println("строк:", len(lines))
for i, line := range lines {
fmt.Printf("%d: [%s]\n", i+1, line)
}
}Как это читать. os.ReadFile("orders.csv") возвращает содержимое файла и ошибку. Содержимое — []byte, срез байтов со страницы 1; string(data) превращает его в строку. Дальше strings.Split режет строку по переводу строки "\n" — получается срез строк файла.
Имя без каталога — orders.csv — значит «файл в текущем каталоге»: в том, где стоит терминал, когда запускают программу.
Попробуйте сейчас: что выведет Split файла.
Цель: предсказать, сколько частей даст
Splitдля файла из трёх строк, и сдать ответ.1. Создайте файл из трёх строк; каждая, и последняя тоже, кончается
\n— так сохраняют файлы редакторы:▶ Выполните · выделенное замените своимcd ~/gocourse/day19/scratch printf 'TK-101;Болт М8;12;1500\nTK-102;Шайба;100;90\nTK-103;Гайка М10;40;350\n' > orders.csv wc -l orders.csv
wc -lсчитает переводы строки и напечатает3 orders.csv.2. Не запуская программу, ответьте: какое число она напечатает после
строк:?▶ Выполните · выделенное замените своимcourse answer day19.q4 ЧИСЛО3. Наберите программу из раздела выше и запустите:
✎ Наберите в файлscratch/main.gopackage main import ( "fmt" "os" "strings" ) func main() { data, err := os.ReadFile("orders.csv") if err != nil { fmt.Fprintln(os.Stderr, "ошибка:", err) os.Exit(1) } lines := strings.Split(string(data), "\n") fmt.Println("строк:", len(lines)) for i, line := range lines { fmt.Printf("%d: [%s]\n", i+1, line) } }▶ Выполнитеgo run .Готово, когда:
course answerответилПринято: day19.q4 = …(пункт проверкиq4), программа запущена.
Что увидите.
stagiaire@lab:~/gocourse/day19/scratch$ go run .
строк: 4
1: [TK-101;Болт М8;12;1500]
2: [TK-102;Шайба;100;90]
3: [TK-103;Гайка М10;40;350]
4: []Как это читать. Split режет по каждому "\n". Последний перевод строки стоит в самом конце файла, и после него остаётся пустой кусок — четвёртая «строка», которой в файле нет. Программа отчёта сочла бы её битой: «строка 4: пустая строка».
Перевод строки в конце файла — не пустая строка. Его убирают до Split:
text := strings.TrimSuffix(string(data), "\n")
lines := strings.Split(text, "\n")strings.TrimSuffix(s, "\n") убирает один "\n" в конце, если он есть. Пустая строка в середине файла остаётся — она настоящая.
stagiaire@lab:~/gocourse/day19/scratch$ go run .
строк: 3
1: [TK-101;Болт М8;12;1500]
2: [TK-102;Шайба;100;90]
3: [TK-103;Гайка М10;40;350]bufio.Scanner по файлу
Scanner из дня 07 читал строки из os.Stdin. Файл для него то же самое: его открывают os.Open и отдают Scanner вместо os.Stdin.
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
f, err := os.Open("orders.csv")
if err != nil {
fmt.Fprintln(os.Stderr, "ошибка:", err)
os.Exit(1)
}
scanner := bufio.NewScanner(f)
n := 0
for scanner.Scan() {
n++
fmt.Printf("%d: [%s]\n", n, scanner.Text())
}
f.Close()
fmt.Println("строк:", n)
}Что увидите.
stagiaire@lab:~/gocourse/day19/scratch$ go run .
1: [TK-101;Болт М8;12;1500]
2: [TK-102;Шайба;100;90]
3: [TK-103;Гайка М10;40;350]
строк: 3Как это читать. os.Open не читает файл, а открывает и отдаёт f. Scanner берёт из него строку за строкой и сам знает, что перевод строки в конце — не новая строка. Номер строки считаем сами, счётчиком n.
f.Close() закрывает файл: программа, которая открывает тысячи файлов и не закрывает, однажды получит отказ. Закрывать нужно на каждом пути выхода; удобнее это делает defer — блок 6.
os.ReadFile + Split |
os.Open + Scanner + Close |
|
|---|---|---|
| как читает | весь файл в память сразу | по строке |
| перевод строки в конце | нужен TrimSuffix |
учитывает сам |
| номер строки | i+1 из range |
свой счётчик |
| когда хуже | файл на гигабайты | нужно помнить про Close |
Для выгрузок этого дня годятся оба.
Текущий каталог
Та же программа, запущенная из каталога выше:
stagiaire@lab:~/gocourse/day19/scratch$ cd ~/gocourse/day19
stagiaire@lab:~/gocourse/day19$ go run ./scratch
ошибка: open orders.csv: no such file or directory
exit status 1go run ./scratch собрал программу из scratch, а запустил в текущем каталоге — ~/gocourse/day19, где orders.csv нет. Файл ищется там, где стоит терминал. После ошибка: — ошибка os.Open целиком: что делали, с каким файлом и что не так.
Попробуйте сейчас: файл через Scanner.
Цель: прочитать тот же файл вторым способом и увидеть ошибку открытия.
1. Наберите программу из раздела «
bufio.Scannerпо файлу»:✎ Наберите в файлscratch/main.gopackage main import ( "bufio" "fmt" "os" ) func main() { f, err := os.Open("orders.csv") if err != nil { fmt.Fprintln(os.Stderr, "ошибка:", err) os.Exit(1) } scanner := bufio.NewScanner(f) n := 0 for scanner.Scan() { n++ fmt.Printf("%d: [%s]\n", n, scanner.Text()) } f.Close() fmt.Println("строк:", n) }▶ Выполнитеcd ~/gocourse/day19/scratch go run . cd ~/gocourse/day19 go run ./scratchГотово, когда: в
scratchпрограмма напечатала три строки истрок: 3, а из~/gocourse/day19—ошибка: open orders.csv: no such file or directory. Проверка дня этот опыт не сверяет.
Попробуйте сейчас: починка fix2.
Цель:
fix2не считает конец файла битой строкой, а настоящие битые строки по-прежнему находит.1. Посмотрите на файл и запустите программу:
▶ Выполнитеcd ~/gocourse/day19/fix2 cat stock.txt go run . echo $?Что должно получиться:
Вывод · это печатает программа, набирать не нужноБолт М8;120 Шайба;450 Гайка М10;75 Bolt-M8;30 Болт М8: 120 шт. Шайба: 450 шт. Гайка М10: 75 шт. Bolt-M8: 30 шт. строка 5: нужно 2 поля, а их 1 exit status 1 1В файле четыре строки, а программа жалуется на пятую.
2. Почините — до трёх изменённых строк. Пустая строка в середине файла по-прежнему битая: проверка подаст и такой файл.
▶ Выполнитеgo run . echo $? diff ~/.course/orig/day19/fix2/main.go main.go3. Закоммитьте:
▶ Выполнитеcd ~/gocourse/day19 git add . git commit -m "fix2: конец файла не пустая строка"Готово, когда: четыре строки остатков, поток ошибок пуст,
echo $?печатает0(пунктыfix2иfixsmall).
Что может пойти не так
| Что видите | Что это значит | Что делать |
|---|---|---|
open orders.csv: no such file or directory |
в текущем каталоге файла нет: программа запущена не там или в имени опечатка | cd в каталог с файлом; ls покажет, что там лежит |
open orders.csv: permission denied |
файл есть, но читать его этому пользователю нельзя | ls -l orders.csv покажет права |
cannot use data (variable of type []byte) as string value in argument to strings.Split |
ReadFile отдаёт байты, а Split ждёт строку |
strings.Split(string(data), "\n") |
declared and not used: f |
файл открыли, но не читали и не закрыли | bufio.NewScanner(f) и f.Close() |
| лишняя битая строка с номером за концом файла | Split после последнего перевода строки дал пустой кусок |
strings.TrimSuffix(string(data), "\n") до Split |
| пустая строка в середине файла не замечена | пустые строки пропускаются все подряд | пропускать только кусок после последнего перевода строки |
exit status 1 под выводом |
go run сообщает код возврата программы |
ничего: при ошибках так и должно быть |
Дальше: отчёт по выгрузке целиком — course next