Skip to content

Instantly share code, notes, and snippets.

@natdm
Created September 26, 2016 16:15
Show Gist options
  • Save natdm/aaeb25c36e610f2af7802370e4dafbb2 to your computer and use it in GitHub Desktop.
Save natdm/aaeb25c36e610f2af7802370e4dafbb2 to your computer and use it in GitHub Desktop.
Compare bytes from a slice of bytes to a file and replace them with other bytes if found.

Reading and replacing some bytes!

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 the main function

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.

Declare comparative byte slices

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
Open file

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)
Check for errors

Our error checking will be nearly the same for the duration of the app.

if err != nil {
	log.Fatalln(err.Error()) //or something similar
}
Defer close

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() 
Read from open file

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())
}
Compare open file to target bytes

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))
}
Replace bytes

Now, we just replace the bytes, one for one.

for i := range holla {
	bs[start+i] = holla[i] 
}
Write back to file

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())
}
Done

Then just log that you're done.

log.Println("Done")

Now for the compare function

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!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment