Here is the entire application. Below that is a step by step guide.
package main
import (
"bytes"
"io/ioutil"
"log"
"os"
)
func main() {
hello := []byte("hello") //The word to compare against, as bytes
holla := []byte("holla") //The word to change to, as bytes
f, err := os.OpenFile("test.txt", os.O_APPEND|os.O_RDWR, os.ModeAppend)
if err != nil {
log.Fatalln(err.Error())
}
defer f.Close()
bs, err := ioutil.ReadAll(f)
if err != nil {
log.Fatalln(err.Error())
}
hasWord, start := compare(bs, hello)
if !hasWord {
log.Fatalf("File does not have %s\n", string(hello))
}
for i := range holla {
bs[start+i] = holla[i]
}
_, err = f.WriteAt(bs, 0)
if err != nil {
log.Fatalln(err.Error())
}
log.Println("Done")
}
func compare(src, compare []byte) (bool, int) {
for i := range src {
if src[i] == compare[0] {
return bytes.Equal(src[i:i+len(compare)], compare), i
}
}
return false, 0
}
In our main function, we need to declare the word we're looking for and the word we're switching to. For convenience, I've made them the same amount of bytes. To replace 3 bytes with more or less than 3 bytes is slightly different.
You can write a slice of bytes (slices are not arrays, they are built on top of arrays) by creating the slice and then, in parentheses, writing the word in quotes. It's more convenient.
hello := []byte("hello") //The word to compare against, as bytes
holla := []byte("holla") //The word to change to, as bytes
Easier to read than this (not the real bytes):
hello := []byte{110, 115, 40, 40, 120} //The word to compare against, as bytes
holla := []byte{110, 95, 80, 90, 140} //The word to change to, as bytes
Now to open the file. We open the file with os.OpenFile instead of os.Open since we will need to read and write to the file. OpenFile takes attributes for rights. Open is read only. Go provides multiple returns, so you get your file and possibly an error.
f, err := os.OpenFile("test.txt", os.O_APPEND|os.O_RDWR, os.ModeAppend)
Our error checking will be nearly the same for the duration of the app.
if err != nil {
log.Fatalln(err.Error()) //or something similar
}
After we check for errors, we can defer the close. We do this after we check the error, always. If we did it before we checked the error, it's possible you didn't get a file, and you got an error, and the app will panic when you're trying to close something that never existed.
defer f.Close()
ioutil.ReadAll takes a reader and returns all the bytes from the reader. It accepts anything that implements the Reader interface. Another convenient function.
bs, err := ioutil.ReadAll(f)
if err != nil {
log.Fatalln(err.Error())
}
We now compare the bytes read in against the bytes returned from ReadAll. If hasWord was false, we can quit the app with a log.Fatalf call. If it does have the word, the index is assigned to the variable "start". We will go over what "compare" does later.
hasWord, start := compare(bs, hello)
if !hasWord {
log.Fatalf("File does not have %s\n", string(hello))
}
Now, we just replace the bytes, one for one.
for i := range holla {
bs[start+i] = holla[i]
}
Then write it out to the file, at byte index 0. If we didn't write at byte index 0, it would write at the end of the file and you would have two lines in the text file. We ignore the bytes written by assigning it to an underscore variable.
_, err = f.WriteAt(bs, 0)
if err != nil {
log.Fatalln(err.Error())
}
Then just log that you're done.
log.Println("Done")
Our compare function signature, compare(src, compare []byte) (bool, int)
says it takes in two byte slices
and returns a bool and an int. There's a bug in this -- what happens if the src is less than the compare? We should have checked for that :-P
All we're doing is looping through the source. If we find that any part of the source is equal to the first letter in the compare, check the next digits to see if they compare equally with "bytes.Equal()" from the standard library. If so, return true and the index/offset (i).
for i := range src {
if src[i] == compare[0] {
return bytes.Equal(src[i:i+len(compare)], compare), i
}
}
And if it doesn't find it, return false with return false, 0
That's it!