英文:
Replacing a line within a file with Golang
问题
我是你的中文翻译助手,以下是你要翻译的内容:
我对Golang还不熟悉,正在尝试一些例子。目前,我想做的是逐行读取文件,并在满足某个条件时将其替换为另一个字符串。
用于测试的文件包含四行:
one
two
three
four
处理该文件的代码如下:
func main() {
file, err := os.OpenFile("test.txt", os.O_RDWR, 0666)
if err != nil {
panic(err)
}
reader := bufio.NewReader(file)
for {
fmt.Print("Try to read ...\n")
pos, _ := file.Seek(0, 1)
log.Printf("Position in file is: %d", pos)
bytes, _, _ := reader.ReadLine()
if len(bytes) == 0 {
break
}
lineString := string(bytes)
if lineString == "two" {
file.Seek(int64(-(len(lineString))), 1)
file.WriteString("This is a test.")
}
fmt.Printf(lineString + "\n")
}
file.Close()
}
从代码片段中可以看出,我希望在从文件中读取到字符串"two"时,将其替换为"This is a test"。
为了获取文件中的当前位置,我使用了Go的_Seek_方法。
然而,问题是每次都会将最后一行替换为"This is a test",使得文件看起来像这样:
one
two
three
This is a test
通过检查将当前文件位置写入终端的打印语句的输出,我发现在__读取完第一行之后__,会得到以下输出:
2016/12/28 21:10:31 Try to read ...
2016/12/28 21:10:31 Position in file is: 19
所以在第一次读取之后,位置光标已经指向了文件的末尾,这就解释了为什么新字符串会被追加到末尾。有人能理解这里发生了什么,或者是什么导致了这种行为吗?
英文:
I'm new to Golang, starting out with some examples. Currently, what I'm trying to do is reading a file line by line and replace it with another string in case it meets a certain condition.
The file is use for testing purposes contains four lines:
one
two
three
four
The code working on that file looks like this:
<!-- language: golang -->
func main() {
file, err := os.OpenFile("test.txt", os.O_RDWR, 0666)
if err != nil {
panic(err)
}
reader := bufio.NewReader(file)
for {
fmt.Print("Try to read ...\n")
pos,_ := file.Seek(0, 1)
log.Printf("Position in file is: %d", pos)
bytes, _, _ := reader.ReadLine()
if (len(bytes) == 0) {
break
}
lineString := string(bytes)
if(lineString == "two") {
file.Seek(int64(-(len(lineString))), 1)
file.WriteString("This is a test.")
}
fmt.Printf(lineString + "\n")
}
file.Close()
}
As you can see in the code snippet, I want to replace the string "two" with "This is a test" as soon as this string is read from the file.
In order to get the current position within the file I use Go's Seek method.
However, what happens is that always the last line gets replaced by This is a test, making the file looking like this:
one
two
three
This is a test
Examining the output of the print statement which writes the current file position to the terminal, I get that kind of output after the first line has been read:
2016/12/28 21:10:31 Try to read ...
2016/12/28 21:10:31 Position in file is: 19
So after the first read, the position cursor already points to the end of my file, which explains why the new string gets appended to the end. Does anyone understand what is happening here or rather what is causing that behavior?
答案1
得分: 3
读者不受file.Seek
控制。你声明了读者为:reader := bufio.NewReader(file)
,然后你一次读取一行:bytes, _, _ := reader.ReadLine()
,然而file.Seek
不会改变读者正在读取的位置。
建议你阅读文档中关于ReadSeeker的内容,并切换到使用它。还有一个使用SectionReader的示例。
英文:
The Reader is not controller by the file.Seek
. You have declared the reader as: reader := bufio.NewReader(file)
and then you read one line at a time bytes, _, _ := reader.ReadLine()
however the file.Seek
does not change the position that the reader is reading.
Suggest you read about the ReadSeeker in the docs and switch over to using that. Also there is an example using the SectionReader.
答案2
得分: 3
除了错误的 seek 用法之外,困难之处在于你要替换的行与替换内容的长度不同。标准的做法是创建一个带有修改的新(临时)文件。假设这一步成功,就用新文件替换原始文件。
package main
import (
"bufio"
"io"
"io/ioutil"
"log"
"os"
)
func main() {
// 要修改的文件
name := "text.txt"
// 打开原始文件
f, err := os.Open(name)
if err != nil {
log.Fatal(err)
}
defer f.Close()
// 创建临时文件
tmp, err := ioutil.TempFile("", "replace-*")
if err != nil {
log.Fatal(err)
}
defer tmp.Close()
// 在从 f 复制到 tmp 的同时进行替换
if err := replace(f, tmp); err != nil {
log.Fatal(err)
}
// 确保成功写入临时文件
if err := tmp.Close(); err != nil {
log.Fatal(err)
}
// 关闭正在读取的文件
if err := f.Close(); err != nil {
log.Fatal(err)
}
// 用临时文件覆盖原始文件
if err := os.Rename(tmp.Name(), name); err != nil {
log.Fatal(err)
}
}
func replace(r io.Reader, w io.Writer) error {
// 使用 scanner 逐行读取
sc := bufio.NewScanner(r)
for sc.Scan() {
line := sc.Text()
if line == "two" {
line = "This is a test."
}
if _, err := io.WriteString(w, line+"\n"); err != nil {
return err
}
}
return sc.Err()
}
对于更复杂的替换,我实现了一个可以替换正则表达式匹配的包。https://github.com/icholy/replace
import (
"io"
"regexp"
"github.com/icholy/replace"
"golang.org/x/text/transform"
)
func replace2(r io.Reader, w io.Writer) error {
// 编译多行正则表达式
re := regexp.MustCompile(`(?m)^two$`)
// 创建替换转换器
tr := replace.RegexpString(re, "This is a test.")
// 在复制的同时进行转换
_, err := io.Copy(w, transform.NewReader(r, tr))
return err
}
英文:
Aside from the incorrect seek usage, the difficulty is that the line you're replacing isn't the same length as the replacement. The standard approach is to create a new (temporary) file with the modifications. Assuming that is successful, replace the original file with the new one.
package main
import (
"bufio"
"io"
"io/ioutil"
"log"
"os"
)
func main() {
// file we're modifying
name := "text.txt"
// open original file
f, err := os.Open(name)
if err != nil {
log.Fatal(err)
}
defer f.Close()
// create temp file
tmp, err := ioutil.TempFile("", "replace-*")
if err != nil {
log.Fatal(err)
}
defer tmp.Close()
// replace while copying from f to tmp
if err := replace(f, tmp); err != nil {
log.Fatal(err)
}
// make sure the tmp file was successfully written to
if err := tmp.Close(); err != nil {
log.Fatal(err)
}
// close the file we're reading from
if err := f.Close(); err != nil {
log.Fatal(err)
}
// overwrite the original file with the temp file
if err := os.Rename(tmp.Name(), name); err != nil {
log.Fatal(err)
}
}
func replace(r io.Reader, w io.Writer) error {
// use scanner to read line by line
sc := bufio.NewScanner(r)
for sc.Scan() {
line := sc.Text()
if line == "two" {
line = "This is a test."
}
if _, err := io.WriteString(w, line+"\n"); err != nil {
return err
}
}
return sc.Err()
}
For more complex replacements, I've implemented a package which can replace regular expression matches. https://github.com/icholy/replace
import (
"io"
"regexp"
"github.com/icholy/replace"
"golang.org/x/text/transform"
)
func replace2(r io.Reader, w io.Writer) error {
// compile multi-line regular expression
re := regexp.MustCompile(`(?m)^two$`)
// create replace transformer
tr := replace.RegexpString(re, "This is a test.")
// copy while transforming
_, err := io.Copy(w, transform.NewReader(r, tr))
return err
}
答案3
得分: 1
OS包中有一个Expand函数,我相信它可以用来解决类似的问题。
解释:
file.txt
one
two
${num}
four
main.go
package main
import (
"fmt"
"os"
)
var FILENAME = "file.txt"
func main() {
file, err := os.ReadFile(FILENAME)
if err != nil {
panic(err)
}
mapper := func(placeholderName string) string {
switch placeholderName {
case "num":
return "three"
}
return ""
}
fmt.Println(os.Expand(string(file), mapper))
}
输出
one
two
three
four
此外,你可以创建一个配置文件(yml或json),并将数据填充到映射中,该映射可以用作查找表,用于存储占位符及其替换字符串,并修改mapper部分以使用该表从输入文件中查找占位符。
例如,映射将如下所示:
table := map[string]string {
"num": "three"
}
mapper := func(placeholderName string) string {
if val, ok := table[placeholderName]; ok {
return val
}
return ""
}
参考资料:
- os.Expand文档:https://pkg.go.dev/os#Expand
<kbd>Playground</kbd>
英文:
OS package has Expand function which I believe can be used to solve similar problem.
Explanation:
file.txt
one
two
${num}
four
main.go
package main
import (
"fmt"
"os"
)
var FILENAME = "file.txt"
func main() {
file, err := os.ReadFile(FILENAME)
if err != nil {
panic(err)
}
mapper := func(placeholderName string) string {
switch placeholderName {
case "num":
return "three"
}
return ""
}
fmt.Println(os.Expand(string(file), mapper))
}
output
one
two
three
four
Additionally, you may create a config (yml or json) and
populate that data in the map that can be used as a lookup table to store placeholders as well as their replacement strings and modify mapper part to use this table to lookup placeholders from input file.
e.g map will look like this,
table := map[string]string {
"num": "three"
}
mapper := func(placeholderName string) string {
if val, ok := table[placeholderName]; ok {
return val
}
return ""
}
References:
- os.Expand documentation: https://pkg.go.dev/os#Expand
<kbd>Playground</kbd>
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论