我是个新手,我正在试着写一个简单的脚本来逐行读取文件。我还希望将进度(即最后读取的行号)保存在文件系统的某个位置,这样,如果再次将相同的文件作为脚本的输入,脚本将从它停止的行开始读取文件。下面是我开始做的事情。
package main
// Package Imports
import (
"bufio"
"flag"
"fmt"
"log"
"os"
)
// Variable Declaration
var (
ConfigFile = flag.String("configfile", "../config.json", "Path to json configuration file.")
)
// The main function that reads the file and parses the log entries
func main() {
flag.Parse()
settings := NewConfig(*ConfigFile)
inputFile, err := os.Open(settings.Source)
if err != nil {
log.Fatal(err)
}
defer inputFile.Close()
scanner := bufio.NewScanner(inputFile)
for scanner.Scan() {
fmt.Println(scanner.Text())
}
if err := scanner.Err(); err != nil {
log.Fatal(err)
}
}
// Saves the current progress
func SaveProgress() {
}
// Get the line count from the progress to make sure
func GetCounter() {
}我在scanner包中找不到任何处理行号的方法。我知道我可以声明一个整数,比如counter := 0,并在每次像counter++一样读一行时递增它。但是下一次我如何告诉扫描器从特定的行开始呢?因此,例如,如果我下次使用相同的输入文件运行脚本时读取到行30,如何使扫描仪开始从行31读取
更新
我在这里能想到的一个解决方案是使用上面提到的计数器,并使用如下所示的if条件。
scanner := bufio.NewScanner(inputFile)
for scanner.Scan() {
if counter > progress {
fmt.Println(scanner.Text())
}
}我非常确定这样的东西会起作用,但它仍然会在我们已经读过的行上循环。请建议一个更好的方法。
发布于 2016-01-08 01:29:30
如果你不想读,而只是跳过你之前读过的行,你需要回到你离开的位置。
不同的解决方案以函数的形式给出,该函数接受从其读取的输入和开始读取行的开始位置(字节位置),例如:
func solution(input io.ReadSeeker, start int64) error还使用了一个特殊的io.Reader输入,它还实现了io.Seeker,这是一个公共接口,允许跳过数据而不必读取它们。*os.File实现了这一点,因此您可以将*File传递给这些函数。好的。io.Reader和io.Seeker的“合并”接口是io.ReadSeeker。
如果您想要一个干净的开始(从文件的开头开始读取),只需传递start = 0。如果要恢复之前的处理,请传递上次处理停止/中止的字节位置。此位置是以下函数(解决方案)中pos局部变量的值。
下面的所有示例及其测试代码都可以在Go Playground上找到。
1.使用bufio.Scanner
bufio.Scanner不维护位置,但我们可以很容易地扩展它来维护位置(读取的字节),所以当我们想要下一次重启时,我们可以寻找这个位置。
为了以最小的努力做到这一点,我们可以使用一个新的拆分函数,它将输入拆分成标记(行)。我们可以使用Scanner.Split()来设置拆分器函数(决定标记/行的边界的逻辑)。默认的拆分函数是bufio.ScanLines()。
让我们来看一下拆分函数声明:bufio.SplitFunc
type SplitFunc func(data []byte, atEOF bool) (advance int, token []byte, err error)它返回要前进的字节数:advance。这正是我们维护文件位置所需要的。因此,我们可以使用内置的bufio.ScanLines()创建一个新的拆分函数,这样我们甚至不必实现它的逻辑,只需使用advance返回值来维护位置:
func withScanner(input io.ReadSeeker, start int64) error {
fmt.Println("--SCANNER, start:", start)
if _, err := input.Seek(start, 0); err != nil {
return err
}
scanner := bufio.NewScanner(input)
pos := start
scanLines := func(data []byte, atEOF bool) (advance int, token []byte, err error) {
advance, token, err = bufio.ScanLines(data, atEOF)
pos += int64(advance)
return
}
scanner.Split(scanLines)
for scanner.Scan() {
fmt.Printf("Pos: %d, Scanned: %s\n", pos, scanner.Text())
}
return scanner.Err()
}2.使用bufio.Reader
在此解决方案中,我们使用bufio.Reader类型而不是Scanner。bufio.Reader已经有了一个ReadBytes()方法,如果我们将'\n'字节作为分隔符传递,它与“读取一行”功能非常相似。
这个解决方案类似于JimB的解决方案,除了处理所有有效的行终止符序列,还将它们从读取的行中剥离(很少需要它们);在正则表达式表示法中,它是\r?\n。
func withReader(input io.ReadSeeker, start int64) error {
fmt.Println("--READER, start:", start)
if _, err := input.Seek(start, 0); err != nil {
return err
}
r := bufio.NewReader(input)
pos := start
for {
data, err := r.ReadBytes('\n')
pos += int64(len(data))
if err == nil || err == io.EOF {
if len(data) > 0 && data[len(data)-1] == '\n' {
data = data[:len(data)-1]
}
if len(data) > 0 && data[len(data)-1] == '\r' {
data = data[:len(data)-1]
}
fmt.Printf("Pos: %d, Read: %s\n", pos, data)
}
if err != nil {
if err != io.EOF {
return err
}
break
}
}
return nil
}注意:如果内容以空行结束(行终止符),此解决方案将处理空行。如果你不想要这个,你可以像这样检查它:
if len(data) != 0 {
fmt.Printf("Pos: %d, Read: %s\n", pos, data)
} else {
// Last line is empty, omit it
}测试解决方案:
测试代码将简单地使用内容"first\r\nsecond\nthird\nfourth",它包含具有不同行终止符的多行。我们将使用strings.NewReader()来获取一个源为string的io.ReadSeeker。
测试代码首先调用withScanner()和withReader(),传递0 start position:一个干净的开始。在下一轮中,我们将传递start = 14的开始位置,这是3.行的位置,因此我们不会看到前2行被处理(打印):恢复模拟。
func main() {
const content = "first\r\nsecond\nthird\nfourth"
if err := withScanner(strings.NewReader(content), 0); err != nil {
fmt.Println("Scanner error:", err)
}
if err := withReader(strings.NewReader(content), 0); err != nil {
fmt.Println("Reader error:", err)
}
if err := withScanner(strings.NewReader(content), 14); err != nil {
fmt.Println("Scanner error:", err)
}
if err := withReader(strings.NewReader(content), 14); err != nil {
fmt.Println("Reader error:", err)
}
}输出:
--SCANNER, start: 0
Pos: 7, Scanned: first
Pos: 14, Scanned: second
Pos: 20, Scanned: third
Pos: 26, Scanned: fourth
--READER, start: 0
Pos: 7, Read: first
Pos: 14, Read: second
Pos: 20, Read: third
Pos: 26, Read: fourth
--SCANNER, start: 14
Pos: 20, Scanned: third
Pos: 26, Scanned: fourth
--READER, start: 14
Pos: 20, Read: third
Pos: 26, Read: fourth在Go Playground上尝试解决方案和测试代码。
发布于 2016-01-07 23:07:06
不使用Scanner,而是使用bufio.Reader,特别是ReadBytes或ReadString方法。这样,您可以读取到每个行尾,并且仍然可以接收带有行尾的整行。
r := bufio.NewReader(inputFile)
var line []byte
fPos := 0 // or saved position
for i := 1; ; i++ {
line, err = r.ReadBytes('\n')
fmt.Printf("[line:%d pos:%d] %q\n", i, fPos, line)
if err != nil {
break
}
fPos += len(line)
}
if err != io.EOF {
log.Fatal(err)
}您可以随意存储文件位置和行号的组合,下次开始时,您可以使用inputFile.Seek(fPos, os.SEEK_SET)移动到上次停止的位置。
发布于 2016-01-07 21:25:31
如果你想使用扫描仪,你必须通过乞求文件,直到你找到GetCounter()结束行符号。
scanner := bufio.NewScanner(inputFile)
// context line above
// skip first GetCounter() lines
for i := 0; i < GetCounter(); i++ {
scanner.Scan()
}
// context line below
for scanner.Scan() {
fmt.Println(scanner.Text())
}或者,您可以在计数器中存储offset而不是行号,但请记住,在使用扫描仪时,终止标记将被剥离,并且对于新行,标记为\r?\n (正则表达式表示法),因此不清楚您是否应该在文本长度上添加1或2:
// Not clear how to store offset unless custom SplitFunc provided
inputFile.Seek(GetCounter(), 0)
scanner := bufio.NewScanner(inputFile)因此,最好使用以前的解决方案,或者根本不使用Scanner。
https://stackoverflow.com/questions/34654514
复制相似问题