I'm writing a routine to walk a directory tree and create a digital signature (salted-hash) for each file I find. When testing it I get this weird behavior - if I give the program a root path "above" the directory, the program can walk the tree and print out the file names, but if I try and open the file to read it's bytes, I get the error message "no such file or directory" on the file that the routines found - not sure what gives here. How can the Walk() routine "see" the file, but ioutil.ReadFile() not be able to find it?
Sample Code:
// start with path higher up the tree, say $HOME
func doHashWalk(dirPath string) {
err := filepath.Walk(dirPath, walkFn)
// Check err here
}
func walkFn(path string, fi os.FileInfo, err error) (e error) {
if !fi.IsDir() {
// if the first character is a ".", then skip it as it's a hidden file
if strings.HasPrefix(fi.Name(), ".") {
return nil
}
// read in the file bytes -> get the absolute path
fullPath, err := filepath.Abs(fi.Name())
if err != nil {
log.Printf("Abs Err: %s", err)
}
// THIS ALWAYS FAILED WITH ERROR
bytes, err := ioutil.ReadFile(fullPath) // <-- (fi.Name() also doesn't work)
if err != nil {
log.Printf("Err: %s, Bytes: %d", err, len(bytes))
}
// create the salted hash
...
}
return nil
}
Try logging the values of path
vs. fullPath
inside of walkFn
.
Using filepath.Abs()
inside of walkFn
does not give the result you want: it's resolving a filename relative to the current working directory, instead of the original dirPath.
One option is to resolve the target directory to an absolute path up-front in doHashWalk
:
func doHashWalk(dirPath string) {
fullPath, err := filepath.Abs(dirPath)
if err != nil {
log.Println("path error:", err)
return
}
err = filepath.Walk(fullPath, walkFn)
// check err here
}
With that change, the walkFn
callback will always receive a fully-qualified path
argument; no need to call filepath.Abs()
again:
func walkFn(path string, fi os.FileInfo, err error) (e error) {
// ...
bytes, err := ioutil.ReadFile(path)
// ...
}
If it's important for your application to see the path of each file relative to the original dirPath
root, you can sneak that path into the walkFn
callback via a closure:
func doHashWalk(dirPath string) error {
fullPath, err := filepath.Abs(dirPath)
if err != nil {
return err
}
callback := func(path string, fi os.FileInfo, err error) error {
return hashFile(fullPath, path, fi, err)
}
return filepath.Walk(fullPath, callback)
}
func hashFile(root string, path string, fi os.FileInfo, err error) error {
if fi.IsDir() {
return nil
}
rel, err := filepath.Rel(root, path)
if err != nil {
return err
}
log.Println("hash rel:", rel, "abs:", path)
return nil
}
The problems is that you're not using the filepath.Walk
exactly the way it's intended. You allready get the path to the file from the path
parameter.
The docs might not exactly be abundantly clear but they do say this for filepath.Walk:
Walk walks the file tree rooted at root, calling walkFn for each file or directory in the tree, including root.
So you could shorten walkFn
to something like this:
func walkFn(path string, fi os.FileInfo, err error) (e error) {
if !fi.IsDir() {
bytes, err := ioutil.ReadFile(path) // path is the path to the file.
if err != nil {
fmt.Println("Fail")
}
}
return nil
}
Walk will take care of not walking up the file-tree via ..
there's no need for you to check for that. Considering the absolute path part I don't really think you need that either, but I make no promises ;)